[
  {
    "path": ".gitignore",
    "content": "config.js\n.cov\ncoverage\nnode_modules\n.naeindex\ncoverage.html\n.monitor\n\n*.min.*.js\n*.min.*.css\nassets.json\n\n# Ignore Mac OS desktop services store\n.DS_Store\n\n# Ignore Windows desktop setting file\ndesktop.ini\n\n# Ignore Redis snapshot\ndump.rdb\n\n*.log\n\n.idea\npublic/upload/*\n\n*.sublime-project\n*.sublime-workspace\n*.swp\n\npackage-lock.json\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n  \"predef\": [\n    \"phantom\",\n    \"module\",\n    \"require\",\n    \"__dirname\",\n    \"process\",\n    \"console\",\n    \"it\",\n    \"describe\",\n    \"before\",\n    \"beforeEach\",\n    \"after\",\n    \"afterEach\",\n    \"ace\",\n    \"$\"\n  ],\n\n  \"browser\": true,\n  \"node\": true,\n  \"es5\": true,\n  \"bitwise\": true,\n  \"curly\": true,\n  \"eqeqeq\": true,\n  \"forin\": false,\n  \"immed\": true,\n  \"latedef\": true,\n  \"newcap\": true,\n  \"noarg\": true,\n  \"noempty\": true,\n  \"nonew\": true,\n  \"plusplus\": false,\n  \"undef\": true,\n  \"strict\": false,\n  \"trailing\": false,\n  \"globalstrict\": true,\n  \"nonstandard\": true,\n  \"white\": true,\n  \"indent\": 2,\n  \"expr\": true,\n  \"multistr\": true,\n  \"onevar\": false,\n  \"unused\": \"vars\"\n}\n"
  },
  {
    "path": ".mention-bot",
    "content": "{\n  \"userBlacklist\": [\"huacnlee\"]\n}\n"
  },
  {
    "path": ".snyk",
    "content": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.12.0\nignore: {}\n# patches apply the minimum changes required to fix a vulnerability\npatch:\n  'npm:tunnel-agent:20170305':\n    - jpush-sdk > request > tunnel-agent:\n        patched: '2018-07-01T04:07:14.342Z'\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\n\nlanguage: node_js\n\nenv:\n  - CXX=g++-4.8\n\nnode_js:\n  - stable\n\naddons:\n  apt:\n    sources:\n    - ubuntu-toolchain-r-test\n    packages:\n    - g++-4.8\n\nservices:\n  - mongodb\n  - redis\n\nbefore_install:\n  - $CXX --version\n\nscript: make test-cov\n\nafter_success: npm i codecov && codecov\n"
  },
  {
    "path": "History.md",
    "content": "2.1.0 / 2015-09-15\n==================\n\n* 使用 oneapm 代替 newrelic\n\n2.0.1 / 2015-08-07\n==================\n\n* 去掉【收藏功能】\n\n0.3.6 / 2013-11-22\n==================\n\n  * fix #237 if topic not exists, do not modified it.\n  * Merge pull request #230 from JacksonTian/fix_null\n  * 修复给空值设置属性的错误\n  * Merge pull request #224 from JacksonTian/config_ga\n  * 将Google tracker可配置化\n  * Merge pull request #223 from jiyinyiyong/ga\n  * add Google Analytics\n  * Merge pull request #217 from leizongmin/master\n  * 编辑器进入全屏模式时，调整一下样式\n  * hotfix sendAtMail\n  * fixed \"TypeError: Cannot read property author_id of null\"\n  * 使用七牛 gravatar.qiniudn.com 镜像\n  * Merge pull request #212 from tjwudi/master\n  * Fix 'title' textarea layout problem.\n  * Merge pull request #211 from tjwudi/master\n  * Add OS regular files to .gitignore\n  * Merge pull request #209 from leizongmin/master\n  * 解决打开Topic时自动跳到输入框问题\n  * Merge pull request #205 from jiyinyiyong/master\n  * Merge pull request #208 from leizongmin/master\n  * 帖子内容页面，增加表格样式\n  * 发布帖子使用 EpicEditor 编辑器\n  * xss白名单 增加thead标签\n  * Merge pull request #204 from JacksonTian/assets_issue\n  * limit the length of message to 20\n  * 兼容未启用压缩功能的情况\n  * Merge pull request #193 from dead-horse/fix-block-count\n  * fix block count\n  * Merge pull request #190 from leizongmin/master\n  * 修正用户收藏的话题页面，页码链接不正确问题\n  * Merge pull request #186 from phoenixlzx/master\n  * Fixed mongoose version\n  * Merge pull request #182 from JacksonTian/assets_mini\n  * 替换debug为mini\n  * Merge pull request #181 from JacksonTian/assets\n  * 用config.debug判断是否线上状态\n  * Merge pull request #180 from JacksonTian/assets\n  * 升级data2xml\n  * 静态资源重构\n  * Merge pull request #178 from JacksonTian/typo\n  * Node.JS => Node.js\n  * Merge pull request #177 from JacksonTian/style\n  * 文章页面的样式\n  * Fix几个样式问题\n  * fixed #175 stars max size\n  * fixed require package.json nae not support bug\n  * Merge pull request #174 from JacksonTian/style\n  * 去除用标签来制造空格的行为\n  * 更新样式\n  * Merge pull request #171 from jiyinyiyong/markdown-p\n  * limit image height\n  * limit pre-wrap inside p\n  * Merge pull request #165 from VitoLau/master\n  * 更新about faq页面结构\n  * 调整about和faq页面 padding大小\n  * Merge pull request #164 from jiyinyiyong/master\n  * fix: markdown-text use pre-wrap\n  * format indentations to 2\n  * limit the with of message links to prevent line breaks\n  * 修复文字过长没有换行的问题\n\n0.3.5 / 2013-05-30\n==================\n\n  * Update logo based on the new official one (@finian)\n  * UI more flatter; use \"white-space:pre\" to show spaces (@jiyinyiyong)\n  * fixed #161 xss process after markdown transfer\n  * 修复reply2的逻辑；暂时屏蔽标签功能\n  * use fixed-width font in reply\n  * a text align and a padding\n  * add padding to the read messages\n  * 点击回复数直接跳到最后一个回复\n  * 支持html\n  * fixed #154 消息跳转没有直接跳转到回复\n  * fixed #146 修复tag编辑bug\n  * 增加nae config\n  * 增加邮件提示内容\n  * read file sync package.json\n\n0.3.4 / 2013-05-27\n==================\n\n  * user markd instead showdown, use ace (@fengmk2)\n  * 使用加粗的边缘线; 过滤粉红色的边缘线; 加深 panel header 颜色; 去掉 scrollbar 定制 (@jiyinyiyong)\n  * 指定xss模块的配置信息，禁止HTML标签的style和class属性 (@lezongmin)\n  * shanzhai'd Github T3T (@jiyinyiyong)\n  * @jiyinyiyong 修改界面布局 fixed #139\n  * 添加话题详情主要内容的行高 (@kerngven)\n  * 增加POST提交时间间隔限制 (@leizongmin)\n  * 中英文间用空格\n  * 发帖页面优化\n  * 文本框高度不要闪烁\n  * use bootstrap 2; hide tags\n  * see the demo of new UI\n  * 搜索页面，如果回复时间过长，会产生断行的情况\n  * unit test cases\n  * #132 Add https:// validate on user.js cnodejs/nodeclub#132 (@meteormatt)\n  * Add 0.10 for travis\n  * fixed #107 update user links\n\n0.3.3 / 2013-03-11\n==================\n\n  * Merge pull request #126 from cnodejs/updateSignFlow\n  * 修复topic更新bug；修复@某人 bug\n  * reply2也可以定位到\n  * 修复node 0.6 test cases\n  * 修复删除评论异常\n  * 修复 exports.updateLastReply 没有callback的bug\n  * 管理员可以帮忙激活账号\n  * Merge pull request #125 from JacksonTian/refine\n  * Fix http to https\n  * 重构注册和发帖以及发邮件的部分\n  * Merge pull request #117 from JacksonTian/get_post\n  * 去除掉req.method的判断，分拆方法\n  * Merge pull request #122 from JacksonTian/proxy\n  * 分离controller和数据操作业务逻辑\n  * 改完下划线驼峰为小驼峰式风格\n  * Merge pull request #116 from JacksonTian/codingstyle\n  * Coding style refine.\n  * Merge pull request #113 from JacksonTian/master\n  * 添加依赖服务状态图标\n  * Merge pull request #112 from JacksonTian/refine\n  * 修正rewire.reset()导致的单元测试异常\n  * Refine coding style\n  * Merge pull request #111 from JacksonTian/master\n  * Merge pull request #110 from JacksonTian/reset_history\n  * Update Authors\n  * 恢复History.md文件\n  * Merge pull request #104 from ccding/master\n  * fix issue #27: lower case email address for gravatar\n  * Merge pull request #103 from ccding/master\n  * fix issue #92: email address with gmail label (\"+\" encode)\n  * fixed topic delete not post method security problem.\n  * empty author\n  * fixed author empty bug\n  * Merge pull request #99 from leizongmin/master\n  * 将Markdown中的H标题解析放到代码块解析后面\n  * Merge pull request #96 from leizongmin/master\n  * 修正无法正确解析http://127.0.0.1这样的IP地址链接\n  * fixed font\n  * fixed color style\n  * Merge pull request #87 from jiyinyiyong/rebased\n  * Merge pull request #91 from leizongmin/master\n  * 使用xss模块来过滤主题及回复内容\n  * update to 0.3.2\n  * Merge remote branch 'cnode/master'\n  * fix escape\n  * Merge pull request #89 from dead-horse/master\n  * fix test\n  * use  in node-validator\n  * Merge remote branch 'cnode/master'\n  * support block code\n  * Merge pull request #88 from dead-horse/master\n  * fix\n  * change @me to markdown\n  * fix @ bug in topic content\n  * Merge pull request #86 from dead-horse/master\n  * not escape html in\n  * add preview\n  * remove tags in topics of home page\n  * some css\n  * 合并通知按钮\n  * use escape replace of xss()\n  * fixed test cases\n  * Merge pull request #85 from dead-horse/master\n  * 过滤url允许绝对路径\n\n0.3.2 / 2012-03-04\n==================\n\n  * ensure IncomingForm.UPLOAD_DIR\n  * ensure upload image dir exists\n  * fixed csrf bug in mark message read\n  * remove customHost\n  * add .naeignore files\n  * * merge cnodeclub to nodeclub; * add more settings for custom site; * fixed upload.js not worked bug;\n  * Merge pull request #4 from dead-horse/master\n  * Merge pull request #11 from thebrecht/master\n  * 话题回复数纳入二级回复，样式调整\n  * 支持table,邮件提醒\n  * 加入亂數產生新密碼\n  * fix style\n  * bugs fixed\n  * Merge pull request #3 from LeToNode/master\n  * Merge pull request #6 from ericzhang-cn/master\n  * markdown语法粗体应为两个星号，原描述有误\n  * Merge pull request #2 from roymax/master\n  * change to async\n  * change to async\n  * Update README.md\n  * 修复`abc+label@gmail.com`格式的注册邮箱不能成功激活的问题\n  * commit\n  * project init\n  * first commit\n"
  },
  {
    "path": "LICENSE",
    "content": "(The MIT License)\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "TESTS = $(shell find test -type f -name \"*.test.js\")\nTEST_TIMEOUT = 10000\nMOCHA_REPORTER = spec\n# NPM_REGISTRY = \"--registry=http://registry.npm.taobao.org\"\nNPM_REGISTRY = \"\"\n\n\nall: test\n\ninstall:\n\t@npm install $(NPM_REGISTRY)\n\npretest:\n\t@if ! test -f config.js; then \\\n\t\tcp config.default.js config.js; \\\n\tfi\n\t@if ! test -d public/upload; then \\\n\t\tmkdir public/upload; \\\n\tfi\n\ntest: install pretest\n\t@NODE_ENV=test ./node_modules/mocha/bin/mocha \\\n\t\t--reporter $(MOCHA_REPORTER) \\\n\t\t-r should \\\n\t\t-r test/env \\\n\t\t--timeout $(TEST_TIMEOUT) \\\n\t\t$(TESTS)\n\ntestfile:\n\t@NODE_ENV=test ./node_modules/mocha/bin/mocha \\\n\t\t--reporter $(MOCHA_REPORTER) \\\n\t\t-r should \\\n\t\t-r test/env \\\n\t\t--timeout $(TEST_TIMEOUT) \\\n\t\t$(FILE)\n\ntest-cov cov: install pretest\n\t@NODE_ENV=test node \\\n\t\tnode_modules/.bin/istanbul cover --preserve-comments \\\n\t\t./node_modules/.bin/_mocha \\\n\t\t-- \\\n\t\t-r should \\\n\t\t-r test/env \\\n\t\t--reporter $(MOCHA_REPORTER) \\\n\t\t--timeout $(TEST_TIMEOUT) \\\n\t\t$(TESTS)\n\n\nbuild:\n\t@./node_modules/loader-builder/bin/builder views .\n\nrun:\n\t@node app.js\n\nstart: install build\n\t@NODE_ENV=production ./node_modules/.bin/pm2 start app.js -i 0 --name \"cnode\" --max-memory-restart 400M\n\nrestart: install build\n\t@NODE_ENV=production ./node_modules/.bin/pm2 restart \"cnode\"\n\n.PHONY: install test testfile cov test-cov build run start restart\n"
  },
  {
    "path": "README.md",
    "content": "Nodeclub\n=\n\n[![build status][travis-image]][travis-url]\n[![codecov.io][codecov-image]][codecov-url]\n[![David deps][david-image]][david-url]\n[![node version][node-image]][node-url]\n\n[travis-image]: https://img.shields.io/travis/cnodejs/nodeclub/master.svg?style=flat-square\n[travis-url]: https://travis-ci.org/cnodejs/nodeclub\n[codecov-image]: https://img.shields.io/codecov/c/github/cnodejs/nodeclub/master.svg?style=flat-square\n[codecov-url]: https://codecov.io/github/cnodejs/nodeclub?branch=master\n[david-image]: https://img.shields.io/david/cnodejs/nodeclub.svg?style=flat-square\n[david-url]: https://david-dm.org/cnodejs/nodeclub\n[node-image]: https://img.shields.io/badge/node.js-%3E=_4.2-green.svg?style=flat-square\n[node-url]: http://nodejs.org/download/\n\n## 介绍\n\nNodeclub 是使用 **Node.js** 和 **MongoDB** 开发的社区系统，界面优雅，功能丰富，小巧迅速，\n已在Node.js 中文技术社区 [CNode(http://cnodejs.org)](http://cnodejs.org) 得到应用，但你完全可以用它搭建自己的社区。\n\n## 安装部署\n\n*不保证 Windows 系统的兼容性*\n\n线上跑的是 [Node.js](https://nodejs.org) v8.12.0，[MongoDB](https://www.mongodb.org) 是 v4.0.3，[Redis](http://redis.io) 是 v4.0.9。\n\n```\n1. 安装 `Node.js[必须]` `MongoDB[必须]` `Redis[必须]`\n2. 启动 MongoDB 和 Redis\n3. `$ make install` 安装 Nodeclub 的依赖包\n4. `cp config.default.js config.js` 请根据需要修改配置文件\n5. `$ make test` 确保各项服务都正常\n6. `$ node app.js`\n7. visit `http://localhost:3000`\n8. done!\n```\n\n## 测试\n\n跑测试\n\n```bash\n$ make test\n```\n\n跑覆盖率测试\n\n```bash\n$ make test-cov\n```\n\n## 贡献\n\n有任何意见或建议都欢迎提 issue，或者直接提给 [@alsotang](https://github.com/alsotang)\n\n## License\n\nMIT\n"
  },
  {
    "path": "api/v1/message.js",
    "content": "var eventproxy = require('eventproxy');\nvar Message    = require('../../proxy').Message;\nvar at           = require('../../common/at');\nvar renderHelper = require('../../common/render_helper');\nvar _          = require('lodash');\n\nvar index = function (req, res, next) {\n  var user_id = req.user._id;\n  var mdrender = req.query.mdrender === 'false' ? false : true;\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  ep.all('has_read_messages', 'hasnot_read_messages', function (has_read_messages, hasnot_read_messages) {\n    res.send({\n      success: true,\n      data: {\n        has_read_messages: has_read_messages,\n        hasnot_read_messages: hasnot_read_messages\n      }\n    });\n  });\n\n  ep.all('has_read', 'unread', function (has_read, unread) {\n    [has_read, unread].forEach(function (msgs, idx) {\n      var epfill = new eventproxy();\n      epfill.fail(next);\n      epfill.after('message_ready', msgs.length, function (docs) {\n        docs = docs.filter(function (doc) {\n          return !doc.is_invalid;\n        });\n        docs = docs.map(function (doc) {\n          doc.author = _.pick(doc.author, ['loginname', 'avatar_url']);\n          doc.topic  = _.pick(doc.topic, ['id', 'author', 'title', 'last_reply_at']);\n          doc.reply  = _.pick(doc.reply, ['id', 'content', 'ups', 'create_at']);\n          if (mdrender) {\n            doc.reply.content = renderHelper.markdown(at.linkUsers(doc.reply.content));\n          }\n          doc        = _.pick(doc, ['id', 'type', 'has_read', 'author', 'topic', 'reply', 'create_at']);\n\n          return doc;\n        });\n        ep.emit(idx === 0 ? 'has_read_messages' : 'hasnot_read_messages', docs);\n      });\n      msgs.forEach(function (doc) {\n        Message.getMessageById(doc._id, epfill.group('message_ready'));\n      });\n    });\n  });\n\n  Message.getReadMessagesByUserId(user_id, ep.done('has_read'));\n\n  Message.getUnreadMessageByUserId(user_id, ep.done('unread'));\n};\n\nexports.index = index;\n\nvar markAll = function (req, res, next) {\n  var user_id = req.user._id;\n  var ep = new eventproxy();\n  ep.fail(next);\n  Message.getUnreadMessageByUserId(user_id, ep.done('unread', function (docs) {\n    docs.forEach(function (doc) {\n      doc.has_read = true;\n      doc.save();\n    });\n    return docs;\n  }));\n\n  ep.all('unread', function (unread) {\n    unread = unread.map(function (doc) {\n      doc = _.pick(doc, ['id']);\n      return doc;\n    });\n    res.send({\n      success: true,\n      marked_msgs: unread\n    });\n  });\n};\n\nexports.markAll = markAll;\n\n\nvar markOne = function (req, res, next) {\n  var msg_id = req.params.msg_id;\n  var ep = new eventproxy();\n  ep.fail(next);\n  Message.updateOneMessageToRead(msg_id, ep.done('marked_result', function (result) {\n    return result;\n  }));\n\n  ep.all('marked_result', function (result) {\n    res.send({\n      success: true,\n      marked_msg_id: msg_id\n    });\n  });\n};\n\nexports.markOne = markOne;\n\n\nvar count = function (req, res, next) {\n  var userId = req.user.id;\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  Message.getMessagesCount(userId, ep.done(function (count) {\n    res.send({success: true, data: count});\n  }));\n};\n\nexports.count = count;\n"
  },
  {
    "path": "api/v1/middleware.js",
    "content": "var UserModel  = require('../../models').User;\nvar eventproxy = require('eventproxy');\nvar validator  = require('validator');\n\n// 非登录用户直接屏蔽\nvar auth = function (req, res, next) {\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  var accessToken = String(req.body.accesstoken || req.query.accesstoken || '');\n  accessToken = validator.trim(accessToken);\n\n  UserModel.findOne({accessToken: accessToken}, ep.done(function (user) {\n    if (!user) {\n      res.status(401);\n      return res.send({success: false, error_msg: '错误的accessToken'});\n    }\n    if (user.is_block) {\n      res.status(403);\n      return res.send({success: false, error_msg: '您的账户被禁用'});\n    }\n    req.user = user;\n    next();\n  }));\n\n};\n\nexports.auth = auth;\n\n// 非登录用户也可通过\nvar tryAuth = function (req, res, next) {\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  var accessToken = String(req.body.accesstoken || req.query.accesstoken || '');\n  accessToken = validator.trim(accessToken);\n\n  UserModel.findOne({accessToken: accessToken}, ep.done(function (user) {\n    if (!user) {\n      return next()\n    }\n    if (user.is_block) {\n      res.status(403);\n      return res.send({success: false, error_msg: '您的账户被禁用'});\n    }\n    req.user = user;\n    next();\n  }));\n\n};\n\nexports.tryAuth = tryAuth;\n"
  },
  {
    "path": "api/v1/reply.js",
    "content": "var eventproxy = require('eventproxy');\nvar validator  = require('validator');\nvar Topic      = require('../../proxy').Topic;\nvar User       = require('../../proxy').User;\nvar Reply      = require('../../proxy').Reply;\nvar at         = require('../../common/at');\nvar message    = require('../../common/message');\nvar config     = require('../../config');\n\nvar create = function (req, res, next) {\n  var topic_id = req.params.topic_id;\n  var content  = req.body.content || '';\n  var reply_id = req.body.reply_id;\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  var str = validator.trim(content);\n  if (str === '') {\n    res.status(400);\n    return res.send({success: false, error_msg: '回复内容不能为空'});\n  }\n\n  if (!validator.isMongoId(topic_id)) {\n    res.status(400);\n    return res.send({success: false, error_msg: '不是有效的话题id'});\n  }\n  \n  Topic.getTopic(topic_id, ep.done(function (topic) {\n    if (!topic) {\n      res.status(404);\n      return res.send({success: false, error_msg: '话题不存在'});\n    }\n    if (topic.lock) {\n      res.status(403);\n      return res.send({success: false, error_msg: '该话题已被锁定'});\n    }\n    ep.emit('topic', topic);\n  }));\n\n  ep.all('topic', function (topic) {\n    User.getUserById(topic.author_id, ep.done('topic_author'));\n  });\n\n  ep.all('topic', 'topic_author', function (topic, topicAuthor) {\n    Reply.newAndSave(content, topic_id, req.user.id, reply_id, ep.done(function (reply) {\n      Topic.updateLastReply(topic_id, reply._id, ep.done(function () {\n        ep.emit('reply_saved', reply);\n        //发送at消息，并防止重复 at 作者\n        var newContent = content.replace('@' + topicAuthor.loginname + ' ', '');\n        at.sendMessageToMentionUsers(newContent, topic_id, req.user.id, reply._id);\n      }));\n    }));\n\n    User.getUserById(req.user.id, ep.done(function (user) {\n      user.score += 5;\n      user.reply_count += 1;\n      user.save();\n      ep.emit('score_saved');\n    }));\n  });\n\n  ep.all('reply_saved', 'topic', function (reply, topic) {\n    if (topic.author_id.toString() !== req.user.id.toString()) {\n      message.sendReplyMessage(topic.author_id, req.user.id, topic._id, reply._id);\n    }\n    ep.emit('message_saved');\n  });\n\n  ep.all('reply_saved', 'message_saved', 'score_saved', function (reply) {\n    res.send({\n      success: true,\n      reply_id: reply._id\n    });\n  });\n};\n\nexports.create = create;\n\nvar ups = function (req, res, next) {\n  var replyId = req.params.reply_id;\n  var userId  = req.user.id;\n\n  if (!validator.isMongoId(replyId)) {\n    res.status(400);\n    return res.send({success: false, error_msg: '不是有效的评论id'});\n  }\n  \n  Reply.getReplyById(replyId, function (err, reply) {\n    if (err) {\n      return next(err);\n    }\n    if (!reply) {\n      res.status(404);\n      return res.send({success: false, error_msg: '评论不存在'});\n    }\n    if (reply.author_id.equals(userId) && !config.debug) {\n      res.status(403);\n      return res.send({success: false, error_msg: '不能帮自己点赞'});\n    } else {\n      var action;\n      reply.ups = reply.ups || [];\n      var upIndex = reply.ups.indexOf(userId);\n      if (upIndex === -1) {\n        reply.ups.push(userId);\n        action = 'up';\n      } else {\n        reply.ups.splice(upIndex, 1);\n        action = 'down';\n      }\n      reply.save(function () {\n        res.send({\n          success: true,\n          action: action\n        });\n      });\n    }\n  });\n};\n\nexports.ups = ups;\n"
  },
  {
    "path": "api/v1/tools.js",
    "content": "var eventproxy = require('eventproxy');\n\nvar accesstoken = function (req, res, next) {\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  res.send({\n    success: true,\n    loginname: req.user.loginname,\n    avatar_url: req.user.avatar_url,\n    id: req.user.id\n  });\n};\nexports.accesstoken = accesstoken;\n"
  },
  {
    "path": "api/v1/topic.js",
    "content": "var models       = require('../../models');\nvar TopicModel   = models.Topic;\nvar TopicProxy   = require('../../proxy').Topic;\nvar TopicCollect = require('../../proxy').TopicCollect;\nvar UserProxy    = require('../../proxy').User;\nvar UserModel    = models.User;\nvar config       = require('../../config');\nvar eventproxy   = require('eventproxy');\nvar _            = require('lodash');\nvar at           = require('../../common/at');\nvar renderHelper = require('../../common/render_helper');\nvar validator    = require('validator');\n\nvar index = function (req, res, next) {\n  var page     = parseInt(req.query.page, 10) || 1;\n  page         = page > 0 ? page : 1;\n  var tab      = req.query.tab || 'all';\n  var limit    = Number(req.query.limit) || config.list_topic_count;\n  var mdrender = req.query.mdrender === 'false' ? false : true;\n\n  var query = {};\n  if (!tab || tab === 'all') {\n    query.tab = {$nin: ['job', 'dev']}\n  } else {\n    if (tab === 'good') {\n      query.good = true;\n    } else {\n      query.tab = tab;\n    }\n  }\n  query.deleted = false;\n  var options = { skip: (page - 1) * limit, limit: limit, sort: '-top -last_reply_at'};\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  TopicModel.find(query, '', options, ep.done('topics'));\n\n  ep.all('topics', function (topics) {\n    topics.forEach(function (topic) {\n      UserModel.findById(topic.author_id, ep.done(function (author) {\n        if (mdrender) {\n          topic.content = renderHelper.markdown(at.linkUsers(topic.content));\n        }\n        topic.author = _.pick(author, ['loginname', 'avatar_url']);\n        ep.emit('author');\n      }));\n    });\n\n    ep.after('author', topics.length, function () {\n      topics = topics.map(function (topic) {\n        return _.pick(topic, ['id', 'author_id', 'tab', 'content', 'title', 'last_reply_at',\n          'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author']);\n      });\n\n      res.send({success: true, data: topics});\n    });\n  });\n};\n\nexports.index = index;\n\nvar show = function (req, res, next) {\n  var topicId  = String(req.params.id);\n\n  var mdrender = req.query.mdrender === 'false' ? false : true;\n  var ep       = new eventproxy();\n\n  if (!validator.isMongoId(topicId)) {\n    res.status(400);\n    return res.send({success: false, error_msg: '不是有效的话题id'});\n  }\n\n  ep.fail(next);\n\n  TopicProxy.getFullTopic(topicId, ep.done(function (msg, topic, author, replies) {\n    if (!topic) {\n      res.status(404);\n      return res.send({success: false, error_msg: '话题不存在'});\n    }\n    \n    topic.visit_count += 1;\n    topic.save();\n\n    topic = _.pick(topic, ['id', 'author_id', 'tab', 'content', 'title', 'last_reply_at',\n      'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author']);\n\n    if (mdrender) {\n      topic.content = renderHelper.markdown(at.linkUsers(topic.content));\n    }\n    topic.author = _.pick(author, ['loginname', 'avatar_url']);\n\n    topic.replies = replies.map(function (reply) {\n      if (mdrender) {\n        reply.content = renderHelper.markdown(at.linkUsers(reply.content));\n      }\n      reply.author = _.pick(reply.author, ['loginname', 'avatar_url']);\n      reply =  _.pick(reply, ['id', 'author', 'content', 'ups', 'create_at', 'reply_id']);\n      reply.reply_id = reply.reply_id || null;\n\n      if (reply.ups && req.user && reply.ups.indexOf(req.user._id) != -1) {\n        reply.is_uped = true;\n      } else {\n        reply.is_uped = false;\n      }\n\n      return reply;\n    });\n\n    ep.emit('full_topic', topic)\n  }));\n\n\n  if (!req.user) {\n    ep.emitLater('is_collect', null)\n  } else {\n    TopicCollect.getTopicCollect(req.user._id, topicId, ep.done('is_collect'))\n  }\n\n  ep.all('full_topic', 'is_collect', function (full_topic, is_collect) {\n    full_topic.is_collect = !!is_collect;\n\n    res.send({success: true, data: full_topic});\n  })\n\n};\n\nexports.show = show;\n\nvar create = function (req, res, next) {\n  var title   = validator.trim(req.body.title || '');\n  var tab     = validator.trim(req.body.tab || '');\n  var content = validator.trim(req.body.content || '');\n\n  // 得到所有的 tab, e.g. ['ask', 'share', ..]\n  var allTabs = config.tabs.map(function (tPair) {\n    return tPair[0];\n  });\n\n  // 验证\n  var editError;\n  if (title === '') {\n    editError = '标题不能为空';\n  } else if (title.length < 5 || title.length > 100) {\n    editError = '标题字数太多或太少';\n  } else if (!tab || !_.includes(allTabs, tab)) {\n    editError = '必须选择一个版块';\n  } else if (content === '') {\n    editError = '内容不可为空';\n  }\n  // END 验证\n\n  if (editError) {\n    res.status(400);\n    return res.send({success: false, error_msg: editError});\n  }\n\n  TopicProxy.newAndSave(title, content, tab, req.user.id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n\n    var proxy = new eventproxy();\n    proxy.fail(next);\n\n    proxy.all('score_saved', function () {\n      res.send({\n        success: true,\n        topic_id: topic.id\n      });\n    });\n    UserProxy.getUserById(req.user.id, proxy.done(function (user) {\n      user.score += 5;\n      user.topic_count += 1;\n      user.save();\n      req.user = user;\n      proxy.emit('score_saved');\n    }));\n\n    //发送at消息\n    at.sendMessageToMentionUsers(content, topic.id, req.user.id);\n  });\n};\n\nexports.create = create;\n\nexports.update = function (req, res, next) {\n  var topic_id = _.trim(req.body.topic_id);\n  var title    = _.trim(req.body.title);\n  var tab      = _.trim(req.body.tab);\n  var content  = _.trim(req.body.content);\n\n  // 得到所有的 tab, e.g. ['ask', 'share', ..]\n  var allTabs = config.tabs.map(function (tPair) {\n    return tPair[0];\n  });\n\n  TopicProxy.getTopicById(topic_id, function (err, topic, tags) {\n    if (!topic) {\n      res.status(400);\n      return res.send({success: false, error_msg: '此话题不存在或已被删除。'});\n    }\n\n    if (topic.author_id.equals(req.user._id) || req.user.is_admin) {\n      // 验证\n      var editError;\n      if (title === '') {\n        editError = '标题不能是空的。';\n      } else if (title.length < 5 || title.length > 100) {\n        editError = '标题字数太多或太少。';\n      } else if (!tab || !_.includes(allTabs, tab)) {\n        editError = '必须选择一个版块。';\n      }\n      // END 验证\n\n      if (editError) {\n        return res.send({success: false, error_msg: editError});\n      }\n\n      //保存话题\n      topic.title     = title;\n      topic.content   = content;\n      topic.tab       = tab;\n      topic.update_at = new Date();\n\n      topic.save(function (err) {\n        if (err) {\n          return next(err);\n        }\n        //发送at消息\n        at.sendMessageToMentionUsers(content, topic._id, req.user._id);\n\n        res.send({\n          success: true,\n          topic_id: topic.id\n        });\n      });\n    } else {\n      res.status(403)\n      return res.send({success: false, error_msg: '对不起，你不能编辑此话题。'});\n    }\n  });\n};\n\n"
  },
  {
    "path": "api/v1/topic_collect.js",
    "content": "var eventproxy = require('eventproxy');\nvar TopicProxy   = require('../../proxy').Topic;\nvar TopicCollectProxy = require('../../proxy').TopicCollect;\nvar UserProxy = require('../../proxy').User;\nvar _ = require('lodash');\nvar validator    = require('validator');\n\nfunction list(req, res, next) {\n  var loginname = req.params.loginname;\n  var ep        = new eventproxy();\n\n  ep.fail(next);\n\n  UserProxy.getUserByLoginName(loginname, ep.done(function (user) {\n    if (!user) {\n      res.status(404);\n      return res.send({success: false, error_msg: '用户不存在'});\n    }\n\n    // api 返回 100 条就好了\n    TopicCollectProxy.getTopicCollectsByUserId(user._id, {limit: 100}, ep.done('collected_topics'));\n\n    ep.all('collected_topics', function (collected_topics) {\n\n      var ids = collected_topics.map(function (doc) {\n        return String(doc.topic_id)\n      });\n      var query = { _id: { '$in': ids } };\n      TopicProxy.getTopicsByQuery(query, {}, ep.done('topics', function (topics) {\n        topics = _.sortBy(topics, function (topic) {\n          return ids.indexOf(String(topic._id))\n        });\n        return topics\n      }));\n\n    });\n\n    ep.all('topics', function (topics) {\n      topics = topics.map(function (topic) {\n        topic.author = _.pick(topic.author, ['loginname', 'avatar_url']);\n        return _.pick(topic, ['id', 'author_id', 'tab', 'content', 'title', 'last_reply_at',\n          'good', 'top', 'reply_count', 'visit_count', 'create_at', 'author']);\n      });\n      res.send({success: true, data: topics})\n\n    })\n  }))\n}\n\nexports.list = list;\n\nfunction collect(req, res, next) {\n  var topic_id = req.body.topic_id;\n\n  if (!validator.isMongoId(topic_id)) {\n    res.status(400);\n    return res.send({success: false, error_msg: '不是有效的话题id'});\n  }\n\n  TopicProxy.getTopic(topic_id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.status(404);\n      return res.json({success: false, error_msg: '话题不存在'});\n    }\n\n    TopicCollectProxy.getTopicCollect(req.user.id, topic._id, function (err, doc) {\n      if (err) {\n        return next(err);\n      }\n      if (doc) {\n        res.json({success: false});\n        return;\n      }\n\n      TopicCollectProxy.newAndSave(req.user.id, topic._id, function (err) {\n        if (err) {\n          return next(err);\n        }\n        res.json({success: true});\n      });\n      UserProxy.getUserById(req.user.id, function (err, user) {\n        if (err) {\n          return next(err);\n        }\n        user.collect_topic_count += 1;\n        user.save();\n      });\n\n      topic.collect_count += 1;\n      topic.save();\n    });\n  });\n}\n\nexports.collect = collect;\n\nfunction de_collect(req, res, next) {\n  var topic_id = req.body.topic_id;\n\n  if (!validator.isMongoId(topic_id)) {\n    res.status(400);\n    return res.send({success: false, error_msg: '不是有效的话题id'});\n  }\n\n  TopicProxy.getTopic(topic_id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.status(404);\n      return res.json({success: false, error_msg: '话题不存在'});\n    }\n    TopicCollectProxy.remove(req.user.id, topic._id, function (err, removeResult) {\n      if (err) {\n        return next(err);\n      }\n      if (removeResult.n == 0) {\n        return res.json({success: false})\n      }\n\n      UserProxy.getUserById(req.user.id, function (err, user) {\n        if (err) {\n          return next(err);\n        }\n        user.collect_topic_count -= 1;\n        user.save();\n      });\n\n      topic.collect_count -= 1;\n      topic.save();\n\n      res.json({success: true});\n    });\n\n  });\n}\n\nexports.de_collect = de_collect;\n"
  },
  {
    "path": "api/v1/user.js",
    "content": "var _            = require('lodash');\nvar eventproxy   = require('eventproxy');\nvar UserProxy    = require('../../proxy').User;\nvar TopicProxy   = require('../../proxy').Topic;\nvar ReplyProxy   = require('../../proxy').Reply;\nvar TopicCollect = require('../../proxy').TopicCollect;\n\nvar show = function (req, res, next) {\n  var loginname = req.params.loginname;\n  var ep        = new eventproxy();\n\n  ep.fail(next);\n\n  UserProxy.getUserByLoginName(loginname, ep.done(function (user) {\n    if (!user) {\n      res.status(404);\n      return res.send({success: false, error_msg: '用户不存在'});\n    }\n    var query = {author_id: user._id};\n    var opt = {limit: 15, sort: '-create_at'};\n    TopicProxy.getTopicsByQuery(query, opt, ep.done('recent_topics'));\n\n    ReplyProxy.getRepliesByAuthorId(user._id, {limit: 20, sort: '-create_at'},\n      ep.done(function (replies) {\n        var topic_ids = replies.map(function (reply) {\n          return reply.topic_id.toString()\n        });\n        topic_ids = _.uniq(topic_ids).slice(0, 5); //  只显示最近5条\n\n        var query = {_id: {'$in': topic_ids}};\n        var opt = {};\n        TopicProxy.getTopicsByQuery(query, opt, ep.done('recent_replies', function (recent_replies) {\n          recent_replies = _.sortBy(recent_replies, function (topic) {\n            return topic_ids.indexOf(topic._id.toString())\n          });\n          return recent_replies;\n        }));\n      }));\n\n    ep.all('recent_topics', 'recent_replies',\n      function (recent_topics, recent_replies) {\n\n        user = _.pick(user, ['loginname', 'avatar_url', 'githubUsername',\n          'create_at', 'score']);\n\n        user.recent_topics = recent_topics.map(function (topic) {\n          topic.author = _.pick(topic.author, ['loginname', 'avatar_url']);\n          topic        = _.pick(topic, ['id', 'author', 'title', 'last_reply_at']);\n          return topic;\n        });\n        user.recent_replies = recent_replies.map(function (topic) {\n          topic.author = _.pick(topic.author, ['loginname', 'avatar_url']);\n          topic        = _.pick(topic, ['id', 'author', 'title', 'last_reply_at']);\n          return topic;\n        });\n\n        res.send({success: true, data: user});\n      });\n  }));\n};\n\nexports.show = show;\n"
  },
  {
    "path": "api_router_v1.js",
    "content": "var express           = require('express');\nvar topicController   = require('./api/v1/topic');\nvar topicCollectController   = require('./api/v1/topic_collect');\nvar userController    = require('./api/v1/user');\nvar toolsController   = require('./api/v1/tools');\nvar replyController   = require('./api/v1/reply');\nvar messageController = require('./api/v1/message');\nvar middleware        = require('./api/v1/middleware');\nvar limit             = require('./middlewares/limit');\nvar config            = require('./config');\n\nvar router            = express.Router();\n\n\n// 主题\nrouter.get('/topics', topicController.index);\nrouter.get('/topic/:id', middleware.tryAuth, topicController.show);\nrouter.post('/topics', middleware.auth, limit.peruserperday('create_topic', config.create_post_per_day, {showJson: true}), topicController.create);\nrouter.post('/topics/update', middleware.auth, topicController.update);\n\n\n// 主题收藏\nrouter.post('/topic_collect/collect', middleware.auth, topicCollectController.collect); // 关注某话题\nrouter.post('/topic_collect/de_collect', middleware.auth, topicCollectController.de_collect); // 取消关注某话题\nrouter.get('/topic_collect/:loginname', topicCollectController.list);\n\n// 用户\nrouter.get('/user/:loginname', userController.show);\n\n\n\n// accessToken 测试\nrouter.post('/accesstoken', middleware.auth, toolsController.accesstoken);\n\n// 评论\nrouter.post('/topic/:topic_id/replies', middleware.auth, limit.peruserperday('create_reply', config.create_reply_per_day, {showJson: true}), replyController.create);\nrouter.post('/reply/:reply_id/ups', middleware.auth, replyController.ups);\n\n// 通知\nrouter.get('/messages', middleware.auth, messageController.index);\nrouter.get('/message/count', middleware.auth, messageController.count);\nrouter.post('/message/mark_all', middleware.auth, messageController.markAll);\nrouter.post('/message/mark_one/:msg_id', middleware.auth, messageController.markOne);\n\nmodule.exports = router;\n"
  },
  {
    "path": "app.js",
    "content": "/*!\n * nodeclub - app.js\n */\n\n/**\n * Module dependencies.\n */\n\nvar config = require('./config');\n\nif (!config.debug && config.oneapm_key) {\n  require('oneapm');\n}\n\nrequire('colors');\nvar path = require('path');\nvar Loader = require('loader');\nvar LoaderConnect = require('loader-connect')\nvar express = require('express');\nvar session = require('express-session');\nvar passport = require('passport');\nrequire('./middlewares/mongoose_log'); // 打印 mongodb 查询日志\nrequire('./models');\nvar GitHubStrategy = require('passport-github').Strategy;\nvar githubStrategyMiddleware = require('./middlewares/github_strategy');\nvar webRouter = require('./web_router');\nvar apiRouterV1 = require('./api_router_v1');\nvar auth = require('./middlewares/auth');\nvar errorPageMiddleware = require('./middlewares/error_page');\nvar proxyMiddleware = require('./middlewares/proxy');\nvar RedisStore = require('connect-redis')(session);\nvar _ = require('lodash');\nvar csurf = require('csurf');\nvar compress = require('compression');\nvar bodyParser = require('body-parser');\nvar busboy = require('connect-busboy');\nvar errorhandler = require('errorhandler');\nvar cors = require('cors');\nvar requestLog = require('./middlewares/request_log');\nvar renderMiddleware = require('./middlewares/render');\nvar logger = require('./common/logger');\nvar helmet = require('helmet');\nvar bytes = require('bytes')\n\n\n// 静态文件目录\nvar staticDir = path.join(__dirname, 'public');\n// assets\nvar assets = {};\n\nif (config.mini_assets) {\n  try {\n    assets = require('./assets.json');\n  } catch (e) {\n    logger.error('You must execute `make build` before start app when mini_assets is true.');\n    throw e;\n  }\n}\n\nvar urlinfo = require('url').parse(config.host);\nconfig.hostname = urlinfo.hostname || config.host;\n\nvar app = express();\n\n// configuration in all env\napp.set('views', path.join(__dirname, 'views'));\napp.set('view engine', 'html');\napp.engine('html', require('ejs-mate'));\napp.locals._layoutFile = 'layout.html';\napp.enable('trust proxy');\n\n// Request logger。请求时间\napp.use(requestLog);\n\nif (config.debug) {\n  // 渲染时间\n  app.use(renderMiddleware.render);\n}\n\n// 静态资源\nif (config.debug) {\n  app.use(LoaderConnect.less(__dirname)); // 测试环境用，编译 .less on the fly\n}\napp.use('/public', express.static(staticDir));\napp.use('/agent', proxyMiddleware.proxy);\n\n// 通用的中间件\napp.use(require('response-time')());\napp.use(helmet.frameguard('sameorigin'));\napp.use(bodyParser.json({limit: '1mb'}));\napp.use(bodyParser.urlencoded({ extended: true, limit: '1mb' }));\napp.use(require('method-override')());\napp.use(require('cookie-parser')(config.session_secret));\napp.use(compress());\napp.use(session({\n  secret: config.session_secret,\n  store: new RedisStore({\n    port: config.redis_port,\n    host: config.redis_host,\n    db: config.redis_db,\n    pass: config.redis_password,\n  }),\n  resave: false,\n  saveUninitialized: false,\n}));\n\n// oauth 中间件\napp.use(passport.initialize());\n\n// github oauth\npassport.serializeUser(function (user, done) {\n  done(null, user);\n});\npassport.deserializeUser(function (user, done) {\n  done(null, user);\n});\npassport.use(new GitHubStrategy(config.GITHUB_OAUTH, githubStrategyMiddleware));\n\n// custom middleware\napp.use(auth.authUser);\napp.use(auth.blockUser());\n\nif (!config.debug) {\n  app.use(function (req, res, next) {\n    if (req.path === '/api' || req.path.indexOf('/api') === -1) {\n      csurf()(req, res, next);\n      return;\n    }\n    next();\n  });\n  app.set('view cache', true);\n}\n\n// for debug\n// app.get('/err', function (req, res, next) {\n//   next(new Error('haha'))\n// });\n\n// set static, dynamic helpers\n_.extend(app.locals, {\n  config: config,\n  Loader: Loader,\n  assets: assets\n});\n\napp.use(errorPageMiddleware.errorPage);\n_.extend(app.locals, require('./common/render_helper'));\napp.use(function (req, res, next) {\n  res.locals.csrf = req.csrfToken ? req.csrfToken() : '';\n  next();\n});\n\napp.use(busboy({\n  limits: {\n    fileSize: bytes(config.file_limit)\n  }\n}));\n\n// routes\napp.use('/api/v1', cors(), apiRouterV1);\napp.use('/', webRouter);\n\n// error handler\nif (config.debug) {\n  app.use(errorhandler());\n} else {\n  app.use(function (err, req, res, next) {\n    logger.error(err);\n    return res.status(500).send('500 status');\n  });\n}\n\nif (!module.parent) {\n  app.listen(config.port, function () {\n    logger.info('NodeClub listening on port', config.port);\n    logger.info('God bless love....');\n    logger.info('You can debug your app with http://' + config.hostname + ':' + config.port);\n    logger.info('');\n  });\n}\n\nmodule.exports = app;\n"
  },
  {
    "path": "bin/fix_at_problem.js",
    "content": "// 一次性脚本\n// 修复之前重复编辑帖子会导致重复 @someone 的渲染问题\nvar TopicModel = require('../models').Topic;\n\nTopicModel.find({content: /\\[{2,}@/}).exec(function (err, topics) {\n  topics.forEach(function (topic) {\n    topic.content = fix(topic.content);\n    console.log(topic.id);\n    topic.save();\n  });\n});\n\nfunction fix(str) {\n  str = str.replace(/\\[{1,}(\\[@\\w+)(\\]\\(.+?\\))\\2+/, function (match_text, $1, $2) {\n    return $1 + $2;\n  });\n  return str;\n}\n"
  },
  {
    "path": "bin/fix_topic_collect_count.js",
    "content": "var TopicCollect = require('../models').TopicCollect;\nvar UserModel = require('../models').User;\nvar TopicModel = require('../models').Topic\n\n// 修复用户的topic_collect计数\nTopicCollect.aggregate(\n  [{\n    \"$group\" :\n      {\n        _id : {user_id: \"$user_id\"},\n        count : { $sum : 1}\n      }\n  }], function (err, result) {\n    result.forEach(function (row) {\n      var userId = row._id.user_id;\n      var count = row.count;\n\n      UserModel.findOne({\n        _id: userId\n      }, function (err, user) {\n\n        if (!user) {\n          return;\n        }\n\n        user.collect_topic_count = count;\n        user.save(function () {\n          console.log(user.loginname, count)\n        });\n      })\n    })\n  })\n\n  // 修复帖子的topic_collect计数\n  TopicCollect.aggregate(\n    [{\n      \"$group\" :\n        {\n          _id : {topic_id: \"$topic_id\"},\n          count : { $sum : 1}\n        }\n    }], function (err, result) {\n      result.forEach(function (row) {\n        var topic_id = row._id.topic_id;\n        var count = row.count;\n\n        TopicModel.findOne({\n          _id: topic_id\n        }, function (err, topic) {\n\n          if (!topic) {\n            return;\n          }\n\n          topic.collect_topic_count = count;\n          topic.save(function () {\n            console.log(topic.id, count)\n          });\n        })\n      })\n    })\n"
  },
  {
    "path": "bin/generate_accesstoken.js",
    "content": "// 一次性脚本\n// 为所有老用户生成 accessToken\n\nvar uuid = require('node-uuid');\nvar mongoose = require('mongoose');\nvar config = require('../config');\nvar async = require('async');\nrequire('../models/user');\n\nmongoose.connect(config.db, function (err) {\n  if (err) {\n    console.error('connect to %s error: ', config.db, err.message);\n    process.exit(1);\n  }\n});\n\nvar UserModel = mongoose.model('User');\n\nvar hasRemain = true;\nasync.whilst(\n  function () {\n    return hasRemain;\n  },\n  function (callback) {\n    UserModel.findOne({accessToken: {$exists: false}}, function (err, user) {\n      if (!user) {\n        hasRemain = false;\n        callback();\n        return;\n      }\n      user.accessToken = uuid.v4();\n      user.save(function () {\n        console.log(user.loginname + ' done!');\n        callback();\n      });\n    });\n  },\n  function (err) {\n    mongoose.disconnect();\n  });\n\n"
  },
  {
    "path": "bin/get_user_topics.js",
    "content": "var UserModel = require('../models').User;\nvar TopicModel = require('../models').Topic\n\n// usage:\n// node get_user_topics.js alsotang\nUserModel.findOne({\n  loginname: process.argv[2]\n}, function (err, user) {\n  TopicModel.find({\n    author_id: user._id\n  }, function (err, topics) {\n    console.log(topics)\n  })\n})"
  },
  {
    "path": "common/at.js",
    "content": "/*!\n * nodeclub - topic mention user controller.\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * Copyright(c) 2012 muyuan\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar User       = require('../proxy').User;\nvar Message    = require('./message');\nvar EventProxy = require('eventproxy');\nvar _          = require('lodash');\n\n/**\n * 从文本中提取出@username 标记的用户名数组\n * @param {String} text 文本内容\n * @return {Array} 用户名数组\n */\nvar fetchUsers = function (text) {\n  if (!text) {\n    return [];\n  }\n\n  var ignoreRegexs = [\n    /```.+?```/g, // 去除单行的 ```\n    /^```[\\s\\S]+?^```/gm, // ``` 里面的是 pre 标签内容\n    /`[\\s\\S]+?`/g, // 同一行中，`some code` 中内容也不该被解析\n    /^    .*/gm, // 4个空格也是 pre 标签，在这里 . 不会匹配换行\n    /\\b\\S*?@[^\\s]*?\\..+?\\b/g, // somebody@gmail.com 会被去除\n    /\\[@.+?\\]\\(\\/.+?\\)/g, // 已经被 link 的 username\n    /\\/@/g, // 一般是url中path的一部分\n  ];\n\n  ignoreRegexs.forEach(function (ignore_regex) {\n    text = text.replace(ignore_regex, '');\n  });\n\n  var results = text.match(/@[a-z0-9\\-_]+\\b/igm);\n  var names = [];\n  if (results) {\n    for (var i = 0, l = results.length; i < l; i++) {\n      var s = results[i];\n      //remove leading char @\n      s = s.slice(1);\n      names.push(s);\n    }\n  }\n  names = _.uniq(names);\n  return names;\n};\nexports.fetchUsers = fetchUsers;\n\n/**\n * 根据文本内容中读取用户，并发送消息给提到的用户\n * Callback:\n * - err, 数据库异常\n * @param {String} text 文本内容\n * @param {String} topicId 主题ID\n * @param {String} authorId 作者ID\n * @param {String} reply_id 回复ID\n * @param {Function} callback 回调函数\n */\nexports.sendMessageToMentionUsers = function (text, topicId, authorId, reply_id, callback) {\n  if (typeof reply_id === 'function') {\n    callback = reply_id;\n    reply_id = null;\n  }\n  callback = callback || _.noop;\n\n  User.getUsersByNames(fetchUsers(text), function (err, users) {\n    if (err || !users) {\n      return callback(err);\n    }\n    var ep = new EventProxy();\n    ep.fail(callback);\n\n    users = users.filter(function (user) {\n      return !user._id.equals(authorId);\n    });\n\n    ep.after('sent', users.length, function () {\n      callback();\n    });\n\n    users.forEach(function (user) {\n      Message.sendAtMessage(user._id, authorId, topicId, reply_id, ep.done('sent'));\n    });\n  });\n};\n\n/**\n * 根据文本内容，替换为数据库中的数据\n * Callback:\n * - err, 数据库异常\n * - text, 替换后的文本内容\n * @param {String} text 文本内容\n * @param {Function} callback 回调函数\n */\nexports.linkUsers = function (text, callback) {\n  var users = fetchUsers(text);\n  for (var i = 0, l = users.length; i < l; i++) {\n    var name = users[i];\n    text = text.replace(new RegExp('@' + name + '\\\\b(?!\\\\])', 'g'), '[@' + name + '](/user/' + name + ')');\n  }\n  if (!callback) {\n    return text;\n  }\n  return callback(null, text);\n};\n"
  },
  {
    "path": "common/cache.js",
    "content": "var redis  = require('./redis');\nvar _      = require('lodash');\nvar logger = require('./logger');\n\nvar get = function (key, callback) {\n  var t = new Date();\n  redis.get(key, function (err, data) {\n    if (err) {\n      return callback(err);\n    }\n    if (!data) {\n      return callback();\n    }\n    data = JSON.parse(data);\n    var duration = (new Date() - t);\n    logger.debug('Cache', 'get', key, (duration + 'ms').green);\n    callback(null, data);\n  });\n};\n\nexports.get = get;\n\n// time 参数可选，秒为单位\nvar set = function (key, value, time, callback) {\n  var t = new Date();\n\n  if (typeof time === 'function') {\n    callback = time;\n    time = null;\n  }\n  callback = callback || _.noop;\n  value = JSON.stringify(value);\n\n  if (!time) {\n    redis.set(key, value, callback);\n  } else {\n    redis.setex(key, time, value, callback);\n  }\n  var duration = (new Date() - t);\n  logger.debug(\"Cache\", \"set\", key, (duration + 'ms').green);\n};\n\nexports.set = set;\n"
  },
  {
    "path": "common/logger.js",
    "content": "var config = require('../config');\nvar pathLib = require('path')\n\nvar env = process.env.NODE_ENV || \"development\"\n\n\nvar log4js = require('log4js');\nlog4js.configure({\n  appenders: [\n    { type: 'console' },\n    { type: 'file', filename: pathLib.join(config.log_dir, 'cheese.log'), category: 'cheese' }\n  ]\n});\n\nvar logger = log4js.getLogger('cheese');\nlogger.setLevel(config.debug && env !== 'test' ? 'DEBUG' : 'ERROR')\n\nmodule.exports = logger;\n"
  },
  {
    "path": "common/mail.js",
    "content": "var mailer        = require('nodemailer');\nvar smtpTransport = require('nodemailer-smtp-transport');\nvar config        = require('../config');\nvar util          = require('util');\nvar logger = require('./logger');\nvar transporter     = mailer.createTransport(smtpTransport(config.mail_opts));\nvar SITE_ROOT_URL = 'http://' + config.host;\nvar async = require('async')\n\n/**\n * Send an email\n * @param {Object} data 邮件对象\n */\nvar sendMail = function (data) {\n  if (config.debug) {\n    return;\n  }\n\n  // 重试5次\n  async.retry({times: 5}, function (done) {\n    transporter.sendMail(data, function (err) {\n      if (err) {\n        // 写为日志\n        logger.error('send mail error', err, data);\n        return done(err);\n      }\n      return done()\n    });\n  }, function (err) {\n    if (err) {\n      return logger.error('send mail finally error', err, data);\n    }\n    logger.info('send mail success', data)\n  })\n};\nexports.sendMail = sendMail;\n\n/**\n * 发送激活通知邮件\n * @param {String} who 接收人的邮件地址\n * @param {String} token 重置用的token字符串\n * @param {String} name 接收人的用户名\n */\nexports.sendActiveMail = function (who, token, name) {\n  var from    = util.format('%s <%s>', config.name, config.mail_opts.auth.user);\n  var to      = who;\n  var subject = config.name + '社区帐号激活';\n  var html    = '<p>您好：' + name + '</p>' +\n    '<p>我们收到您在' + config.name + '社区的注册信息，请点击下面的链接来激活帐户：</p>' +\n    '<a href  = \"' + SITE_ROOT_URL + '/active_account?key=' + token + '&name=' + name + '\">激活链接</a>' +\n    '<p>若您没有在' + config.name + '社区填写过注册信息，说明有人滥用了您的电子邮箱，请删除此邮件，我们对给您造成的打扰感到抱歉。</p>' +\n    '<p>' + config.name + '社区 谨上。</p>';\n\n  exports.sendMail({\n    from: from,\n    to: to,\n    subject: subject,\n    html: html\n  });\n};\n\n/**\n * 发送密码重置通知邮件\n * @param {String} who 接收人的邮件地址\n * @param {String} token 重置用的token字符串\n * @param {String} name 接收人的用户名\n */\nexports.sendResetPassMail = function (who, token, name) {\n  var from = util.format('%s <%s>', config.name, config.mail_opts.auth.user);\n  var to = who;\n  var subject = config.name + '社区密码重置';\n  var html = '<p>您好：' + name + '</p>' +\n    '<p>我们收到您在' + config.name + '社区重置密码的请求，请在24小时内单击下面的链接来重置密码：</p>' +\n    '<a href=\"' + SITE_ROOT_URL + '/reset_pass?key=' + token + '&name=' + name + '\">重置密码链接</a>' +\n    '<p>若您没有在' + config.name + '社区填写过注册信息，说明有人滥用了您的电子邮箱，请删除此邮件，我们对给您造成的打扰感到抱歉。</p>' +\n    '<p>' + config.name + '社区 谨上。</p>';\n\n  exports.sendMail({\n    from: from,\n    to: to,\n    subject: subject,\n    html: html\n  });\n};\n"
  },
  {
    "path": "common/message.js",
    "content": "var models       = require('../models');\nvar eventproxy   = require('eventproxy');\nvar Message      = models.Message;\nvar User         = require('../proxy').User;\nvar messageProxy = require('../proxy/message');\nvar _            = require('lodash');\n\nexports.sendReplyMessage = function (master_id, author_id, topic_id, reply_id, callback) {\n  callback = callback || _.noop;\n  var ep = new eventproxy();\n  ep.fail(callback);\n\n  var message       = new Message();\n  message.type      = 'reply';\n  message.master_id = master_id;\n  message.author_id = author_id;\n  message.topic_id  = topic_id;\n  message.reply_id  = reply_id;\n\n  message.save(ep.done('message_saved'));\n  ep.all('message_saved', function (msg) {\n    callback(null, msg);\n  });\n};\n\nexports.sendAtMessage = function (master_id, author_id, topic_id, reply_id, callback) {\n  callback = callback || _.noop;\n  var ep = new eventproxy();\n  ep.fail(callback);\n\n  var message       = new Message();\n  message.type      = 'at';\n  message.master_id = master_id;\n  message.author_id = author_id;\n  message.topic_id  = topic_id;\n  message.reply_id  = reply_id;\n\n  message.save(ep.done('message_saved'));\n  ep.all('message_saved', function (msg) {\n    callback(null, msg);\n  });\n};\n"
  },
  {
    "path": "common/redis.js",
    "content": "var config = require('../config');\nvar Redis = require('ioredis');\nvar logger = require('./logger')\n\nvar client = new Redis({\n  port: config.redis_port,\n  host: config.redis_host,\n  db: config.redis_db,\n  password: config.redis_password,\n});\n\nclient.on('error', function (err) {\n  if (err) {\n    logger.error('connect to redis error, check your redis config', err);\n    process.exit(1);\n  }\n})\n\nexports = module.exports = client;\n"
  },
  {
    "path": "common/render_helper.js",
    "content": "/*!\n * nodeclub - common/render_helpers.js\n * Copyright(c) 2013 fengmk2 <fengmk2@gmail.com>\n * MIT Licensed\n */\n\n\"use strict\";\n\n/**\n * Module dependencies.\n */\n\nvar MarkdownIt = require('markdown-it');\nvar _          = require('lodash');\nvar config     = require('../config');\nvar validator  = require('validator');\nvar jsxss      = require('xss');\nvar multiline = require('multiline')\n\n// Set default options\nvar md = new MarkdownIt();\n\nmd.set({\n  html:         false,        // Enable HTML tags in source\n  xhtmlOut:     false,        // Use '/' to close single tags (<br />)\n  breaks:       false,        // Convert '\\n' in paragraphs into <br>\n  linkify:      true,        // Autoconvert URL-like text to links\n  typographer:  true,        // Enable smartypants and other sweet transforms\n});\n\nmd.renderer.rules.fence = function (tokens, idx) {\n  var token    = tokens[idx];\n  var language = token.info && ('language-' + token.info) || '';\n  language     = validator.escape(language);\n\n  return '<pre class=\"prettyprint ' + language + '\">'\n    + '<code>' + validator.escape(token.content) + '</code>'\n    + '</pre>';\n};\n\nmd.renderer.rules.code_block = function (tokens, idx /*, options*/) {\n  var token    = tokens[idx];\n\n  return '<pre class=\"prettyprint\">'\n    + '<code>' + validator.escape(token.content) + '</code>'\n    + '</pre>';\n};\n\nvar myxss = new jsxss.FilterXSS({\n  onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {\n    // 让 prettyprint 可以工作\n    if (tag === 'pre' && name === 'class') {\n      return name + '=\"' + jsxss.escapeAttrValue(value) + '\"';\n    }\n  }\n});\n\nexports.markdown = function (text) {\n  return '<div class=\"markdown-text\">' + myxss.process(md.render(text || '')) + '</div>';\n};\n\nexports.escapeSignature = function (signature) {\n  return signature.split('\\n').map(function (p) {\n    return _.escape(p);\n  }).join('<br>');\n};\n\nexports.staticFile = function (filePath) {\n  if (filePath.indexOf('http') === 0 || filePath.indexOf('//') === 0) {\n    return filePath;\n  }\n  return config.site_static_host + filePath;\n};\n\nexports.tabName = function (tab) {\n  var pair = _.find(config.tabs, function (pair) {\n    return pair[0] === tab;\n  });\n  if (pair) {\n    return pair[1];\n  }\n};\n\nexports.proxy = function (url) {\n  return url;\n  // 当 google 和 github 封锁严重时，则需要通过服务器代理访问它们的静态资源\n  // return '/agent?url=' + encodeURIComponent(url);\n};\n\n// 为了在 view 中使用\nexports._ = _;\nexports.multiline = multiline;\n"
  },
  {
    "path": "common/store.js",
    "content": "var qn    = require('./store_qn');\nvar local = require('./store_local');\n\nmodule.exports = qn || local;\n"
  },
  {
    "path": "common/store_local.js",
    "content": "var config  = require('../config');\nvar utility = require('utility');\nvar path    = require('path');\nvar fs      = require('fs');\n\nexports.upload = function (file, options, callback) {\n  var filename = options.filename;\n\n  var newFilename = utility.md5(filename + String((new Date()).getTime())) +\n    path.extname(filename);\n\n  var upload_path = config.upload.path;\n  var base_url    = config.upload.url;\n  var filePath    = path.join(upload_path, newFilename);\n  var fileUrl     = base_url + newFilename;\n\n  file.on('end', function () {\n    callback(null, {\n      url: fileUrl\n    });\n  });\n\n  file.pipe(fs.createWriteStream(filePath));\n};\n"
  },
  {
    "path": "common/store_qn.js",
    "content": "var qn     = require('qn');\nvar config = require('../config');\n\n//7牛 client\nvar qnClient = null;\nif (config.qn_access && config.qn_access.secretKey !== 'your secret key') {\n  qnClient = qn.create(config.qn_access);\n}\n\nmodule.exports = qnClient;\n"
  },
  {
    "path": "common/tools.js",
    "content": "var bcrypt = require('bcryptjs');\nvar moment = require('moment');\n\nmoment.locale('zh-cn'); // 使用中文\n\n// 格式化时间\nexports.formatDate = function (date, friendly) {\n  date = moment(date);\n\n  if (friendly) {\n    return date.fromNow();\n  } else {\n    return date.format('YYYY-MM-DD HH:mm');\n  }\n\n};\n\nexports.validateId = function (str) {\n  return (/^[a-zA-Z0-9\\-_]+$/i).test(str);\n};\n\nexports.bhash = function (str, callback) {\n  bcrypt.hash(str, 10, callback);\n};\n\nexports.bcompare = function (str, hash, callback) {\n  bcrypt.compare(str, hash, callback);\n};\n"
  },
  {
    "path": "config.default.js",
    "content": "/**\n * config\n */\n\nvar path = require('path');\n\nvar config = {\n  // debug 为 true 时，用于本地调试\n  debug: true,\n\n  get mini_assets() { return !this.debug; }, // 是否启用静态文件的合并压缩，详见视图中的Loader\n\n  name: 'Nodeclub', // 社区名字\n  description: 'CNode：Node.js专业中文社区', // 社区的描述\n  keywords: 'nodejs, node, express, connect, socket.io',\n\n  // 添加到 html head 中的信息\n  site_headers: [\n    '<meta name=\"author\" content=\"EDP@TAOBAO\" />'\n  ],\n  site_logo: '/public/images/cnodejs_light.svg', // default is `name`\n  site_icon: '/public/images/cnode_icon_32.png', // 默认没有 favicon, 这里填写网址\n  // 右上角的导航区\n  site_navs: [\n    // 格式 [ path, title, [target=''] ]\n    [ '/about', '关于' ]\n  ],\n  // cdn host，如 http://cnodejs.qiniudn.com\n  site_static_host: '', // 静态文件存储域名\n  // 社区的域名\n  host: 'localhost',\n  // 默认的Google tracker ID，自有站点请修改，申请地址：http://www.google.com/analytics/\n  google_tracker_id: '',\n  // 默认的cnzz tracker ID，自有站点请修改\n  cnzz_tracker_id: '',\n\n  // mongodb 配置\n  db: 'mongodb://127.0.0.1/node_club_dev',\n\n  // redis 配置，默认是本地\n  redis_host: '127.0.0.1',\n  redis_port: 6379,\n  redis_db: 0,\n  redis_password: '',\n\n  session_secret: 'node_club_secret', // 务必修改\n  auth_cookie_name: 'node_club',\n\n  // 程序运行的端口\n  port: 3000,\n\n  // 话题列表显示的话题数量\n  list_topic_count: 20,\n\n  // RSS配置\n  rss: {\n    title: 'CNode：Node.js专业中文社区',\n    link: 'http://cnodejs.org',\n    language: 'zh-cn',\n    description: 'CNode：Node.js专业中文社区',\n    //最多获取的RSS Item数量\n    max_rss_items: 50\n  },\n\n  log_dir: path.join(__dirname, 'logs'),\n\n  // 邮箱配置\n  mail_opts: {\n    host: 'smtp.126.com',\n    port: 25,\n    auth: {\n      user: 'club@126.com',\n      pass: 'club'\n    },\n    ignoreTLS: true,\n  },\n\n  //weibo app key\n  weibo_key: 10000000,\n  weibo_id: 'your_weibo_id',\n\n  // admin 可删除话题，编辑标签。把 user_login_name 换成你的登录名\n  admins: { user_login_name: true },\n\n  // github 登陆的配置\n  GITHUB_OAUTH: {\n    clientID: 'your GITHUB_CLIENT_ID',\n    clientSecret: 'your GITHUB_CLIENT_SECRET',\n    callbackURL: 'http://cnodejs.org/auth/github/callback'\n  },\n  // 是否允许直接注册（否则只能走 github 的方式）\n  allow_sign_up: true,\n\n  // oneapm 是个用来监控网站性能的服务\n  oneapm_key: '',\n\n  // 下面两个配置都是文件上传的配置\n\n  // 7牛的access信息，用于文件上传\n  qn_access: {\n    accessKey: 'your access key',\n    secretKey: 'your secret key',\n    bucket: 'your bucket name',\n    origin: 'http://your qiniu domain',\n    // 如果vps在国外，请使用 http://up.qiniug.com/ ，这是七牛的国际节点\n    // 如果在国内，此项请留空\n    uploadURL: 'http://xxxxxxxx',\n  },\n\n  // 文件上传配置\n  // 注：如果填写 qn_access，则会上传到 7牛，以下配置无效\n  upload: {\n    path: path.join(__dirname, 'public/upload/'),\n    url: '/public/upload/'\n  },\n\n  file_limit: '1MB',\n\n  // 版块\n  tabs: [\n    ['share', '分享'],\n    ['ask', '问答'],\n    ['job', '招聘'],\n  ],\n\n  // 极光推送\n  jpush: {\n    appKey: 'YourAccessKeyyyyyyyyyyyy',\n    masterSecret: 'YourSecretKeyyyyyyyyyyyyy',\n    isDebug: false,\n  },\n\n  create_post_per_day: 1000, // 每个用户一天可以发的主题数\n  create_reply_per_day: 1000, // 每个用户一天可以发的评论数\n  create_user_per_ip: 1000, // 每个 ip 每天可以注册账号的次数\n  visit_per_day: 1000, // 每个 ip 每天能访问的次数\n};\n\nif (process.env.NODE_ENV === 'test') {\n  config.db = 'mongodb://127.0.0.1/node_club_test';\n}\n\nmodule.exports = config;\n"
  },
  {
    "path": "controllers/github.js",
    "content": "var Models         = require('../models');\nvar User           = Models.User;\nvar authMiddleWare = require('../middlewares/auth');\nvar tools          = require('../common/tools');\nvar eventproxy     = require('eventproxy');\nvar uuid           = require('node-uuid');\nvar validator      = require('validator');\n\nexports.callback = function (req, res, next) {\n  var profile = req.user;\n  var email = profile.emails && profile.emails[0] && profile.emails[0].value;\n  if (!email) {\n    return res.status(500)\n      .render('sign/no_github_email');\n  }\n  User.findOne({githubId: profile.id}, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    // 当用户已经是 cnode 用户时，通过 github 登陆将会更新他的资料\n    if (user) {\n      user.githubUsername = profile.username;\n      user.githubId = profile.id;\n      user.githubAccessToken = profile.accessToken;\n      // user.loginname = profile.username;\n      user.avatar = profile._json.avatar_url;\n      user.email = email || user.email;\n\n\n      user.save(function (err) {\n        if (err) {\n          // 根据 err.err 的错误信息决定如何回应用户，这个地方写得很难看\n          if (err.message.indexOf('duplicate key error') !== -1) {\n            if (err.message.indexOf('loginname') !== -1) {\n              return res.status(500)\n                .send('您 GitHub 账号的用户名与之前在 CNodejs 注册的用户名重复了');\n            }\n          }\n          return next(err);\n        }\n        authMiddleWare.gen_session(user, res);\n        return res.redirect('/');\n      });\n    } else {\n      // 如果用户还未存在，则建立新用户\n      req.session.profile = profile;\n      return res.redirect('/auth/github/new');\n    }\n  });\n};\n\nexports.new = function (req, res, next) {\n  res.render('sign/new_oauth', {actionPath: '/auth/github/create'});\n};\n\nexports.create = function (req, res, next) {\n  var profile = req.session.profile;\n\n  var isnew = req.body.isnew;\n  var loginname = validator.trim(req.body.name || '').toLowerCase();\n  var password = validator.trim(req.body.pass || '');\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  if (!profile) {\n    return res.redirect('/signin');\n  }\n  delete req.session.profile;\n\n  var email = profile.emails && profile.emails[0] && profile.emails[0].value;\n  if (!email) {\n    return res.status(500)\n      .render('sign/no_github_email');\n  }\n  if (isnew) { // 注册新账号\n    var user = new User({\n      loginname: profile.username,\n      pass: profile.accessToken,\n      email: email,\n      avatar: profile._json.avatar_url,\n      githubId: profile.id,\n      githubUsername: profile.username,\n      githubAccessToken: profile.accessToken,\n      active: true,\n      accessToken: uuid.v4(),\n    });\n    user.save(function (err) {\n      if (err) {\n        // 根据 err.err 的错误信息决定如何回应用户，这个地方写得很难看\n        if (err.message.indexOf('duplicate key error') !== -1) {\n          if (err.message.indexOf('loginname') !== -1) {\n            return res.status(500)\n              .send('您 GitHub 账号的用户名与之前在 CNodejs 注册的用户名重复了');\n          }\n        }\n        return next(err);\n        // END 根据 err.err 的错误信息决定如何回应用户，这个地方写得很难看\n      }\n      authMiddleWare.gen_session(user, res);\n      res.redirect('/');\n    });\n  } else { // 关联老账号\n    ep.on('login_error', function (login_error) {\n      res.status(403);\n      res.render('sign/signin', { error: '账号名或密码错误。' });\n    });\n    User.findOne({loginname: loginname},\n      ep.done(function (user) {\n        if (!user) {\n          return ep.emit('login_error');\n        }\n        tools.bcompare(password, user.pass, ep.done(function (bool) {\n          if (!bool) {\n            return ep.emit('login_error');\n          }\n          user.githubUsername = profile.username;\n          user.githubId = profile.id;\n          // user.loginname = profile.username;\n          user.avatar = profile._json.avatar_url;\n          user.githubAccessToken = profile.accessToken;\n\n          user.save(function (err) {\n            if (err) {\n              return next(err);\n            }\n            authMiddleWare.gen_session(user, res);\n            res.redirect('/');\n          });\n        }));\n      }));\n  }\n};\n"
  },
  {
    "path": "controllers/message.js",
    "content": "var Message    = require('../proxy').Message;\nvar eventproxy = require('eventproxy');\n\nexports.index = function (req, res, next) {\n  var user_id = req.session.user._id;\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  ep.all('has_read_messages', 'hasnot_read_messages', function (has_read_messages, hasnot_read_messages) {\n    res.render('message/index', {has_read_messages: has_read_messages, hasnot_read_messages: hasnot_read_messages});\n  });\n\n  ep.all('has_read', 'unread', function (has_read, unread) {\n    [has_read, unread].forEach(function (msgs, idx) {\n      var epfill = new eventproxy();\n      epfill.fail(next);\n      epfill.after('message_ready', msgs.length, function (docs) {\n        docs = docs.filter(function (doc) {\n          return !doc.is_invalid;\n        });\n        ep.emit(idx === 0 ? 'has_read_messages' : 'hasnot_read_messages', docs);\n      });\n      msgs.forEach(function (doc) {\n        Message.getMessageRelations(doc, epfill.group('message_ready'));\n      });\n    });\n    \n    Message.updateMessagesToRead(user_id, unread);\n  });\n\n  Message.getReadMessagesByUserId(user_id, ep.done('has_read'));\n  Message.getUnreadMessageByUserId(user_id, ep.done('unread'));\n};\n"
  },
  {
    "path": "controllers/reply.js",
    "content": "var validator  = require('validator');\nvar _          = require('lodash');\nvar at         = require('../common/at');\nvar message    = require('../common/message');\nvar EventProxy = require('eventproxy');\nvar User       = require('../proxy').User;\nvar Topic      = require('../proxy').Topic;\nvar Reply      = require('../proxy').Reply;\nvar config     = require('../config');\n\n/**\n * 添加回复\n */\nexports.add = function (req, res, next) {\n  var content = req.body.r_content;\n  var topic_id = req.params.topic_id;\n  var reply_id = req.body.reply_id;\n\n  var str = validator.trim(String(content));\n  if (str === '') {\n    return res.renderError('回复内容不能为空!', 422);\n  }\n\n  var ep = EventProxy.create();\n  ep.fail(next);\n\n  Topic.getTopic(topic_id, ep.doneLater(function (topic) {\n    if (!topic) {\n      ep.unbind();\n      // just 404 page\n      return next();\n    }\n\n    if (topic.lock) {\n      return res.status(403).send('此主题已锁定。');\n    }\n    ep.emit('topic', topic);\n  }));\n\n  ep.all('topic', function (topic) {\n    User.getUserById(topic.author_id, ep.done('topic_author'));\n  });\n\n  ep.all('topic', 'topic_author', function (topic, topicAuthor) {\n    Reply.newAndSave(content, topic_id, req.session.user._id, reply_id, ep.done(function (reply) {\n      Topic.updateLastReply(topic_id, reply._id, ep.done(function () {\n        ep.emit('reply_saved', reply);\n        //发送at消息，并防止重复 at 作者\n        var newContent = content.replace('@' + topicAuthor.loginname + ' ', '');\n        at.sendMessageToMentionUsers(newContent, topic_id, req.session.user._id, reply._id);\n      }));\n    }));\n\n    User.getUserById(req.session.user._id, ep.done(function (user) {\n      user.score += 5;\n      user.reply_count += 1;\n      user.save();\n      req.session.user = user;\n      ep.emit('score_saved');\n    }));\n  });\n\n  ep.all('reply_saved', 'topic', function (reply, topic) {\n    if (topic.author_id.toString() !== req.session.user._id.toString()) {\n      message.sendReplyMessage(topic.author_id, req.session.user._id, topic._id, reply._id);\n    }\n    ep.emit('message_saved');\n  });\n\n  ep.all('reply_saved', 'message_saved', 'score_saved', function (reply) {\n    res.redirect('/topic/' + topic_id + '#' + reply._id);\n  });\n};\n\n/**\n * 删除回复信息\n */\nexports.delete = function (req, res, next) {\n  var reply_id = req.body.reply_id;\n  Reply.getReplyById(reply_id, function (err, reply) {\n    if (err) {\n      return next(err);\n    }\n\n    if (!reply) {\n      res.status(422);\n      res.json({status: 'no reply ' + reply_id + ' exists'});\n      return;\n    }\n    if (reply.author_id.toString() === req.session.user._id.toString() || req.session.user.is_admin) {\n      reply.deleted = true;\n      reply.save();\n      res.json({status: 'success'});\n\n      reply.author.score -= 5;\n      reply.author.reply_count -= 1;\n      reply.author.save();\n    } else {\n      res.json({status: 'failed'});\n      return;\n    }\n\n    Topic.reduceCount(reply.topic_id, _.noop);\n  });\n};\n/*\n 打开回复编辑器\n */\nexports.showEdit = function (req, res, next) {\n  var reply_id = req.params.reply_id;\n\n  Reply.getReplyById(reply_id, function (err, reply) {\n    if (!reply) {\n      return res.render404('此回复不存在或已被删除。');\n    }\n    if (req.session.user._id.equals(reply.author_id) || req.session.user.is_admin) {\n      res.render('reply/edit', {\n        reply_id: reply._id,\n        content: reply.content\n      });\n    } else {\n      return res.renderError('对不起，你不能编辑此回复。', 403);\n    }\n  });\n};\n/*\n 提交编辑回复\n */\nexports.update = function (req, res, next) {\n  var reply_id = req.params.reply_id;\n  var content = req.body.t_content;\n\n  Reply.getReplyById(reply_id, function (err, reply) {\n    if (!reply) {\n      return res.render404('此回复不存在或已被删除。');\n    }\n\n    if (String(reply.author_id) === req.session.user._id.toString() || req.session.user.is_admin) {\n\n      if (content.trim().length > 0) {\n        reply.content = content;\n        reply.update_at = new Date();\n        reply.save(function (err) {\n          if (err) {\n            return next(err);\n          }\n          res.redirect('/topic/' + reply.topic_id + '#' + reply._id);\n        });\n      } else {\n        return res.renderError('回复的字数太少。', 400);\n      }\n    } else {\n      return res.renderError('对不起，你不能编辑此回复。', 403);\n    }\n  });\n};\n\nexports.up = function (req, res, next) {\n  var replyId = req.params.reply_id;\n  var userId = req.session.user._id;\n  Reply.getReplyById(replyId, function (err, reply) {\n    if (err) {\n      return next(err);\n    }\n    if (reply.author_id.equals(userId) && !config.debug) {\n      // 不能帮自己点赞\n      res.send({\n        success: false,\n        message: '呵呵，不能帮自己点赞。',\n      });\n    } else {\n      var action;\n      reply.ups = reply.ups || [];\n      var upIndex = reply.ups.indexOf(userId);\n      if (upIndex === -1) {\n        reply.ups.push(userId);\n        action = 'up';\n      } else {\n        reply.ups.splice(upIndex, 1);\n        action = 'down';\n      }\n      reply.save(function () {\n        res.send({\n          success: true,\n          action: action\n        });\n      });\n    }\n  });\n};\n"
  },
  {
    "path": "controllers/rss.js",
    "content": "var config       = require('../config');\nvar convert      = require('data2xml')();\nvar Topic        = require('../proxy').Topic;\nvar cache        = require('../common/cache');\nvar renderHelper = require('../common/render_helper');\nvar eventproxy   = require('eventproxy');\n\nexports.index = function (req, res, next) {\n  if (!config.rss) {\n    res.statusCode = 404;\n    return res.send('Please set `rss` in config.js');\n  }\n  res.contentType('application/xml');\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  cache.get('rss', ep.done(function (rss) {\n    if (!config.debug && rss) {\n      res.send(rss);\n    } else {\n      var opt = {\n        limit: config.rss.max_rss_items,\n        sort: '-create_at',\n      };\n      Topic.getTopicsByQuery({tab: {$nin: ['dev']}}, opt, function (err, topics) {\n        if (err) {\n          return next(err);\n        }\n        var rss_obj = {\n          _attr: { version: '2.0' },\n          channel: {\n            title: config.rss.title,\n            link: config.rss.link,\n            language: config.rss.language,\n            description: config.rss.description,\n            item: []\n          }\n        };\n\n        topics.forEach(function (topic) {\n          rss_obj.channel.item.push({\n            title: topic.title,\n            link: config.rss.link + '/topic/' + topic._id,\n            guid: config.rss.link + '/topic/' + topic._id,\n            description: renderHelper.markdown(topic.content),\n            author: topic.author.loginname,\n            pubDate: topic.create_at.toUTCString()\n          });\n        });\n\n        var rssContent = convert('rss', rss_obj);\n        rssContent = utf8ForXml(rssContent)\n        cache.set('rss', rssContent, 60 * 5); // 五分钟\n        res.send(rssContent);\n      });\n    }\n  }));\n};\n\nfunction utf8ForXml(inputStr) {\n  return inputStr.replace(/[^\\x09\\x0A\\x0D\\x20-\\xFF\\x85\\xA0-\\uD7FF\\uE000-\\uFDCF\\uFDE0-\\uFFFD]/gm, '');\n}\n"
  },
  {
    "path": "controllers/search.js",
    "content": "exports.index = function (req, res, next) {\n  var q = req.query.q;\n  q = encodeURIComponent(q);\n  res.redirect('https://www.google.com.hk/search?q=site:cnodejs.org+' + q);\n};\n"
  },
  {
    "path": "controllers/sign.js",
    "content": "var validator      = require('validator');\nvar eventproxy     = require('eventproxy');\nvar config         = require('../config');\nvar User           = require('../proxy').User;\nvar mail           = require('../common/mail');\nvar tools          = require('../common/tools');\nvar utility        = require('utility');\nvar authMiddleWare = require('../middlewares/auth');\nvar uuid           = require('node-uuid');\n\n//sign up\nexports.showSignup = function (req, res) {\n  res.render('sign/signup');\n};\n\nexports.signup = function (req, res, next) {\n  var loginname = validator.trim(req.body.loginname).toLowerCase();\n  var email     = validator.trim(req.body.email).toLowerCase();\n  var pass      = validator.trim(req.body.pass);\n  var rePass    = validator.trim(req.body.re_pass);\n\n  var ep = new eventproxy();\n  ep.fail(next);\n  ep.on('prop_err', function (msg) {\n    res.status(422);\n    res.render('sign/signup', {error: msg, loginname: loginname, email: email});\n  });\n\n  // 验证信息的正确性\n  if ([loginname, pass, rePass, email].some(function (item) { return item === ''; })) {\n    ep.emit('prop_err', '信息不完整。');\n    return;\n  }\n  if (loginname.length < 5) {\n    ep.emit('prop_err', '用户名至少需要5个字符。');\n    return;\n  }\n  if (!tools.validateId(loginname)) {\n    return ep.emit('prop_err', '用户名不合法。');\n  }\n  if (!validator.isEmail(email)) {\n    return ep.emit('prop_err', '邮箱不合法。');\n  }\n  if (pass !== rePass) {\n    return ep.emit('prop_err', '两次密码输入不一致。');\n  }\n  // END 验证信息的正确性\n\n\n  User.getUsersByQuery({'$or': [\n    {'loginname': loginname},\n    {'email': email}\n  ]}, {}, function (err, users) {\n    if (err) {\n      return next(err);\n    }\n    if (users.length > 0) {\n      ep.emit('prop_err', '用户名或邮箱已被使用。');\n      return;\n    }\n\n    tools.bhash(pass, ep.done(function (passhash) {\n      // create gravatar\n      var avatarUrl = User.makeGravatar(email);\n      User.newAndSave(loginname, loginname, passhash, email, avatarUrl, false, function (err) {\n        if (err) {\n          return next(err);\n        }\n        // 发送激活邮件\n        mail.sendActiveMail(email, utility.md5(email + passhash + config.session_secret), loginname);\n        res.render('sign/signup', {\n          success: '欢迎加入 ' + config.name + '！我们已给您的注册邮箱发送了一封邮件，请点击里面的链接来激活您的帐号。'\n        });\n      });\n\n    }));\n  });\n};\n\n/**\n * Show user login page.\n *\n * @param  {HttpRequest} req\n * @param  {HttpResponse} res\n */\nexports.showLogin = function (req, res) {\n  req.session._loginReferer = req.headers.referer;\n  res.render('sign/signin');\n};\n\n/**\n * define some page when login just jump to the home page\n * @type {Array}\n */\nvar notJump = [\n  '/active_account', //active page\n  '/reset_pass',     //reset password page, avoid to reset twice\n  '/signup',         //regist page\n  '/search_pass'    //serch pass page\n];\n\n/**\n * Handle user login.\n *\n * @param {HttpRequest} req\n * @param {HttpResponse} res\n * @param {Function} next\n */\nexports.login = function (req, res, next) {\n  var loginname = validator.trim(req.body.name).toLowerCase();\n  var pass      = validator.trim(req.body.pass);\n  var ep        = new eventproxy();\n\n  ep.fail(next);\n\n  if (!loginname || !pass) {\n    res.status(422);\n    return res.render('sign/signin', { error: '信息不完整。' });\n  }\n\n  var getUser;\n  if (loginname.indexOf('@') !== -1) {\n    getUser = User.getUserByMail;\n  } else {\n    getUser = User.getUserByLoginName;\n  }\n\n  ep.on('login_error', function (login_error) {\n    res.status(403);\n    res.render('sign/signin', { error: '用户名或密码错误' });\n  });\n\n  getUser(loginname, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    if (!user) {\n      return ep.emit('login_error');\n    }\n    var passhash = user.pass;\n    tools.bcompare(pass, passhash, ep.done(function (bool) {\n      if (!bool) {\n        return ep.emit('login_error');\n      }\n      if (!user.active) {\n        // 重新发送激活邮件\n        mail.sendActiveMail(user.email, utility.md5(user.email + passhash + config.session_secret), user.loginname);\n        res.status(403);\n        return res.render('sign/signin', { error: '此帐号还没有被激活，激活链接已发送到 ' + user.email + ' 邮箱，请查收。' });\n      }\n      // store session cookie\n      authMiddleWare.gen_session(user, res);\n      //check at some page just jump to home page\n      var refer = req.session._loginReferer || '/';\n      for (var i = 0, len = notJump.length; i !== len; ++i) {\n        if (refer.indexOf(notJump[i]) >= 0) {\n          refer = '/';\n          break;\n        }\n      }\n      res.redirect(refer);\n    }));\n  });\n};\n\n// sign out\nexports.signout = function (req, res, next) {\n  req.session.destroy();\n  res.clearCookie(config.auth_cookie_name, { path: '/' });\n  res.redirect('/');\n};\n\nexports.activeAccount = function (req, res, next) {\n  var key  = validator.trim(req.query.key);\n  var name = validator.trim(req.query.name);\n\n  User.getUserByLoginName(name, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    if (!user) {\n      return next(new Error('[ACTIVE_ACCOUNT] no such user: ' + name));\n    }\n    var passhash = user.pass;\n    if (!user || utility.md5(user.email + passhash + config.session_secret) !== key) {\n      return res.render('notify/notify', {error: '信息有误，帐号无法被激活。'});\n    }\n    if (user.active) {\n      return res.render('notify/notify', {error: '帐号已经是激活状态。'});\n    }\n    user.active = true;\n    user.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      res.render('notify/notify', {success: '帐号已被激活，请登录'});\n    });\n  });\n};\n\nexports.showSearchPass = function (req, res) {\n  res.render('sign/search_pass');\n};\n\nexports.updateSearchPass = function (req, res, next) {\n  var email = validator.trim(req.body.email).toLowerCase();\n  if (!validator.isEmail(email)) {\n    return res.render('sign/search_pass', {error: '邮箱不合法', email: email});\n  }\n\n  // 动态生成retrive_key和timestamp到users collection,之后重置密码进行验证\n  var retrieveKey  = uuid.v4();\n  var retrieveTime = new Date().getTime();\n\n  User.getUserByMail(email, function (err, user) {\n    if (!user) {\n      res.render('sign/search_pass', {error: '没有这个电子邮箱。', email: email});\n      return;\n    }\n    user.retrieve_key = retrieveKey;\n    user.retrieve_time = retrieveTime;\n    user.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      // 发送重置密码邮件\n      mail.sendResetPassMail(email, retrieveKey, user.loginname);\n      res.render('notify/notify', {success: '我们已给您填写的电子邮箱发送了一封邮件，请在24小时内点击里面的链接来重置密码。'});\n    });\n  });\n};\n\n/**\n * reset password\n * 'get' to show the page, 'post' to reset password\n * after reset password, retrieve_key&time will be destroy\n * @param  {http.req}   req\n * @param  {http.res}   res\n * @param  {Function} next\n */\nexports.resetPass = function (req, res, next) {\n  var key  = validator.trim(req.query.key || '');\n  var name = validator.trim(req.query.name || '');\n\n  User.getUserByNameAndKey(name, key, function (err, user) {\n    if (!user) {\n      res.status(403);\n      return res.render('notify/notify', {error: '信息有误，密码无法重置。'});\n    }\n    var now = new Date().getTime();\n    var oneDay = 1000 * 60 * 60 * 24;\n    if (!user.retrieve_time || now - user.retrieve_time > oneDay) {\n      res.status(403);\n      return res.render('notify/notify', {error: '该链接已过期，请重新申请。'});\n    }\n    return res.render('sign/reset', {name: name, key: key});\n  });\n};\n\nexports.updatePass = function (req, res, next) {\n  var psw   = validator.trim(req.body.psw) || '';\n  var repsw = validator.trim(req.body.repsw) || '';\n  var key   = validator.trim(req.body.key) || '';\n  var name  = validator.trim(req.body.name) || '';\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  if (psw !== repsw) {\n    return res.render('sign/reset', {name: name, key: key, error: '两次密码输入不一致。'});\n  }\n  User.getUserByNameAndKey(name, key, ep.done(function (user) {\n    if (!user) {\n      return res.render('notify/notify', {error: '错误的激活链接'});\n    }\n    tools.bhash(psw, ep.done(function (passhash) {\n      user.pass          = passhash;\n      user.retrieve_key  = null;\n      user.retrieve_time = null;\n      user.active        = true; // 用户激活\n\n      user.save(function (err) {\n        if (err) {\n          return next(err);\n        }\n        return res.render('notify/notify', {success: '你的密码已重置。'});\n      });\n    }));\n  }));\n};\n\n"
  },
  {
    "path": "controllers/site.js",
    "content": "/*!\n * nodeclub - site index controller.\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * Copyright(c) 2012 muyuan\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar User         = require('../proxy').User;\nvar Topic        = require('../proxy').Topic;\nvar config       = require('../config');\nvar eventproxy   = require('eventproxy');\nvar cache        = require('../common/cache');\nvar xmlbuilder   = require('xmlbuilder');\nvar renderHelper = require('../common/render_helper');\nvar _            = require('lodash');\nvar moment = require('moment');\n\nexports.index = function (req, res, next) {\n  var page = parseInt(req.query.page, 10) || 1;\n  page = page > 0 ? page : 1;\n  var tab = req.query.tab || 'all';\n\n  var proxy = new eventproxy();\n  proxy.fail(next);\n\n  // 取主题\n  var query = {};\n  if (!tab || tab === 'all') {\n    query.tab = {$nin: ['job', 'dev']}\n  } else {\n    if (tab === 'good') {\n      query.good = true;\n    } else {\n      query.tab = tab;\n    }\n  }\n  if (!query.good) {\n    query.create_at = {$gte: moment().subtract(1, 'years').toDate()}\n  }\n\n  var limit = config.list_topic_count;\n  var options = { skip: (page - 1) * limit, limit: limit, sort: '-top -last_reply_at'};\n\n  Topic.getTopicsByQuery(query, options, proxy.done('topics', function (topics) {\n    return topics;\n  }));\n\n  // 取排行榜上的用户\n  cache.get('tops', proxy.done(function (tops) {\n    if (tops) {\n      proxy.emit('tops', tops);\n    } else {\n      User.getUsersByQuery(\n        {is_block: false},\n        { limit: 10, sort: '-score'},\n        proxy.done('tops', function (tops) {\n          cache.set('tops', tops, 60 * 1);\n          return tops;\n        })\n      );\n    }\n  }));\n  // END 取排行榜上的用户\n\n  // 取0回复的主题\n  cache.get('no_reply_topics', proxy.done(function (no_reply_topics) {\n    if (no_reply_topics) {\n      proxy.emit('no_reply_topics', no_reply_topics);\n    } else {\n      Topic.getTopicsByQuery(\n        { reply_count: 0, tab: {$nin: ['job', 'dev']}},\n        { limit: 5, sort: '-create_at'},\n        proxy.done('no_reply_topics', function (no_reply_topics) {\n          cache.set('no_reply_topics', no_reply_topics, 60 * 1);\n          return no_reply_topics;\n        }));\n    }\n  }));\n  // END 取0回复的主题\n\n  // 取分页数据\n  var pagesCacheKey = JSON.stringify(query) + 'pages';\n  cache.get(pagesCacheKey, proxy.done(function (pages) {\n    if (pages) {\n      proxy.emit('pages', pages);\n    } else {\n      Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {\n        var pages = Math.ceil(all_topics_count / limit);\n        cache.set(pagesCacheKey, pages, 60 * 1);\n        proxy.emit('pages', pages);\n      }));\n    }\n  }));\n  // END 取分页数据\n\n  var tabName = renderHelper.tabName(tab);\n  proxy.all('topics', 'tops', 'no_reply_topics', 'pages',\n    function (topics, tops, no_reply_topics, pages) {\n      res.render('index', {\n        topics: topics,\n        current_page: page,\n        list_topic_count: limit,\n        tops: tops,\n        no_reply_topics: no_reply_topics,\n        pages: pages,\n        tabs: config.tabs,\n        tab: tab,\n        pageTitle: tabName && (tabName + '版块'),\n      });\n    });\n};\n\nexports.sitemap = function (req, res, next) {\n  var urlset = xmlbuilder.create('urlset',\n    {version: '1.0', encoding: 'UTF-8'});\n  urlset.att('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');\n\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  ep.all('sitemap', function (sitemap) {\n    res.type('xml');\n    res.send(sitemap);\n  });\n\n  cache.get('sitemap', ep.done(function (sitemapData) {\n    if (sitemapData) {\n      ep.emit('sitemap', sitemapData);\n    } else {\n      Topic.getLimit5w(function (err, topics) {\n        if (err) {\n          return next(err);\n        }\n        topics.forEach(function (topic) {\n          urlset.ele('url').ele('loc', 'http://cnodejs.org/topic/' + topic._id);\n        });\n\n        var sitemapData = urlset.end();\n        // 缓存一天\n        cache.set('sitemap', sitemapData, 3600 * 24);\n        ep.emit('sitemap', sitemapData);\n      });\n    }\n  }));\n};\n\nexports.appDownload = function (req, res, next) {\n  res.redirect('https://github.com/soliury/noder-react-native/blob/master/README.md')\n};\n"
  },
  {
    "path": "controllers/static.js",
    "content": "var multiline = require('multiline');\n// static page\n// About\nexports.about = function (req, res, next) {\n  res.render('static/about', {\n    pageTitle: '关于我们'\n  });\n};\n\n// FAQ\nexports.faq = function (req, res, next) {\n  res.render('static/faq');\n};\n\nexports.getstart = function (req, res) {\n  res.render('static/getstart', {\n    pageTitle: 'Node.js 新手入门'\n  });\n};\n\n\nexports.robots = function (req, res, next) {\n  res.type('text/plain');\n  res.send(multiline(function () {;\n/*\n# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n*/\n  }));\n};\n\nexports.api = function (req, res, next) {\n  res.render('static/api');\n};\n"
  },
  {
    "path": "controllers/topic.js",
    "content": "/*!\n * nodeclub - controllers/topic.js\n */\n\n/**\n * Module dependencies.\n */\n\nvar validator = require('validator');\n\nvar at           = require('../common/at');\nvar User         = require('../proxy').User;\nvar Topic        = require('../proxy').Topic;\nvar TopicCollect = require('../proxy').TopicCollect;\nvar EventProxy   = require('eventproxy');\nvar tools        = require('../common/tools');\nvar store        = require('../common/store');\nvar config       = require('../config');\nvar _            = require('lodash');\nvar cache        = require('../common/cache');\nvar logger = require('../common/logger')\n\n/**\n * Topic page\n *\n * @param  {HttpRequest} req\n * @param  {HttpResponse} res\n * @param  {Function} next\n */\nexports.index = function (req, res, next) {\n  function isUped(user, reply) {\n    if (!reply.ups) {\n      return false;\n    }\n    return reply.ups.indexOf(user._id) !== -1;\n  }\n\n  var topic_id = req.params.tid;\n  var currentUser = req.session.user;\n\n  if (topic_id.length !== 24) {\n    return res.render404('此话题不存在或已被删除。');\n  }\n  var events = ['topic', 'other_topics', 'no_reply_topics', 'is_collect'];\n  var ep = EventProxy.create(events,\n    function (topic, other_topics, no_reply_topics, is_collect) {\n    res.render('topic/index', {\n      topic: topic,\n      author_other_topics: other_topics,\n      no_reply_topics: no_reply_topics,\n      is_uped: isUped,\n      is_collect: is_collect,\n    });\n  });\n\n  ep.fail(next);\n\n  Topic.getFullTopic(topic_id, ep.done(function (message, topic, author, replies) {\n    if (message) {\n      logger.error('getFullTopic error topic_id: ' + topic_id)\n      return res.renderError(message);\n    }\n\n    topic.visit_count += 1;\n    topic.save();\n\n    topic.author  = author;\n    topic.replies = replies;\n\n    // 点赞数排名第三的回答，它的点赞数就是阈值\n    topic.reply_up_threshold = (function () {\n      var allUpCount = replies.map(function (reply) {\n        return reply.ups && reply.ups.length || 0;\n      });\n      allUpCount = _.sortBy(allUpCount, Number).reverse();\n\n      var threshold = allUpCount[2] || 0;\n      if (threshold < 3) {\n        threshold = 3;\n      }\n      return threshold;\n    })();\n\n    ep.emit('topic', topic);\n\n    // get other_topics\n    var options = { limit: 5, sort: '-last_reply_at'};\n    var query = { author_id: topic.author_id, _id: { '$nin': [ topic._id ] } };\n    Topic.getTopicsByQuery(query, options, ep.done('other_topics'));\n\n    // get no_reply_topics\n    cache.get('no_reply_topics', ep.done(function (no_reply_topics) {\n      if (no_reply_topics) {\n        ep.emit('no_reply_topics', no_reply_topics);\n      } else {\n        Topic.getTopicsByQuery(\n          { reply_count: 0, tab: {$nin: ['job', 'dev']}},\n          { limit: 5, sort: '-create_at'},\n          ep.done('no_reply_topics', function (no_reply_topics) {\n            cache.set('no_reply_topics', no_reply_topics, 60 * 1);\n            return no_reply_topics;\n          }));\n      }\n    }));\n  }));\n\n  if (!currentUser) {\n    ep.emit('is_collect', null);\n  } else {\n    TopicCollect.getTopicCollect(currentUser._id, topic_id, ep.done('is_collect'))\n  }\n};\n\nexports.create = function (req, res, next) {\n  res.render('topic/edit', {\n    tabs: config.tabs\n  });\n};\n\n\nexports.put = function (req, res, next) {\n  var title   = validator.trim(req.body.title);\n  var tab     = validator.trim(req.body.tab);\n  var content = validator.trim(req.body.t_content);\n\n  // 得到所有的 tab, e.g. ['ask', 'share', ..]\n  var allTabs = config.tabs.map(function (tPair) {\n    return tPair[0];\n  });\n\n  // 验证\n  var editError;\n  if (title === '') {\n    editError = '标题不能是空的。';\n  } else if (title.length < 5 || title.length > 100) {\n    editError = '标题字数太多或太少。';\n  } else if (!tab || allTabs.indexOf(tab) === -1) {\n    editError = '必须选择一个版块。';\n  } else if (content === '') {\n    editError = '内容不可为空';\n  }\n  // END 验证\n\n  if (editError) {\n    res.status(422);\n    return res.render('topic/edit', {\n      edit_error: editError,\n      title: title,\n      content: content,\n      tabs: config.tabs\n    });\n  }\n\n  Topic.newAndSave(title, content, tab, req.session.user._id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n\n    var proxy = new EventProxy();\n\n    proxy.all('score_saved', function () {\n      res.redirect('/topic/' + topic._id);\n    });\n    proxy.fail(next);\n    User.getUserById(req.session.user._id, proxy.done(function (user) {\n      user.score += 5;\n      user.topic_count += 1;\n      user.save();\n      req.session.user = user;\n      proxy.emit('score_saved');\n    }));\n\n    //发送at消息\n    at.sendMessageToMentionUsers(content, topic._id, req.session.user._id);\n  });\n};\n\nexports.showEdit = function (req, res, next) {\n  var topic_id = req.params.tid;\n\n  Topic.getTopicById(topic_id, function (err, topic, tags) {\n    if (!topic) {\n      res.render404('此话题不存在或已被删除。');\n      return;\n    }\n\n    if (String(topic.author_id) === String(req.session.user._id) || req.session.user.is_admin) {\n      res.render('topic/edit', {\n        action: 'edit',\n        topic_id: topic._id,\n        title: topic.title,\n        content: topic.content,\n        tab: topic.tab,\n        tabs: config.tabs\n      });\n    } else {\n      res.renderError('对不起，你不能编辑此话题。', 403);\n    }\n  });\n};\n\nexports.update = function (req, res, next) {\n  var topic_id = req.params.tid;\n  var title    = req.body.title;\n  var tab      = req.body.tab;\n  var content  = req.body.t_content;\n\n  Topic.getTopicById(topic_id, function (err, topic, tags) {\n    if (!topic) {\n      res.render404('此话题不存在或已被删除。');\n      return;\n    }\n\n    if (topic.author_id.equals(req.session.user._id) || req.session.user.is_admin) {\n      title   = validator.trim(title);\n      tab     = validator.trim(tab);\n      content = validator.trim(content);\n\n      // 验证\n      var editError;\n      if (title === '') {\n        editError = '标题不能是空的。';\n      } else if (title.length < 5 || title.length > 100) {\n        editError = '标题字数太多或太少。';\n      } else if (!tab) {\n        editError = '必须选择一个版块。';\n      }\n      // END 验证\n\n      if (editError) {\n        return res.render('topic/edit', {\n          action: 'edit',\n          edit_error: editError,\n          topic_id: topic._id,\n          content: content,\n          tabs: config.tabs\n        });\n      }\n\n      //保存话题\n      topic.title     = title;\n      topic.content   = content;\n      topic.tab       = tab;\n      topic.update_at = new Date();\n\n      topic.save(function (err) {\n        if (err) {\n          return next(err);\n        }\n        //发送at消息\n        at.sendMessageToMentionUsers(content, topic._id, req.session.user._id);\n\n        res.redirect('/topic/' + topic._id);\n\n      });\n    } else {\n      res.renderError('对不起，你不能编辑此话题。', 403);\n    }\n  });\n};\n\nexports.delete = function (req, res, next) {\n  //删除话题, 话题作者topic_count减1\n  //删除回复，回复作者reply_count减1\n  //删除topic_collect，用户collect_topic_count减1\n\n  var topic_id = req.params.tid;\n\n  Topic.getFullTopic(topic_id, function (err, err_msg, topic, author, replies) {\n    if (err) {\n      return res.send({ success: false, message: err.message });\n    }\n    if (!req.session.user.is_admin && !(topic.author_id.equals(req.session.user._id))) {\n      res.status(403);\n      return res.send({success: false, message: '无权限'});\n    }\n    if (!topic) {\n      res.status(422);\n      return res.send({ success: false, message: '此话题不存在或已被删除。' });\n    }\n    author.score -= 5;\n    author.topic_count -= 1;\n    author.save();\n\n    topic.deleted = true;\n    topic.save(function (err) {\n      if (err) {\n        return res.send({ success: false, message: err.message });\n      }\n      res.send({ success: true, message: '话题已被删除。' });\n    });\n  });\n};\n\n// 设为置顶\nexports.top = function (req, res, next) {\n  var topic_id = req.params.tid;\n  var referer  = req.get('referer');\n\n  if (topic_id.length !== 24) {\n    res.render404('此话题不存在或已被删除。');\n    return;\n  }\n  Topic.getTopic(topic_id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.render404('此话题不存在或已被删除。');\n      return;\n    }\n    topic.top = !topic.top;\n    topic.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      var msg = topic.top ? '此话题已置顶。' : '此话题已取消置顶。';\n      res.render('notify/notify', {success: msg, referer: referer});\n    });\n  });\n};\n\n// 设为精华\nexports.good = function (req, res, next) {\n  var topicId = req.params.tid;\n  var referer = req.get('referer');\n\n  Topic.getTopic(topicId, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.render404('此话题不存在或已被删除。');\n      return;\n    }\n    topic.good = !topic.good;\n    topic.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      var msg = topic.good ? '此话题已加精。' : '此话题已取消加精。';\n      res.render('notify/notify', {success: msg, referer: referer});\n    });\n  });\n};\n\n// 锁定主题，不可再回复\nexports.lock = function (req, res, next) {\n  var topicId = req.params.tid;\n  var referer = req.get('referer');\n  Topic.getTopic(topicId, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.render404('此话题不存在或已被删除。');\n      return;\n    }\n    topic.lock = !topic.lock;\n    topic.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      var msg = topic.lock ? '此话题已锁定。' : '此话题已取消锁定。';\n      res.render('notify/notify', {success: msg, referer: referer});\n    });\n  });\n};\n\n// 收藏主题\nexports.collect = function (req, res, next) {\n  var topic_id = req.body.topic_id;\n\n  Topic.getTopic(topic_id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.json({status: 'failed'});\n    }\n\n    TopicCollect.getTopicCollect(req.session.user._id, topic._id, function (err, doc) {\n      if (err) {\n        return next(err);\n      }\n      if (doc) {\n        res.json({status: 'failed'});\n        return;\n      }\n\n      TopicCollect.newAndSave(req.session.user._id, topic._id, function (err) {\n        if (err) {\n          return next(err);\n        }\n        res.json({status: 'success'});\n      });\n      User.getUserById(req.session.user._id, function (err, user) {\n        if (err) {\n          return next(err);\n        }\n        user.collect_topic_count += 1;\n        user.save();\n      });\n\n      req.session.user.collect_topic_count += 1;\n      topic.collect_count += 1;\n      topic.save();\n    });\n  });\n};\n\nexports.de_collect = function (req, res, next) {\n  var topic_id = req.body.topic_id;\n  Topic.getTopic(topic_id, function (err, topic) {\n    if (err) {\n      return next(err);\n    }\n    if (!topic) {\n      res.json({status: 'failed'});\n    }\n    TopicCollect.remove(req.session.user._id, topic._id, function (err, removeResult) {\n      if (err) {\n        return next(err);\n      }\n      if (removeResult.n == 0) {\n        return res.json({status: 'failed'})\n      }\n\n      User.getUserById(req.session.user._id, function (err, user) {\n        if (err) {\n          return next(err);\n        }\n        user.collect_topic_count -= 1;\n        req.session.user = user;\n        user.save();\n      });\n\n      topic.collect_count -= 1;\n      topic.save();\n\n      res.json({status: 'success'});\n    });\n  });\n};\n\nexports.upload = function (req, res, next) {\n  var isFileLimit = false;\n  req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {\n      file.on('limit', function () {\n        isFileLimit = true;\n\n        res.json({\n          success: false,\n          msg: 'File size too large. Max is ' + config.file_limit\n        })\n      });\n\n      store.upload(file, {filename: filename}, function (err, result) {\n        if (err) {\n          return next(err);\n        }\n        if (isFileLimit) {\n          return;\n        }\n        res.json({\n          success: true,\n          url: result.url,\n        });\n      });\n\n    });\n\n  req.pipe(req.busboy);\n};\n"
  },
  {
    "path": "controllers/user.js",
    "content": "var User         = require('../proxy').User;\nvar Topic        = require('../proxy').Topic;\nvar Reply        = require('../proxy').Reply;\nvar TopicCollect = require('../proxy').TopicCollect;\nvar utility      = require('utility');\nvar util         = require('util');\nvar TopicModel   = require('../models').Topic;\nvar ReplyModel   = require('../models').Reply;\nvar tools        = require('../common/tools');\nvar config       = require('../config');\nvar EventProxy   = require('eventproxy');\nvar validator    = require('validator');\nvar _            = require('lodash');\nvar uuid = require('node-uuid')\n\nexports.index = function (req, res, next) {\n  var user_name = req.params.name;\n  User.getUserByLoginName(user_name, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    if (!user) {\n      res.render404('这个用户不存在。');\n      return;\n    }\n\n    var render = function (recent_topics, recent_replies) {\n      user.url = (function () {\n        if (user.url && user.url.indexOf('http') !== 0) {\n          return 'http://' + user.url;\n        }\n        return user.url;\n      })();\n      // 如果用户没有激活，那么管理员可以帮忙激活\n      var token = '';\n      if (!user.active && req.session.user && req.session.user.is_admin) {\n        token = utility.md5(user.email + user.pass + config.session_secret);\n      }\n      res.render('user/index', {\n        user: user,\n        recent_topics: recent_topics,\n        recent_replies: recent_replies,\n        token: token,\n        pageTitle: util.format('@%s 的个人主页', user.loginname),\n      });\n    };\n\n    var proxy = new EventProxy();\n    proxy.assign('recent_topics', 'recent_replies', render);\n    proxy.fail(next);\n\n    var query = {author_id: user._id};\n    var opt = {limit: 5, sort: '-create_at'};\n    Topic.getTopicsByQuery(query, opt, proxy.done('recent_topics'));\n\n    Reply.getRepliesByAuthorId(user._id, {limit: 20, sort: '-create_at'},\n      proxy.done(function (replies) {\n\n        var topic_ids = replies.map(function (reply) {\n          return reply.topic_id.toString()\n        })\n        topic_ids = _.uniq(topic_ids).slice(0, 5); //  只显示最近5条\n\n        var query = {_id: {'$in': topic_ids}};\n        var opt = {};\n        Topic.getTopicsByQuery(query, opt, proxy.done('recent_replies', function (recent_replies) {\n          recent_replies = _.sortBy(recent_replies, function (topic) {\n            return topic_ids.indexOf(topic._id.toString())\n          })\n          return recent_replies;\n        }));\n      }));\n  });\n};\n\nexports.listStars = function (req, res, next) {\n  User.getUsersByQuery({is_star: true}, {}, function (err, stars) {\n    if (err) {\n      return next(err);\n    }\n    res.render('user/stars', {stars: stars});\n  });\n};\n\nexports.showSetting = function (req, res, next) {\n  User.getUserById(req.session.user._id, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    if (req.query.save === 'success') {\n      user.success = '保存成功。';\n    }\n    user.error = null;\n    return res.render('user/setting', user);\n  });\n};\n\nexports.setting = function (req, res, next) {\n  var ep = new EventProxy();\n  ep.fail(next);\n\n  // 显示出错或成功信息\n  function showMessage(msg, data, isSuccess) {\n    data = data || req.body;\n    var data2 = {\n      loginname: data.loginname,\n      email: data.email,\n      url: data.url,\n      location: data.location,\n      signature: data.signature,\n      weibo: data.weibo,\n      accessToken: data.accessToken,\n    };\n    if (isSuccess) {\n      data2.success = msg;\n    } else {\n      data2.error = msg;\n    }\n    res.render('user/setting', data2);\n  }\n\n  // post\n  var action = req.body.action;\n  if (action === 'change_setting') {\n    var url = validator.trim(req.body.url);\n    var location = validator.trim(req.body.location);\n    var weibo = validator.trim(req.body.weibo);\n    var signature = validator.trim(req.body.signature);\n\n    User.getUserById(req.session.user._id, ep.done(function (user) {\n      user.url = url;\n      user.location = location;\n      user.signature = signature;\n      user.weibo = weibo;\n      user.save(function (err) {\n        if (err) {\n          return next(err);\n        }\n        req.session.user = user.toObject({virtual: true});\n        return res.redirect('/setting?save=success');\n      });\n    }));\n  }\n  if (action === 'change_password') {\n    var old_pass = validator.trim(req.body.old_pass);\n    var new_pass = validator.trim(req.body.new_pass);\n    if (!old_pass || !new_pass) {\n      return res.send('旧密码或新密码不得为空');\n    }\n\n    User.getUserById(req.session.user._id, ep.done(function (user) {\n      tools.bcompare(old_pass, user.pass, ep.done(function (bool) {\n        if (!bool) {\n          return showMessage('当前密码不正确。', user);\n        }\n\n        tools.bhash(new_pass, ep.done(function (passhash) {\n          user.pass = passhash;\n          user.save(function (err) {\n            if (err) {\n              return next(err);\n            }\n            return showMessage('密码已被修改。', user, true);\n\n          });\n        }));\n      }));\n    }));\n  }\n};\n\nexports.toggleStar = function (req, res, next) {\n  var user_id = req.body.user_id;\n  User.getUserById(user_id, function (err, user) {\n    if (err) {\n      return next(err);\n    }\n    if (!user) {\n      return next(new Error('user is not exists'));\n    }\n    user.is_star = !user.is_star;\n    user.save(function (err) {\n      if (err) {\n        return next(err);\n      }\n      res.json({ status: 'success' });\n    });\n  });\n};\n\nexports.listCollectedTopics = function (req, res, next) {\n  var name = req.params.name;\n  var page = Number(req.query.page) || 1;\n  var limit = config.list_topic_count;\n\n  User.getUserByLoginName(name, function (err, user) {\n    if (err || !user) {\n      return next(err);\n    }\n    var pages = Math.ceil(user.collect_topic_count/limit);\n    var render = function (topics) {\n      res.render('user/collect_topics', {\n        topics: topics,\n        current_page: page,\n        pages: pages,\n        user: user\n      });\n    };\n\n    var proxy = EventProxy.create('topics', render);\n    proxy.fail(next);\n\n    var opt = {\n      skip: (page - 1) * limit,\n      limit: limit,\n    };\n\n    TopicCollect.getTopicCollectsByUserId(user._id, opt, proxy.done(function (docs) {\n      var ids = docs.map(function (doc) {\n        return String(doc.topic_id)\n      })\n      var query = { _id: { '$in': ids } };\n\n      Topic.getTopicsByQuery(query, {}, proxy.done('topics', function (topics) {\n        topics = _.sortBy(topics, function (topic) {\n          return ids.indexOf(String(topic._id))\n        })\n        return topics\n      }));\n    }));\n  });\n};\n\nexports.top100 = function (req, res, next) {\n  var opt = {limit: 100, sort: '-score'};\n  User.getUsersByQuery({is_block: false}, opt, function (err, tops) {\n    if (err) {\n      return next(err);\n    }\n    res.render('user/top100', {\n      users: tops,\n      pageTitle: 'top100',\n    });\n  });\n};\n\nexports.listTopics = function (req, res, next) {\n  var user_name = req.params.name;\n  var page = Number(req.query.page) || 1;\n  var limit = config.list_topic_count;\n\n  User.getUserByLoginName(user_name, function (err, user) {\n    if (!user) {\n      res.render404('这个用户不存在。');\n      return;\n    }\n\n    var render = function (topics, pages) {\n      res.render('user/topics', {\n        user: user,\n        topics: topics,\n        current_page: page,\n        pages: pages\n      });\n    };\n\n    var proxy = new EventProxy();\n    proxy.assign('topics', 'pages', render);\n    proxy.fail(next);\n\n    var query = {'author_id': user._id};\n    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};\n    Topic.getTopicsByQuery(query, opt, proxy.done('topics'));\n\n    Topic.getCountByQuery(query, proxy.done(function (all_topics_count) {\n      var pages = Math.ceil(all_topics_count / limit);\n      proxy.emit('pages', pages);\n    }));\n  });\n};\n\nexports.listReplies = function (req, res, next) {\n  var user_name = req.params.name;\n  var page = Number(req.query.page) || 1;\n  var limit = 50;\n\n  User.getUserByLoginName(user_name, function (err, user) {\n    if (!user) {\n      res.render404('这个用户不存在。');\n      return;\n    }\n\n    var render = function (topics, pages) {\n      res.render('user/replies', {\n        user: user,\n        topics: topics,\n        current_page: page,\n        pages: pages\n      });\n    };\n\n    var proxy = new EventProxy();\n    proxy.assign('topics', 'pages', render);\n    proxy.fail(next);\n\n    var opt = {skip: (page - 1) * limit, limit: limit, sort: '-create_at'};\n    Reply.getRepliesByAuthorId(user._id, opt, proxy.done(function (replies) {\n      // 获取所有有评论的主题\n      var topic_ids = replies.map(function (reply) {\n        return reply.topic_id.toString();\n      });\n      topic_ids = _.uniq(topic_ids);\n\n      var query = {'_id': {'$in': topic_ids}};\n      Topic.getTopicsByQuery(query, {}, proxy.done('topics', function (topics) {\n        topics = _.sortBy(topics, function (topic) {\n          return topic_ids.indexOf(topic._id.toString())\n        })\n        return topics;\n      }));\n    }));\n\n    Reply.getCountByAuthorId(user._id, proxy.done('pages', function (count) {\n      var pages = Math.ceil(count / limit);\n      return pages;\n    }));\n  });\n};\n\nexports.block = function (req, res, next) {\n  var loginname = req.params.name;\n  var action = req.body.action;\n\n  var ep = EventProxy.create();\n  ep.fail(next);\n\n  User.getUserByLoginName(loginname, ep.done(function (user) {\n    if (!user) {\n      return next(new Error('user is not exists'));\n    }\n    if (action === 'set_block') {\n      ep.all('block_user',\n        function (user) {\n          res.json({status: 'success'});\n        });\n      user.is_block = true;\n      user.save(ep.done('block_user'));\n\n    } else if (action === 'cancel_block') {\n      user.is_block = false;\n      user.save(ep.done(function () {\n\n        res.json({status: 'success'});\n      }));\n    }\n  }));\n};\n\nexports.deleteAll = function (req, res, next) {\n  var loginname = req.params.name;\n\n  var ep = EventProxy.create();\n  ep.fail(next);\n\n  User.getUserByLoginName(loginname, ep.done(function (user) {\n    if (!user) {\n      return next(new Error('user is not exists'));\n    }\n    ep.all('del_topics', 'del_replys', 'del_ups',\n      function () {\n        res.json({status: 'success'});\n      });\n    // 删除主题\n    TopicModel.updateMany({author_id: user._id}, {$set: {deleted: true}}, ep.done('del_topics'));\n    // 删除评论\n    ReplyModel.updateMany({author_id: user._id}, {$set: {deleted: true}}, ep.done('del_replys'));\n    // 点赞数也全部干掉\n    ReplyModel.updateMany({}, {$pull: {'ups': user._id}}, ep.done('del_ups'));\n  }));\n};\n\nexports.refreshToken = function (req, res, next) {\n  var user_id = req.session.user._id;\n\n  var ep = EventProxy.create();\n  ep.fail(next);\n\n   User.getUserById(user_id, ep.done(function (user) {\n    user.accessToken = uuid.v4();\n    user.save(ep.done(function () {\n      res.json({status: 'success', accessToken: user.accessToken});\n    }));\n  }));\n};"
  },
  {
    "path": "logs/.gitkeep",
    "content": ""
  },
  {
    "path": "middlewares/auth.js",
    "content": "var mongoose   = require('mongoose');\nvar UserModel  = mongoose.model('User');\nvar Message    = require('../proxy').Message;\nvar config     = require('../config');\nvar eventproxy = require('eventproxy');\nvar UserProxy  = require('../proxy').User;\n\n/**\n * 需要管理员权限\n */\nexports.adminRequired = function (req, res, next) {\n  if (!req.session.user) {\n    return res.render('notify/notify', { error: '你还没有登录。' });\n  }\n\n  if (!req.session.user.is_admin) {\n    return res.render('notify/notify', { error: '需要管理员权限。' });\n  }\n\n  next();\n};\n\n/**\n * 需要登录\n */\nexports.userRequired = function (req, res, next) {\n  if (!req.session || !req.session.user || !req.session.user._id) {\n    return res.status(403).send('forbidden!');\n  }\n\n  next();\n};\n\nexports.blockUser = function () {\n  return function (req, res, next) {\n    if (req.path === '/signout') {\n      return next();\n    }\n\n    if (req.session.user && req.session.user.is_block && req.method !== 'GET') {\n      return res.status(403).send('您已被管理员屏蔽了。有疑问请联系 @alsotang。');\n    }\n    next();\n  };\n};\n\n\nfunction gen_session(user, res) {\n  var auth_token = user._id + '$$$$'; // 以后可能会存储更多信息，用 $$$$ 来分隔\n  var opts = {\n    path: '/',\n    maxAge: 1000 * 60 * 60 * 24 * 30,\n    signed: true,\n    httpOnly: true\n  };\n  res.cookie(config.auth_cookie_name, auth_token, opts); //cookie 有效期30天\n}\n\nexports.gen_session = gen_session;\n\n// 验证用户是否登录\nexports.authUser = function (req, res, next) {\n  var ep = new eventproxy();\n  ep.fail(next);\n\n  // Ensure current_user always has defined.\n  res.locals.current_user = null;\n\n  if (config.debug && req.cookies['mock_user']) {\n    var mockUser = JSON.parse(req.cookies['mock_user']);\n    req.session.user = new UserModel(mockUser);\n    if (mockUser.is_admin) {\n      req.session.user.is_admin = true;\n    }\n    return next();\n  }\n\n  ep.all('get_user', function (user) {\n    if (!user) {\n      return next();\n    }\n    user = res.locals.current_user = req.session.user = new UserModel(user);\n\n    if (config.admins.hasOwnProperty(user.loginname)) {\n      user.is_admin = true;\n    }\n\n    Message.getMessagesCount(user._id, ep.done(function (count) {\n      user.messages_count = count;\n      next();\n    }));\n  });\n\n  if (req.session.user) {\n    ep.emit('get_user', req.session.user);\n  } else {\n    var auth_token = req.signedCookies[config.auth_cookie_name];\n    if (!auth_token) {\n      return next();\n    }\n\n    var auth = auth_token.split('$$$$');\n    var user_id = auth[0];\n    UserProxy.getUserById(user_id, ep.done('get_user'));\n  }\n};\n"
  },
  {
    "path": "middlewares/conf.js",
    "content": "var config = require('../config');\n\nexports.github = function (req, res, next) {\n  if (config.GITHUB_OAUTH.clientID === 'your GITHUB_CLIENT_ID') {\n    return res.send('call the admin to set github oauth.');\n  }\n  next();\n};\n"
  },
  {
    "path": "middlewares/error_page.js",
    "content": "// ErrorPage middleware\nexports.errorPage = function (req, res, next) {\n\n  res.render404 = function (error) {\n    return res.status(404).render('notify/notify', { error: error });\n  };\n\n  res.renderError = function (error, statusCode) {\n    if (statusCode === undefined) {\n      statusCode = 400;\n    }\n    return res.status(statusCode).render('notify/notify', { error: error });\n  };\n\n  next();\n};\n"
  },
  {
    "path": "middlewares/github_strategy.js",
    "content": "module.exports = function (accessToken, refreshToken, profile, done) {\n  profile.accessToken = accessToken;\n  done(null, profile);\n};\n"
  },
  {
    "path": "middlewares/limit.js",
    "content": "var config = require('../config');\nvar cache  = require('../common/cache');\nvar moment = require('moment');\n\nvar SEPARATOR = '^_^@T_T';\n\nvar makePerDayLimiter = function (identityName, identityFn) {\n  return function (name, limitCount, options) {\n    /*\n    options.showJson = true 表示调用来自API并返回结构化数据；否则表示调用来自前段并渲染错误页面\n    */\n    return function (req, res, next) {\n      var identity = identityFn(req);\n      var YYYYMMDD = moment().format('YYYYMMDD');\n      var key      = YYYYMMDD + SEPARATOR + identityName + SEPARATOR + name + SEPARATOR + identity;\n\n      cache.get(key, function (err, count) {\n        if (err) {\n          return next(err);\n        }\n        count = count || 0;\n        if (count < limitCount) {\n          count += 1;\n          cache.set(key, count, 60 * 60 * 24);\n          res.set('X-RateLimit-Limit', limitCount);\n          res.set('X-RateLimit-Remaining', limitCount - count);\n          next();\n        } else {\n          res.status(403);\n          if (options.showJson) {\n            res.send({success: false, error_msg: '频率限制：当前操作每天可以进行 ' + limitCount + ' 次'});\n          } else {\n            res.render('notify/notify', { error: '频率限制：当前操作每天可以进行 ' + limitCount + ' 次'});\n          }\n        }\n      });\n    };\n  };\n};\n\nexports.peruserperday = makePerDayLimiter('peruserperday', function (req) {\n  return (req.user || req.session.user).loginname;\n});\n\nexports.peripperday = makePerDayLimiter('peripperday', function (req) {\n  var realIP = req.get('x-real-ip');\n  if (!realIP && !config.debug) {\n    throw new Error('should provide `x-real-ip` header')\n  }\n  return realIP;\n});\n"
  },
  {
    "path": "middlewares/mongoose_log.js",
    "content": "var mongoose = require('mongoose');\nvar logger   = require('../common/logger');\nvar config = require('../config');\n\nif (config.debug) {\n  var traceMQuery = function (method, info, query) {\n    return function (err, result, millis) {\n      if (err) {\n        logger.error('traceMQuery error:', err)\n      }\n      var infos = [];\n      infos.push(query._collection.collection.name + \".\" + method.blue);\n      infos.push(JSON.stringify(info));\n      infos.push((millis + 'ms').green);\n\n      logger.debug(\"MONGO\".magenta, infos.join(' '));\n    };\n  };\n\n  mongoose.Mongoose.prototype.mquery.setGlobalTraceFunction(traceMQuery);\n}\n"
  },
  {
    "path": "middlewares/proxy.js",
    "content": "var urllib  = require('url');\nvar request = require('request');\nvar logger = require('../common/logger')\nvar _ = require('lodash')\n\n\nvar ALLOW_HOSTNAME = [\n  'avatars.githubusercontent.com', 'www.gravatar.com',\n  'gravatar.com', 'www.google-analytics.com',\n];\nexports.proxy = function (req, res, next) {\n  var url = decodeURIComponent(req.query.url);\n  var hostname = urllib.parse(url).hostname;\n\n  if (ALLOW_HOSTNAME.indexOf(hostname) === -1) {\n    return res.send(hostname + ' is not allowed');\n  }\n\n  request.get({\n      url: url,\n      headers: _.omit(req.headers, ['cookie', 'refer']),\n    })\n    .on('response', function (response) {\n      res.set(response.headers);\n    })\n    .on('error', function (err) {\n      logger.error(err);\n    })\n    .pipe(res);\n};\n"
  },
  {
    "path": "middlewares/render.js",
    "content": "var logger = require('../common/logger');\n\n// Patch res.render method to output logger\nexports.render = function (req, res, next) {\n  res._render = res.render;\n\n  res.render = function (view, options, fn) {\n    var t = new Date();\n\n    res._render(view, options, fn);\n\n    var duration = (new Date() - t);\n    logger.info(\"Render view\", view, (\"(\" + duration + \"ms)\").green);\n  };\n\n  next();\n};\n"
  },
  {
    "path": "middlewares/request_log.js",
    "content": "var logger = require('../common/logger');\n\nvar ignore = /^\\/(public|agent)/;\n\nexports = module.exports = function (req, res, next) {\n  // Assets do not out log.\n  if (ignore.test(req.url)) {\n    next();\n    return;\n  }\n\n  var t = new Date();\n  logger.info('\\n\\nStarted', t.toISOString(), req.method, req.url, req.ip);\n\n  res.on('finish', function () {\n    var duration = ((new Date()) - t);\n\n    logger.info('Completed', res.statusCode, ('(' + duration + 'ms)').green);\n  });\n\n  next();\n};\n"
  },
  {
    "path": "models/base_model.js",
    "content": "/**\n * 给所有的 Model 扩展功能\n * http://mongoosejs.com/docs/plugins.html\n */\nvar tools = require('../common/tools');\n\nmodule.exports = function (schema) {\n  schema.methods.create_at_ago = function () {\n    return tools.formatDate(this.create_at, true);\n  };\n\n  schema.methods.update_at_ago = function () {\n    return tools.formatDate(this.update_at, true);\n  };\n};\n"
  },
  {
    "path": "models/index.js",
    "content": "var mongoose = require('mongoose');\nvar config   = require('../config');\nvar logger = require('../common/logger')\n\nmongoose.connect(config.db, {\n  poolSize: 20,\n  useCreateIndex: true,\n  useNewUrlParser: true\n}, function (err) {\n  if (err) {\n    logger.error('connect to %s error: ', config.db, err.message);\n    process.exit(1);\n  }\n});\n\n// models\nrequire('./user');\nrequire('./topic');\nrequire('./reply');\nrequire('./topic_collect');\nrequire('./message');\n\nexports.User         = mongoose.model('User');\nexports.Topic        = mongoose.model('Topic');\nexports.Reply        = mongoose.model('Reply');\nexports.TopicCollect = mongoose.model('TopicCollect');\nexports.Message      = mongoose.model('Message');\n"
  },
  {
    "path": "models/message.js",
    "content": "var mongoose  = require('mongoose');\nvar BaseModel = require(\"./base_model\");\nvar Schema    = mongoose.Schema;\nvar ObjectId  = Schema.ObjectId;\n\n/*\n * type:\n * reply: xx 回复了你的话题\n * reply2: xx 在话题中回复了你\n * follow: xx 关注了你\n * at: xx ＠了你\n */\n\nvar MessageSchema = new Schema({\n  type: { type: String },\n  master_id: { type: ObjectId},\n  author_id: { type: ObjectId },\n  topic_id: { type: ObjectId },\n  reply_id: { type: ObjectId },\n  has_read: { type: Boolean, default: false },\n  create_at: { type: Date, default: Date.now }\n});\nMessageSchema.plugin(BaseModel);\nMessageSchema.index({master_id: 1, has_read: -1, create_at: -1});\n\nmongoose.model('Message', MessageSchema);\n"
  },
  {
    "path": "models/reply.js",
    "content": "var mongoose  = require('mongoose');\nvar BaseModel = require(\"./base_model\");\nvar Schema    = mongoose.Schema;\nvar ObjectId  = Schema.ObjectId;\n\nvar ReplySchema = new Schema({\n  content: { type: String },\n  topic_id: { type: ObjectId},\n  author_id: { type: ObjectId },\n  reply_id: { type: ObjectId },\n  create_at: { type: Date, default: Date.now },\n  update_at: { type: Date, default: Date.now },\n  content_is_html: { type: Boolean },\n  ups: [Schema.Types.ObjectId],\n  deleted: {type: Boolean, default: false},\n});\n\nReplySchema.plugin(BaseModel);\nReplySchema.index({topic_id: 1});\nReplySchema.index({author_id: 1, create_at: -1});\n\nmongoose.model('Reply', ReplySchema);\n"
  },
  {
    "path": "models/topic.js",
    "content": "var mongoose  = require('mongoose');\nvar BaseModel = require(\"./base_model\");\nvar Schema    = mongoose.Schema;\nvar ObjectId  = Schema.ObjectId;\nvar config    = require('../config');\nvar _         = require('lodash');\n\nvar TopicSchema = new Schema({\n  title: { type: String },\n  content: { type: String },\n  author_id: { type: ObjectId },\n  top: { type: Boolean, default: false }, // 置顶帖\n  good: {type: Boolean, default: false}, // 精华帖\n  lock: {type: Boolean, default: false}, // 被锁定主题\n  reply_count: { type: Number, default: 0 },\n  visit_count: { type: Number, default: 0 },\n  collect_count: { type: Number, default: 0 },\n  create_at: { type: Date, default: Date.now },\n  update_at: { type: Date, default: Date.now },\n  last_reply: { type: ObjectId },\n  last_reply_at: { type: Date, default: Date.now },\n  content_is_html: { type: Boolean },\n  tab: {type: String},\n  deleted: {type: Boolean, default: false},\n});\n\nTopicSchema.plugin(BaseModel);\nTopicSchema.index({create_at: -1});\nTopicSchema.index({top: -1, last_reply_at: -1});\nTopicSchema.index({author_id: 1, create_at: -1});\n\nTopicSchema.virtual('tabName').get(function () {\n  var tab  = this.tab;\n  var pair = _.find(config.tabs, function (_pair) {\n    return _pair[0] === tab;\n  });\n\n  if (pair) {\n    return pair[1];\n  } else {\n    return '';\n  }\n});\n\nmongoose.model('Topic', TopicSchema);\n"
  },
  {
    "path": "models/topic_collect.js",
    "content": "var mongoose  = require('mongoose');\nvar BaseModel = require(\"./base_model\");\nvar Schema    = mongoose.Schema;\nvar ObjectId  = Schema.ObjectId;\n\nvar TopicCollectSchema = new Schema({\n  user_id: { type: ObjectId },\n  topic_id: { type: ObjectId },\n  create_at: { type: Date, default: Date.now }\n});\n\nTopicCollectSchema.plugin(BaseModel);\nTopicCollectSchema.index({user_id: 1, topic_id: 1}, {unique: true});\n\nmongoose.model('TopicCollect', TopicCollectSchema);\n"
  },
  {
    "path": "models/user.js",
    "content": "var mongoose  = require('mongoose');\nvar BaseModel = require(\"./base_model\");\nvar renderHelper = require('../common/render_helper');\nvar Schema    = mongoose.Schema;\nvar utility   = require('utility');\nvar _ = require('lodash');\n\nvar UserSchema = new Schema({\n  name: { type: String},\n  loginname: { type: String},\n  pass: { type: String },\n  email: { type: String},\n  url: { type: String },\n  profile_image_url: {type: String},\n  location: { type: String },\n  signature: { type: String },\n  profile: { type: String },\n  weibo: { type: String },\n  avatar: { type: String },\n  githubId: { type: String},\n  githubUsername: {type: String},\n  githubAccessToken: {type: String},\n  is_block: {type: Boolean, default: false},\n\n  score: { type: Number, default: 0 },\n  topic_count: { type: Number, default: 0 },\n  reply_count: { type: Number, default: 0 },\n  follower_count: { type: Number, default: 0 },\n  following_count: { type: Number, default: 0 },\n  collect_tag_count: { type: Number, default: 0 },\n  collect_topic_count: { type: Number, default: 0 },\n  create_at: { type: Date, default: Date.now },\n  update_at: { type: Date, default: Date.now },\n  is_star: { type: Boolean },\n  level: { type: String },\n  active: { type: Boolean, default: false },\n\n  receive_reply_mail: {type: Boolean, default: false },\n  receive_at_mail: { type: Boolean, default: false },\n  from_wp: { type: Boolean },\n\n  retrieve_time: {type: Number},\n  retrieve_key: {type: String},\n\n  accessToken: {type: String},\n});\n\nUserSchema.plugin(BaseModel);\nUserSchema.virtual('avatar_url').get(function () {\n  var url = this.avatar || ('https://gravatar.com/avatar/' + utility.md5(this.email.toLowerCase()) + '?size=48');\n\n  // www.gravatar.com 被墙\n  url = url.replace('www.gravatar.com', 'gravatar.com');\n\n  // 让协议自适应 protocol，使用 `//` 开头\n  if (url.indexOf('http:') === 0) {\n    url = url.slice(5);\n  }\n\n  // 如果是 github 的头像，则限制大小\n  if (url.indexOf('githubusercontent') !== -1) {\n    url += '&s=120';\n  }\n\n  return url;\n});\n\nUserSchema.virtual('isAdvanced').get(function () {\n  // 积分高于 700 则认为是高级用户\n  return this.score > 700 || this.is_star;\n});\n\nUserSchema.index({loginname: 1}, {unique: true});\nUserSchema.index({email: 1}, {unique: true});\nUserSchema.index({score: -1});\nUserSchema.index({githubId: 1});\nUserSchema.index({accessToken: 1});\n\nUserSchema.pre('save', function(next){\n  var now = new Date();\n  this.update_at = now;\n  next();\n});\n\nmongoose.model('User', UserSchema);\n"
  },
  {
    "path": "oneapm.js",
    "content": "/**\n * OneAPM agent configuration.\n *\n * See lib/config.defaults.js in the agent distribution for a more complete\n * description of configuration variables and their potential values.\n */\n\nvar config = require('./config');\n\nexports.config = {\n  /**\n   * Array of application names.\n   */\n  app_name : [config.name],\n  /**\n   * Your OneAPM license key.\n   */\n  license_key : config.oneapm_key,\n  logging : {\n    /**\n     * Level at which to log. 'trace' is most useful to OneAPM when diagnosing\n     * issues with the agent, 'info' and higher will impose the least overhead on\n     * production applications.\n     */\n    level : 'info'\n  },\n  transaction_events: {\n        enabled: true\n  }\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"nodeclub\",\n  \"version\": \"2.1.1\",\n  \"private\": true,\n  \"main\": \"app.js\",\n  \"description\": \"A Node.js bbs using MongoDB\",\n  \"repository\": \"https://github.com/cnodejs/nodeclub\",\n  \"dependencies\": {\n    \"async\": \"1.5.2\",\n    \"bcryptjs\": \"2.3.0\",\n    \"body-parser\": \"1.17.1\",\n    \"bytes\": \"^2.2.0\",\n    \"colors\": \"1.1.2\",\n    \"compression\": \"1.7.0\",\n    \"connect-busboy\": \"0.0.2\",\n    \"connect-redis\": \"3.0.2\",\n    \"cookie-parser\": \"1.4.1\",\n    \"cors\": \"2.7.1\",\n    \"csurf\": \"1.8.3\",\n    \"data2xml\": \"1.2.4\",\n    \"ejs-mate\": \"2.3.0\",\n    \"eventproxy\": \"1.0.0\",\n    \"express\": \"4.16.0\",\n    \"express-session\": \"1.12.1\",\n    \"helmet\": \"1.3.0\",\n    \"ioredis\": \"2.0.0\",\n    \"jpush-sdk\": \"3.3.2\",\n    \"loader-builder\": \"2.4.1\",\n    \"loader\": \"2.1.1\",\n    \"lodash\": \"4.17.21\",\n    \"log4js\": \"^0.6.29\",\n    \"markdown-it\": \"6.0.0\",\n    \"memory-cache\": \"0.1.4\",\n    \"method-override\": \"2.3.5\",\n    \"moment\": \"2.15.2\",\n    \"mongoose\": \"5.3.9\",\n    \"multiline\": \"1.0.2\",\n    \"node-uuid\": \"1.4.7\",\n    \"nodemailer\": \"2.3.0\",\n    \"nodemailer-smtp-transport\": \"2.4.0\",\n    \"oneapm\": \"1.2.20\",\n    \"passport\": \"0.3.2\",\n    \"passport-github\": \"1.1.0\",\n    \"pm2\": \"*\",\n    \"qn\": \"1.3.0\",\n    \"ready\": \"0.1.1\",\n    \"request\": \"2.81.0\",\n    \"response-time\": \"2.3.1\",\n    \"superagent\": \"2.0.0\",\n    \"utility\": \"1.6.0\",\n    \"validator\": \"5.1.0\",\n    \"xmlbuilder\": \"7.0.0\",\n    \"xss\": \"0.2.10\",\n    \"snyk\": \"^1.88.0\"\n  },\n  \"devDependencies\": {\n    \"errorhandler\": \"1.4.3\",\n    \"istanbul\": \"0.4.2\",\n    \"loader-connect\": \"1.0.1\",\n    \"mm\": \"1.3.5\",\n    \"mocha\": \"2.4.5\",\n    \"nock\": \"7.5.0\",\n    \"pedding\": \"1.0.0\",\n    \"should\": \"8.3.0\",\n    \"supertest\": \"1.2.0\"\n  },\n  \"scripts\": {\n    \"test\": \"make test\",\n    \"snyk-protect\": \"snyk protect\",\n    \"prepare\": \"npm run snyk-protect\"\n  },\n  \"snyk\": true\n}\n"
  },
  {
    "path": "proxy/index.js",
    "content": "exports.User         = require('./user');\nexports.Message      = require('./message');\nexports.Topic        = require('./topic');\nexports.Reply        = require('./reply');\nexports.TopicCollect = require('./topic_collect');\n"
  },
  {
    "path": "proxy/message.js",
    "content": "var EventProxy = require('eventproxy');\nvar _ = require('lodash');\n\nvar Message = require('../models').Message;\n\nvar User = require('./user');\nvar Topic = require('./topic');\nvar Reply = require('./reply');\n\n/**\n * 根据用户ID，获取未读消息的数量\n * Callback:\n * 回调函数参数列表：\n * - err, 数据库错误\n * - count, 未读消息数量\n * @param {String} id 用户ID\n * @param {Function} callback 获取消息数量\n */\nexports.getMessagesCount = function (id, callback) {\n  Message.countDocuments({master_id: id, has_read: false}, callback);\n};\n\n\n/**\n * 根据消息Id获取消息\n * Callback:\n * - err, 数据库错误\n * - message, 消息对象\n * @param {String} id 消息ID\n * @param {Function} callback 回调函数\n */\nexports.getMessageById = function (id, callback) {\n  Message.findOne({_id: id}, function (err, message) {\n    if (err) {\n      return callback(err);\n    }\n    getMessageRelations(message, callback);\n  });\n};\n\nvar getMessageRelations = exports.getMessageRelations = function (message, callback) {\n  if (message.type === 'reply' || message.type === 'reply2' || message.type === 'at') {\n    var proxy = new EventProxy();\n    proxy.fail(callback);\n    proxy.assign('author', 'topic', 'reply', function (author, topic, reply) {\n      message.author = author;\n      message.topic = topic;\n      message.reply = reply;\n      if (!author || !topic) {\n        message.is_invalid = true;\n      }\n      return callback(null, message);\n    }); // 接收异常\n    User.getUserById(message.author_id, proxy.done('author'));\n    Topic.getTopicById(message.topic_id, proxy.done('topic'));\n    Reply.getReplyById(message.reply_id, proxy.done('reply'));\n  } else {\n    return callback(null, {is_invalid: true});\n  }\n};\n\n/**\n * 根据用户ID，获取已读消息列表\n * Callback:\n * - err, 数据库异常\n * - messages, 消息列表\n * @param {String} userId 用户ID\n * @param {Function} callback 回调函数\n */\nexports.getReadMessagesByUserId = function (userId, callback) {\n  Message.find({master_id: userId, has_read: true}, null,\n    {sort: '-create_at', limit: 20}, callback);\n};\n\n/**\n * 根据用户ID，获取未读消息列表\n * Callback:\n * - err, 数据库异常\n * - messages, 未读消息列表\n * @param {String} userId 用户ID\n * @param {Function} callback 回调函数\n */\nexports.getUnreadMessageByUserId = function (userId, callback) {\n  Message.find({master_id: userId, has_read: false}, null,\n    {sort: '-create_at'}, callback);\n};\n\n\n/**\n * 将消息设置成已读\n */\nexports.updateMessagesToRead = function (userId, messages, callback) {\n  callback = callback || _.noop;\n  if (messages.length === 0) {\n    return callback();\n  }\n\n  var ids = messages.map(function (m) {\n    return m.id;\n  });\n\n  var query = { master_id: userId, _id: { $in: ids } };\n  Message.updateMany(query, { $set: { has_read: true } }).exec(callback);\n};\n\n\n/**\n * 将单个消息设置成已读\n */\nexports.updateOneMessageToRead = function (msg_id, callback) {\n  callback = callback || _.noop;\n  if (!msg_id) {\n    return callback();\n  }\n  var query = { _id: msg_id };\n  Message.updateMany(query, { $set: { has_read: true } }).exec(callback);\n};\n"
  },
  {
    "path": "proxy/reply.js",
    "content": "var models     = require('../models');\nvar Reply      = models.Reply;\nvar EventProxy = require('eventproxy');\nvar tools      = require('../common/tools');\nvar User       = require('./user');\nvar at         = require('../common/at');\n\n/**\n * 获取一条回复信息\n * @param {String} id 回复ID\n * @param {Function} callback 回调函数\n */\nexports.getReply = function (id, callback) {\n  Reply.findOne({_id: id}, callback);\n};\n\n/**\n * 根据回复ID，获取回复\n * Callback:\n * - err, 数据库异常\n * - reply, 回复内容\n * @param {String} id 回复ID\n * @param {Function} callback 回调函数\n */\nexports.getReplyById = function (id, callback) {\n  if (!id) {\n    return callback(null, null);\n  }\n  Reply.findOne({_id: id}, function (err, reply) {\n    if (err) {\n      return callback(err);\n    }\n    if (!reply) {\n      return callback(err, null);\n    }\n\n    var author_id = reply.author_id;\n    User.getUserById(author_id, function (err, author) {\n      if (err) {\n        return callback(err);\n      }\n      reply.author = author;\n      // TODO: 添加更新方法，有些旧帖子可以转换为markdown格式的内容\n      if (reply.content_is_html) {\n        return callback(null, reply);\n      }\n      at.linkUsers(reply.content, function (err, str) {\n        if (err) {\n          return callback(err);\n        }\n        reply.content = str;\n        return callback(err, reply);\n      });\n    });\n  });\n};\n\n/**\n * 根据主题ID，获取回复列表\n * Callback:\n * - err, 数据库异常\n * - replies, 回复列表\n * @param {String} id 主题ID\n * @param {Function} callback 回调函数\n */\nexports.getRepliesByTopicId = function (id, cb) {\n  Reply.find({topic_id: id, deleted: false}, '', {sort: 'create_at'}, function (err, replies) {\n    if (err) {\n      return cb(err);\n    }\n    if (replies.length === 0) {\n      return cb(null, []);\n    }\n\n    var proxy = new EventProxy();\n    proxy.after('reply_find', replies.length, function () {\n      cb(null, replies);\n    });\n    for (var j = 0; j < replies.length; j++) {\n      (function (i) {\n        var author_id = replies[i].author_id;\n        User.getUserById(author_id, function (err, author) {\n          if (err) {\n            return cb(err);\n          }\n          replies[i].author = author || { _id: '' };\n          if (replies[i].content_is_html) {\n            return proxy.emit('reply_find');\n          }\n          at.linkUsers(replies[i].content, function (err, str) {\n            if (err) {\n              return cb(err);\n            }\n            replies[i].content = str;\n            proxy.emit('reply_find');\n          });\n        });\n      })(j);\n    }\n  });\n};\n\n/**\n * 创建并保存一条回复信息\n * @param {String} content 回复内容\n * @param {String} topicId 主题ID\n * @param {String} authorId 回复作者\n * @param {String} [replyId] 回复ID，当二级回复时设定该值\n * @param {Function} callback 回调函数\n */\nexports.newAndSave = function (content, topicId, authorId, replyId, callback) {\n  if (typeof replyId === 'function') {\n    callback = replyId;\n    replyId  = null;\n  }\n  var reply       = new Reply();\n  reply.content   = content;\n  reply.topic_id  = topicId;\n  reply.author_id = authorId;\n\n  if (replyId) {\n    reply.reply_id = replyId;\n  }\n  reply.save(function (err) {\n    callback(err, reply);\n  });\n};\n\n/**\n * 根据topicId查询到最新的一条未删除回复\n * @param topicId 主题ID\n * @param callback 回调函数\n */\nexports.getLastReplyByTopId = function (topicId, callback) {\n  Reply.find({topic_id: topicId, deleted: false}, '_id', {sort: {create_at : -1}, limit : 1}, callback);\n};\n\nexports.getRepliesByAuthorId = function (authorId, opt, callback) {\n  if (!callback) {\n    callback = opt;\n    opt      = null;\n  }\n  Reply.find({author_id: authorId}, {}, opt, callback);\n};\n\n// 通过 author_id 获取回复总数\nexports.getCountByAuthorId = function (authorId, callback) {\n  Reply.countDocuments({author_id: authorId}, callback);\n};\n"
  },
  {
    "path": "proxy/topic.js",
    "content": "var EventProxy = require('eventproxy');\nvar models     = require('../models');\nvar Topic      = models.Topic;\nvar User       = require('./user');\nvar Reply      = require('./reply');\nvar tools      = require('../common/tools');\nvar at         = require('../common/at');\nvar _          = require('lodash');\n\n\n/**\n * 根据主题ID获取主题\n * Callback:\n * - err, 数据库错误\n * - topic, 主题\n * - author, 作者\n * - lastReply, 最后回复\n * @param {String} id 主题ID\n * @param {Function} callback 回调函数\n */\nexports.getTopicById = function (id, callback) {\n  var proxy = new EventProxy();\n  var events = ['topic', 'author', 'last_reply'];\n  proxy.assign(events, function (topic, author, last_reply) {\n    if (!author) {\n      return callback(null, null, null, null);\n    }\n    return callback(null, topic, author, last_reply);\n  }).fail(callback);\n\n  Topic.findOne({_id: id}, proxy.done(function (topic) {\n    if (!topic) {\n      proxy.emit('topic', null);\n      proxy.emit('author', null);\n      proxy.emit('last_reply', null);\n      return;\n    }\n    proxy.emit('topic', topic);\n\n    User.getUserById(topic.author_id, proxy.done('author'));\n\n    if (topic.last_reply) {\n      Reply.getReplyById(topic.last_reply, proxy.done(function (last_reply) {\n        proxy.emit('last_reply', last_reply);\n      }));\n    } else {\n      proxy.emit('last_reply', null);\n    }\n  }));\n};\n\n/**\n * 获取关键词能搜索到的主题数量\n * Callback:\n * - err, 数据库错误\n * - count, 主题数量\n * @param {String} query 搜索关键词\n * @param {Function} callback 回调函数\n */\nexports.getCountByQuery = function (query, callback) {\n  Topic.countDocuments(query, callback);\n};\n\n/**\n * 根据关键词，获取主题列表\n * Callback:\n * - err, 数据库错误\n * - count, 主题列表\n * @param {String} query 搜索关键词\n * @param {Object} opt 搜索选项\n * @param {Function} callback 回调函数\n */\nexports.getTopicsByQuery = function (query, opt, callback) {\n  query.deleted = false;\n  Topic.find(query, {}, opt, function (err, topics) {\n    if (err) {\n      return callback(err);\n    }\n    if (topics.length === 0) {\n      return callback(null, []);\n    }\n\n    var proxy = new EventProxy();\n    proxy.after('topic_ready', topics.length, function () {\n      topics = _.compact(topics); // 删除不合规的 topic\n      return callback(null, topics);\n    });\n    proxy.fail(callback);\n\n    topics.forEach(function (topic, i) {\n      var ep = new EventProxy();\n      ep.all('author', 'reply', function (author, reply) {\n        // 保证顺序\n        // 作者可能已被删除\n        if (author) {\n          topic.author = author;\n          topic.reply = reply;\n        } else {\n          topics[i] = null;\n        }\n        proxy.emit('topic_ready');\n      });\n\n      User.getUserById(topic.author_id, ep.done('author'));\n      // 获取主题的最后回复\n      Reply.getReplyById(topic.last_reply, ep.done('reply'));\n    });\n  });\n};\n\n// for sitemap\nexports.getLimit5w = function (callback) {\n  Topic.find({deleted: false}, '_id', {limit: 50000, sort: '-create_at'}, callback);\n};\n\n/**\n * 获取所有信息的主题\n * Callback:\n * - err, 数据库异常\n * - message, 消息\n * - topic, 主题\n * - author, 主题作者\n * - replies, 主题的回复\n * @param {String} id 主题ID\n * @param {Function} callback 回调函数\n */\nexports.getFullTopic = function (id, callback) {\n  var proxy = new EventProxy();\n  var events = ['topic', 'author', 'replies'];\n  proxy\n    .assign(events, function (topic, author, replies) {\n      callback(null, '', topic, author, replies);\n    })\n    .fail(callback);\n\n  Topic.findOne({_id: id, deleted: false}, proxy.done(function (topic) {\n    if (!topic) {\n      proxy.unbind();\n      return callback(null, '此话题不存在或已被删除。');\n    }\n    at.linkUsers(topic.content, proxy.done('topic', function (str) {\n      topic.linkedContent = str;\n      return topic;\n    }));\n\n    User.getUserById(topic.author_id, proxy.done(function (author) {\n      if (!author) {\n        proxy.unbind();\n        return callback(null, '话题的作者丢了。');\n      }\n      proxy.emit('author', author);\n    }));\n\n    Reply.getRepliesByTopicId(topic._id, proxy.done('replies'));\n  }));\n};\n\n/**\n * 更新主题的最后回复信息\n * @param {String} topicId 主题ID\n * @param {String} replyId 回复ID\n * @param {Function} callback 回调函数\n */\nexports.updateLastReply = function (topicId, replyId, callback) {\n  Topic.findOne({_id: topicId}, function (err, topic) {\n    if (err || !topic) {\n      return callback(err);\n    }\n    topic.last_reply    = replyId;\n    topic.last_reply_at = new Date();\n    topic.reply_count += 1;\n    topic.save(callback);\n  });\n};\n\n/**\n * 根据主题ID，查找一条主题\n * @param {String} id 主题ID\n * @param {Function} callback 回调函数\n */\nexports.getTopic = function (id, callback) {\n  Topic.findOne({_id: id}, callback);\n};\n\n/**\n * 将当前主题的回复计数减1，并且更新最后回复的用户，删除回复时用到\n * @param {String} id 主题ID\n * @param {Function} callback 回调函数\n */\nexports.reduceCount = function (id, callback) {\n  Topic.findOne({_id: id}, function (err, topic) {\n    if (err) {\n      return callback(err);\n    }\n\n    if (!topic) {\n      return callback(new Error('该主题不存在'));\n    }\n    topic.reply_count -= 1;\n\n    Reply.getLastReplyByTopId(id, function (err, reply) {\n      if (err) {\n        return callback(err);\n      }\n\n      if (reply.length !== 0) {\n        topic.last_reply = reply[0]._id;\n      } else {\n        topic.last_reply = null;\n      }\n\n      topic.save(callback);\n    });\n\n  });\n};\n\nexports.newAndSave = function (title, content, tab, authorId, callback) {\n  var topic       = new Topic();\n  topic.title     = title;\n  topic.content   = content;\n  topic.tab       = tab;\n  topic.author_id = authorId;\n\n  topic.save(callback);\n};\n"
  },
  {
    "path": "proxy/topic_collect.js",
    "content": "var TopicCollect = require('../models').TopicCollect;\nvar _ = require('lodash')\n\nexports.getTopicCollect = function (userId, topicId, callback) {\n  TopicCollect.findOne({user_id: userId, topic_id: topicId}, callback);\n};\n\nexports.getTopicCollectsByUserId = function (userId, opt, callback) {\n  var defaultOpt = {sort: '-create_at'};\n  opt = _.assign(defaultOpt, opt)\n  TopicCollect.find({user_id: userId}, '', opt, callback);\n};\n\nexports.newAndSave = function (userId, topicId, callback) {\n  var topic_collect      = new TopicCollect();\n  topic_collect.user_id  = userId;\n  topic_collect.topic_id = topicId;\n  topic_collect.save(callback);\n};\n\nexports.remove = function (userId, topicId, callback) {\n  TopicCollect.deleteOne({user_id: userId, topic_id: topicId}, callback);\n};\n\n"
  },
  {
    "path": "proxy/user.js",
    "content": "var models  = require('../models');\nvar User    = models.User;\nvar utility = require('utility');\nvar uuid    = require('node-uuid');\n\n/**\n * 根据用户名列表查找用户列表\n * Callback:\n * - err, 数据库异常\n * - users, 用户列表\n * @param {Array} names 用户名列表\n * @param {Function} callback 回调函数\n */\nexports.getUsersByNames = function (names, callback) {\n  if (names.length === 0) {\n    return callback(null, []);\n  }\n  User.find({ loginname: { $in: names } }, callback);\n};\n\n/**\n * 根据登录名查找用户\n * Callback:\n * - err, 数据库异常\n * - user, 用户\n * @param {String} loginName 登录名\n * @param {Function} callback 回调函数\n */\nexports.getUserByLoginName = function (loginName, callback) {\n  User.findOne({'loginname': new RegExp('^'+loginName+'$', \"i\")}, callback);\n};\n\n/**\n * 根据用户ID，查找用户\n * Callback:\n * - err, 数据库异常\n * - user, 用户\n * @param {String} id 用户ID\n * @param {Function} callback 回调函数\n */\nexports.getUserById = function (id, callback) {\n  if (!id) {\n    return callback();\n  }\n  User.findOne({_id: id}, callback);\n};\n\n/**\n * 根据邮箱，查找用户\n * Callback:\n * - err, 数据库异常\n * - user, 用户\n * @param {String} email 邮箱地址\n * @param {Function} callback 回调函数\n */\nexports.getUserByMail = function (email, callback) {\n  User.findOne({email: email}, callback);\n};\n\n/**\n * 根据用户ID列表，获取一组用户\n * Callback:\n * - err, 数据库异常\n * - users, 用户列表\n * @param {Array} ids 用户ID列表\n * @param {Function} callback 回调函数\n */\nexports.getUsersByIds = function (ids, callback) {\n  User.find({'_id': {'$in': ids}}, callback);\n};\n\n/**\n * 根据关键字，获取一组用户\n * Callback:\n * - err, 数据库异常\n * - users, 用户列表\n * @param {String} query 关键字\n * @param {Object} opt 选项\n * @param {Function} callback 回调函数\n */\nexports.getUsersByQuery = function (query, opt, callback) {\n  User.find(query, '', opt, callback);\n};\n\n/**\n * 根据查询条件，获取一个用户\n * Callback:\n * - err, 数据库异常\n * - user, 用户\n * @param {String} name 用户名\n * @param {String} key 激活码\n * @param {Function} callback 回调函数\n */\nexports.getUserByNameAndKey = function (loginname, key, callback) {\n  User.findOne({loginname: loginname, retrieve_key: key}, callback);\n};\n\nexports.newAndSave = function (name, loginname, pass, email, avatar_url, active, callback) {\n  var user         = new User();\n  user.name        = loginname;\n  user.loginname   = loginname;\n  user.pass        = pass;\n  user.email       = email;\n  user.avatar      = avatar_url;\n  user.active      = active || false;\n  user.accessToken = uuid.v4();\n\n  user.save(callback);\n};\n\nvar makeGravatar = function (email) {\n  return 'http://www.gravatar.com/avatar/' + utility.md5(email.toLowerCase()) + '?size=48';\n};\nexports.makeGravatar = makeGravatar;\n\nexports.getGravatar = function (user) {\n  return user.avatar || makeGravatar(user);\n};\n"
  },
  {
    "path": "public/github-card.html",
    "content": "<!doctype html><html><body>\n<style type=\"text/css\">\nbody{padding:0;margin:0;font-size:14px;font-family:\"Helvetica Nenu\",Hevetica,Arial,sans-serif;overflow:hidden}body.ready{border:1px solid #eee;border-radius:5px;border-color:#eee #ddd #bbb;box-shadow:rgba(0,0,0,.14) 0 1px 3px}.github-card{border-radius:5px;padding:8px 8px 0;background:#fff;color:#555;position:relative}.github-card a{text-decoration:none;color:#4183c4;outline:0}.github-card a:hover{text-decoration:underline}.github-card .header{position:relative}.github-card .button{position:absolute;top:0;right:8px;padding:4px 8px 4px 7px;color:#555;text-shadow:0 1px 0 #fff;border:1px solid #d4d4d4;border-radius:3px;font-size:13px;font-weight:700;line-height:14px;background-color:#e6e6e6;background-image:-webkit-linear-gradient(#fafafa,#eaeaea);background-image:-moz-linear-gradient(#fafafa,#eaeaea);background-image:-ms-linear-gradient(#fafafa,#eaeaea);background-image:linear-gradient(#fafafa,#eaeaea)}.github-card .button:hover{color:#fff;text-decoration:none;background-color:#3072b3;background-image:-webkit-linear-gradient(#599bdc,#3072b3);background-image:-moz-linear-gradient(#599bdc,#3072b3);background-image:-ms-linear-gradient(#599bdc,#3072b3);background-image:linear-gradient(#599bdc,#3072b3);border-color:#518cc6 #518cc6 #2a65a0;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.user-card .header{padding:3px 0 4px 57px;min-height:48px}.user-card .header a{color:#707070;text-decoration:none}.user-card .header a:hover strong{text-decoration:underline}.user-card img{position:absolute;top:0;left:0;width:48px;height:48px;background:#fff;border-radius:4px}.user-card strong{display:block;color:#292f33;font-size:16px;line-height:1.6}.user-card ul{text-transform:uppercase;font-size:12px;color:#707070;list-style-type:none;margin:0;padding:0;border-top:1px solid #eee;border-bottom:1px solid #eee;zoom:1}.user-card ul:after{display:block;content:'';clear:both}.user-card .status a{color:#707070;text-decoration:none}.user-card .status a:hover{color:#4183c4}.user-card .status li{float:left;padding:4px 18px;border-left:1px solid #eee}.user-card .status li:first-child{border-left:0;padding-left:0}.user-card .footer{font-size:12px;font-weight:700;padding:11px 0 10px;color:#646464}.user-card .footer a{color:#646464}.repo-card .header{padding:3px 0 4px 57px}.repo-card .avatar,.repo-card .avatar img{position:absolute;top:0;left:0;width:48px;height:48px;background:#fff;border-radius:4px}.repo-card .header a{color:#707070}.repo-card .header strong{display:block;font-size:18px;line-height:1.4}.repo-card .header strong a{color:#292f33}.repo-card .header sup{font-size:10px;margin-left:3px;color:#797979}.repo-card .content{padding:6px 0 10px}.repo-card .content p{margin:0 5px 0 0;font:18px/24px Georgia,\"Times New Roman\",Palatino,serif;overflow:hidden;clear:both;word-wrap:break-word}.repo-card .footer{border-top:1px solid #eee;padding:8px 0 6px}.repo-card .status{font-size:10px;padding-right:10px;text-transform:uppercase}.repo-card .status strong{font-size:12px;padding-right:5px}</style>\n<script id=\"user-card\" type=\"text/template\"><div class=\"header\"><a class=\"avatar\" href=\"https://github.com/{login}\"><img src=\"{avatar_url}&s=48\"><strong>{name}</strong><span>@{login}</span></a><a class=\"button\" href=\"https://github.com/{login}\">Follow</a></div><ul class=\"status\"><li><a href=\"https://github.com/{login}?tab=repositories\"><strong>{public_repos}</strong>Repos</a></li><li><a href=\"https://gist.github.com/{login}\"><strong>{public_gists}</strong>Gists</a></li><li><a href=\"https://github.com/{login}/followers\"><strong>{followers}</strong>Followers</a></li></ul><div class=\"footer\">{job}</div></script><script id=\"repo-card\" type=\"text/template\"><div class=\"header\"><a class=\"avatar\" href=\"https://github.com/{login}\"><img src=\"{avatar_url}&s=48\"></a><strong class=\"name\"><a href=\"https://github.com/{full_name}\">{name}</a><sup class=\"language\">{language}</sup></strong><span>{action}<a href=\"https://github.com/{login}\">{login}</a></span><a class=\"button\" href=\"https://github.com/{full_name}\">Star</a></div><div class=\"content\"><p>{description}{homepage}</p></div><div class=\"footer\"><span class=\"status\"><strong>{forks_count}</strong>Forks</span><span class=\"status\"><strong>{watchers_count}</strong>Stars</span></div></script><script>\n(function(e){var r=\"https://api.github.com/\",t;function a(){var e=window.location.href,r;var a=e.slice(e.indexOf(\"?\")+1).split(\"&\");var i=[];for(t=0;t<a.length;t++){r=a[t].split(\"=\");i.push(r[0]);i[r[0]]=r[1]}return i}function i(e,r){if(window.localStorage){if(r){r._timestamp=(new Date).valueOf();localStorage[e]=JSON.stringify(r)}else{var t=localStorage[e];if(t){return JSON.parse(t)}return null}}}function n(e,r){var t=e;var a=r.split(\".\");for(var i=0;i<a.length;i++){if(t){t=t[a[i]]}else{break}}if(t===undefined||t===null){return\"\"}return t}var o=a();function l(r,a){var i=e.getElementById(r+\"-card\");var o=/{([^}]+)}/g;var l=i.innerHTML;var s=l.match(o);for(t=0;t<s.length;t++){l=l.replace(s[t],n(a,s[t].slice(1,-1)))}return l}function s(e,r){var t=i(e);if(t&&t._timestamp){if((new Date).valueOf()-t._timestamp<1e4){return r(t)}}if(o.client_id&&o.client_secret){e+=\"?client_id=\"+o.client_id+\"&client_secret=\"+o.client_secret}var a=new XMLHttpRequest;a.open(\"GET\",e,false);a.onload=function(){r(JSON.parse(a.response))};a.send()}function c(r,a){var i=r.getElementsByTagName(\"a\");for(t=0;t<i.length;t++){(function(e){e.target=\"_\"+(o.target||\"top\")})(i[t])}e.body.appendChild(r);e.body.className=\"ready\";if(parent!==self&&parent.postMessage){var n=Math.max(e.body.scrollHeight,e.documentElement.scrollHeight,e.body.offsetHeight,e.documentElement.offsetHeight,e.body.clientHeight,e.documentElement.clientHeight);parent.postMessage({height:n,sender:o.identity||\"*\"},\"*\")}}function f(t){var a=r+\"users/\"+t;s(a,function(r){r=r||{};var n=r.message;var o=\"0\";if(n){r=i(a)||r;o=\"?\"}else{i(a,r)}r.login=t;r.public_repos=r.public_repos||o;r.public_gists=r.public_gists||o;r.followers=r.followers||o;var s=\"Not available for hire.\";if(r.hireable){var f=\"\";if(r.email){f=\"mailto:\"+r.email}else if(r.blog){f=r.blog}else{f=r.html_url}s='<a href=\"'+f+'\">Available for hire.</a>'}if(n){s=n}r.job=s;var u=e.createElement(\"div\");u.className=\"github-card user-card\";u.innerHTML=l(\"user\",r);c(u)})}function u(t,a){var n=r+\"repos/\"+t+\"/\"+a;s(n,function(r){r=r||{};var a=r.message;var o=\"0\";if(a){r=i(n)||r;o=\"?\"}else{i(n,r)}r.login=t;r.avatar_url=\"\";if(r.owner&&r.owner.avatar_url){r.avatar_url=r.owner.avatar_url}r.forks_count=r.forks_count||o;r.watchers_count=r.watchers_count||o;if(r.fork){r.action=\"Forked by \"}else{r.action=\"Created by \"}var s=r.description;if(!s&&r.source){s=r.source.description}if(!s&&a){s=a}r.description=s||\"No description\";var f=r.homepage;if(!f&&r.source){f=r.source.homepage}if(f){r.homepage=' <a href=\"'+f+'\">'+f.replace(/https?:\\/\\//,\"\")+\"</a>\"}else{r.homepage=\"\"}var u=e.createElement(\"div\");u.className=\"github-card repo-card\";u.innerHTML=l(\"repo\",r);c(u)})}function p(){}if(!o.user){p()}else if(o.repo){u(o.user,o.repo)}else{f(o.user)}})(document);var _gaq=_gaq||[];_gaq.push(['_setAccount','UA-21475122-2']);_gaq.push(['_trackPageview']);(function(d){var g=d.createElement(\"script\");g.async=true;g.src=\"https://ssl.google-analytics.com/ga.js\";var s=d.getElementsByTagName(\"script\")[0];s.parentNode.insertBefore(g, s);})(document);</script></body></html>\n"
  },
  {
    "path": "public/javascripts/main.js",
    "content": "$(document).ready(function () {\n  var windowHeight = $(window).height();\n  var $backtotop = $('#backtotop');\n  var top = windowHeight - $backtotop.height() - 200;\n\n\n  function moveBacktotop() {\n    $backtotop.css({ top: top, right: 0});\n  }\n\n  function footerFixBottom() {\n      if($(document.body).height() < windowHeight){\n          $(\"#footer\").addClass('fix-bottom');\n      }else{\n          $(\"#footer\").removeClass('fix-bottom');\n      }\n  }\n\n  $backtotop.click(function () {\n    $('html,body').animate({ scrollTop: 0 });\n    return false;\n  });\n  $(window).scroll(function () {\n    var windowHeight = $(window).scrollTop();\n    if (windowHeight > 200) {\n      $backtotop.fadeIn();\n    } else {\n      $backtotop.fadeOut();\n    }\n  });\n\n  moveBacktotop();\n  footerFixBottom();\n  $(window).resize(moveBacktotop);\n  $(window).resize(footerFixBottom);\n\n  $('.topic_content a,.reply_content a').attr('target', '_blank');\n\n  // pretty code\n  prettyPrint();\n\n  // data-loading-text=\"提交中\"\n  $('.submit_btn').click(function () {\n    $(this).button('loading');\n  });\n\n  // 广告的统计信息\n  $('.sponsor_outlink').click(function () {\n    var $this = $(this);\n    var label = $this.data('label');\n    ga('send', 'event', 'banner', 'click', label, 1.00, {'nonInteraction': 1});\n  });\n});\n"
  },
  {
    "path": "public/javascripts/responsive.js",
    "content": "$(document).ready(function () {\n  var $responsiveBtn = $('#responsive-sidebar-trigger'),\n    $sidebarMask = $('#sidebar-mask'),\n    $sidebar = $('#sidebar'),\n    $main = $('#main'),\n    winWidth = $(window).width(),\n    startX = 0,\n    startY = 0,\n    delta = {\n      x: 0,\n      y: 0\n    },\n    swipeThreshold = winWidth / 3,\n    toggleSideBar = function () {\n      var isShow = $responsiveBtn.data('is-show'),\n        mainHeight = $main.height(),\n        sidebarHeight = $sidebar.outerHeight();\n      $sidebar.css({right: isShow ? -300 : 0});\n      $responsiveBtn.data('is-show', !isShow);\n      if (!isShow && mainHeight < sidebarHeight) {\n        $main.height(sidebarHeight);\n      }\n      $sidebarMask[isShow ? 'fadeOut' : 'fadeIn']().height($('body').height());\n      $sidebar[isShow ? 'hide' : 'show']()\n    },\n    touchstart = function (e) {\n      var touchs = e.targetTouches;\n      startX = +touchs[0].pageX;\n      startY = +touchs[0].pageY;\n      delta.x = delta.y = 0;\n      document.body.addEventListener('touchmove', touchmove, false);\n      document.body.addEventListener('touchend', touchend, false);\n    },\n    touchmove = function (e) {\n      var touchs = e.changedTouches;\n      delta.x = +touchs[0].pageX - startX;\n      delta.y = +touchs[0].pageY - startY;\n      //当水平距离大于垂直距离时，才认为是用户想滑动打开右侧栏\n      if (Math.abs(delta.x) > Math.abs(delta.y)) {\n        e.preventDefault();\n      }\n    },\n    touchend = function (e) {\n      var touchs = e.changedTouches,\n        isShow = $responsiveBtn.data('is-show');\n      delta.x = +touchs[0].pageX - startX;\n      //右侧栏未显示&&用户touch点在屏幕右侧1/4区域内&&move距离大于阀值时，打开右侧栏\n      if (!isShow && (startX > winWidth * 3 / 4) && Math.abs(delta.x) > swipeThreshold) {\n        $responsiveBtn.trigger('click');\n      }\n      //右侧栏显示中&&用户touch点在屏幕左侧侧1/4区域内&&move距离大于阀值时，关闭右侧栏\n      if (isShow && (startX < winWidth * 1 / 4) && Math.abs(delta.x) > swipeThreshold) {\n        $responsiveBtn.trigger('click');\n      }\n      startX = startY = 0;\n      delta.x = delta.y = 0;\n      document.body.removeEventListener('touchmove', touchmove, false);\n      document.body.removeEventListener('touchend', touchend, false);\n    };\n\n  if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {\n    document.body.addEventListener('touchstart', touchstart);\n  }\n\n  $responsiveBtn.on('click', toggleSideBar);\n\n  $sidebarMask.on('click', function () {\n    $responsiveBtn.trigger('click');\n  });\n\n});\n"
  },
  {
    "path": "public/libs/bootstrap/css/bootstrap-responsive.css",
    "content": "/*!\n * Bootstrap Responsive v2.3.1\n *\n * Copyright 2012 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n\n.clearfix {\n    *zoom: 1;\n}\n\n.clearfix:before,\n.clearfix:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.clearfix:after {\n    clear: both;\n}\n\n.hide-text {\n    font: 0/0 a;\n    color: transparent;\n    text-shadow: none;\n    background-color: transparent;\n    border: 0;\n}\n\n.input-block-level {\n    display: block;\n    width: 100%;\n    min-height: 30px;\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n\n@-ms-viewport {\n    width: device-width;\n}\n\n.hidden {\n    display: none;\n    visibility: hidden;\n}\n\n.visible-phone {\n    display: none !important;\n}\n\n.visible-tablet {\n    display: none !important;\n}\n\n.hidden-desktop {\n    display: none !important;\n}\n\n.visible-desktop {\n    display: inherit !important;\n}\n\n@media (min-width: 768px) and (max-width: 979px) {\n    .hidden-desktop {\n        display: inherit !important;\n    }\n\n    .visible-desktop {\n        display: none !important;\n    }\n\n    .visible-tablet {\n        display: inherit !important;\n    }\n\n    .hidden-tablet {\n        display: none !important;\n    }\n}\n\n@media (max-width: 767px) {\n    .hidden-desktop {\n        display: inherit !important;\n    }\n\n    .visible-desktop {\n        display: none !important;\n    }\n\n    .visible-phone {\n        display: inherit !important;\n    }\n\n    .hidden-phone {\n        display: none !important;\n    }\n}\n\n.visible-print {\n    display: none !important;\n}\n\n@media print {\n    .visible-print {\n        display: inherit !important;\n    }\n\n    .hidden-print {\n        display: none !important;\n    }\n}\n\n@media (min-width: 1200px) {\n    .row {\n        margin-left: -30px;\n        *zoom: 1;\n    }\n\n    .row:before,\n    .row:after {\n        display: table;\n        line-height: 0;\n        content: \"\";\n    }\n\n    .row:after {\n        clear: both;\n    }\n\n    [class*=\"span\"] {\n        float: left;\n        min-height: 1px;\n        margin-left: 30px;\n    }\n\n    .container,\n    .navbar-static-top .container,\n    .navbar-fixed-top .container,\n    .navbar-fixed-bottom .container {\n        width: 1170px;\n    }\n\n    .span12 {\n        width: 1170px;\n    }\n\n    .span11 {\n        width: 1070px;\n    }\n\n    .span10 {\n        width: 970px;\n    }\n\n    .span9 {\n        width: 870px;\n    }\n\n    .span8 {\n        width: 770px;\n    }\n\n    .span7 {\n        width: 670px;\n    }\n\n    .span6 {\n        width: 570px;\n    }\n\n    .span5 {\n        width: 470px;\n    }\n\n    .span4 {\n        width: 370px;\n    }\n\n    .span3 {\n        width: 270px;\n    }\n\n    .span2 {\n        width: 170px;\n    }\n\n    .span1 {\n        width: 70px;\n    }\n\n    .offset12 {\n        margin-left: 1230px;\n    }\n\n    .offset11 {\n        margin-left: 1130px;\n    }\n\n    .offset10 {\n        margin-left: 1030px;\n    }\n\n    .offset9 {\n        margin-left: 930px;\n    }\n\n    .offset8 {\n        margin-left: 830px;\n    }\n\n    .offset7 {\n        margin-left: 730px;\n    }\n\n    .offset6 {\n        margin-left: 630px;\n    }\n\n    .offset5 {\n        margin-left: 530px;\n    }\n\n    .offset4 {\n        margin-left: 430px;\n    }\n\n    .offset3 {\n        margin-left: 330px;\n    }\n\n    .offset2 {\n        margin-left: 230px;\n    }\n\n    .offset1 {\n        margin-left: 130px;\n    }\n\n    .row-fluid {\n        width: 100%;\n        *zoom: 1;\n    }\n\n    .row-fluid:before,\n    .row-fluid:after {\n        display: table;\n        line-height: 0;\n        content: \"\";\n    }\n\n    .row-fluid:after {\n        clear: both;\n    }\n\n    .row-fluid [class*=\"span\"] {\n        display: block;\n        float: left;\n        width: 100%;\n        min-height: 30px;\n        margin-left: 2.564102564102564%;\n        *margin-left: 2.5109110747408616%;\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n    }\n\n    .row-fluid [class*=\"span\"]:first-child {\n        margin-left: 0;\n    }\n\n    .row-fluid .controls-row [class*=\"span\"] + [class*=\"span\"] {\n        margin-left: 2.564102564102564%;\n    }\n\n    .row-fluid .span12 {\n        width: 100%;\n        *width: 99.94680851063829%;\n    }\n\n    .row-fluid .span11 {\n        width: 91.45299145299145%;\n        *width: 91.39979996362975%;\n    }\n\n    .row-fluid .span10 {\n        width: 82.90598290598291%;\n        *width: 82.8527914166212%;\n    }\n\n    .row-fluid .span9 {\n        width: 74.35897435897436%;\n        *width: 74.30578286961266%;\n    }\n\n    .row-fluid .span8 {\n        width: 65.81196581196582%;\n        *width: 65.75877432260411%;\n    }\n\n    .row-fluid .span7 {\n        width: 57.26495726495726%;\n        *width: 57.21176577559556%;\n    }\n\n    .row-fluid .span6 {\n        width: 48.717948717948715%;\n        *width: 48.664757228587014%;\n    }\n\n    .row-fluid .span5 {\n        width: 40.17094017094017%;\n        *width: 40.11774868157847%;\n    }\n\n    .row-fluid .span4 {\n        width: 31.623931623931625%;\n        *width: 31.570740134569924%;\n    }\n\n    .row-fluid .span3 {\n        width: 23.076923076923077%;\n        *width: 23.023731587561375%;\n    }\n\n    .row-fluid .span2 {\n        width: 14.52991452991453%;\n        *width: 14.476723040552828%;\n    }\n\n    .row-fluid .span1 {\n        width: 5.982905982905983%;\n        *width: 5.929714493544281%;\n    }\n\n    .row-fluid .offset12 {\n        margin-left: 105.12820512820512%;\n        *margin-left: 105.02182214948171%;\n    }\n\n    .row-fluid .offset12:first-child {\n        margin-left: 102.56410256410257%;\n        *margin-left: 102.45771958537915%;\n    }\n\n    .row-fluid .offset11 {\n        margin-left: 96.58119658119658%;\n        *margin-left: 96.47481360247316%;\n    }\n\n    .row-fluid .offset11:first-child {\n        margin-left: 94.01709401709402%;\n        *margin-left: 93.91071103837061%;\n    }\n\n    .row-fluid .offset10 {\n        margin-left: 88.03418803418803%;\n        *margin-left: 87.92780505546462%;\n    }\n\n    .row-fluid .offset10:first-child {\n        margin-left: 85.47008547008548%;\n        *margin-left: 85.36370249136206%;\n    }\n\n    .row-fluid .offset9 {\n        margin-left: 79.48717948717949%;\n        *margin-left: 79.38079650845607%;\n    }\n\n    .row-fluid .offset9:first-child {\n        margin-left: 76.92307692307693%;\n        *margin-left: 76.81669394435352%;\n    }\n\n    .row-fluid .offset8 {\n        margin-left: 70.94017094017094%;\n        *margin-left: 70.83378796144753%;\n    }\n\n    .row-fluid .offset8:first-child {\n        margin-left: 68.37606837606839%;\n        *margin-left: 68.26968539734497%;\n    }\n\n    .row-fluid .offset7 {\n        margin-left: 62.393162393162385%;\n        *margin-left: 62.28677941443899%;\n    }\n\n    .row-fluid .offset7:first-child {\n        margin-left: 59.82905982905982%;\n        *margin-left: 59.72267685033642%;\n    }\n\n    .row-fluid .offset6 {\n        margin-left: 53.84615384615384%;\n        *margin-left: 53.739770867430444%;\n    }\n\n    .row-fluid .offset6:first-child {\n        margin-left: 51.28205128205128%;\n        *margin-left: 51.175668303327875%;\n    }\n\n    .row-fluid .offset5 {\n        margin-left: 45.299145299145295%;\n        *margin-left: 45.1927623204219%;\n    }\n\n    .row-fluid .offset5:first-child {\n        margin-left: 42.73504273504273%;\n        *margin-left: 42.62865975631933%;\n    }\n\n    .row-fluid .offset4 {\n        margin-left: 36.75213675213675%;\n        *margin-left: 36.645753773413354%;\n    }\n\n    .row-fluid .offset4:first-child {\n        margin-left: 34.18803418803419%;\n        *margin-left: 34.081651209310785%;\n    }\n\n    .row-fluid .offset3 {\n        margin-left: 28.205128205128204%;\n        *margin-left: 28.0987452264048%;\n    }\n\n    .row-fluid .offset3:first-child {\n        margin-left: 25.641025641025642%;\n        *margin-left: 25.53464266230224%;\n    }\n\n    .row-fluid .offset2 {\n        margin-left: 19.65811965811966%;\n        *margin-left: 19.551736679396257%;\n    }\n\n    .row-fluid .offset2:first-child {\n        margin-left: 17.094017094017094%;\n        *margin-left: 16.98763411529369%;\n    }\n\n    .row-fluid .offset1 {\n        margin-left: 11.11111111111111%;\n        *margin-left: 11.004728132387708%;\n    }\n\n    .row-fluid .offset1:first-child {\n        margin-left: 8.547008547008547%;\n        *margin-left: 8.440625568285142%;\n    }\n\n    input,\n    textarea,\n    .uneditable-input {\n        margin-left: 0;\n    }\n\n    .controls-row [class*=\"span\"] + [class*=\"span\"] {\n        margin-left: 30px;\n    }\n\n    input.span12,\n    textarea.span12,\n    .uneditable-input.span12 {\n        width: 1156px;\n    }\n\n    input.span11,\n    textarea.span11,\n    .uneditable-input.span11 {\n        width: 1056px;\n    }\n\n    input.span10,\n    textarea.span10,\n    .uneditable-input.span10 {\n        width: 956px;\n    }\n\n    input.span9,\n    textarea.span9,\n    .uneditable-input.span9 {\n        width: 856px;\n    }\n\n    input.span8,\n    textarea.span8,\n    .uneditable-input.span8 {\n        width: 756px;\n    }\n\n    input.span7,\n    textarea.span7,\n    .uneditable-input.span7 {\n        width: 656px;\n    }\n\n    input.span6,\n    textarea.span6,\n    .uneditable-input.span6 {\n        width: 556px;\n    }\n\n    input.span5,\n    textarea.span5,\n    .uneditable-input.span5 {\n        width: 456px;\n    }\n\n    input.span4,\n    textarea.span4,\n    .uneditable-input.span4 {\n        width: 356px;\n    }\n\n    input.span3,\n    textarea.span3,\n    .uneditable-input.span3 {\n        width: 256px;\n    }\n\n    input.span2,\n    textarea.span2,\n    .uneditable-input.span2 {\n        width: 156px;\n    }\n\n    input.span1,\n    textarea.span1,\n    .uneditable-input.span1 {\n        width: 56px;\n    }\n\n    .thumbnails {\n        margin-left: -30px;\n    }\n\n    .thumbnails > li {\n        margin-left: 30px;\n    }\n\n    .row-fluid .thumbnails {\n        margin-left: 0;\n    }\n}\n\n@media (min-width: 768px) and (max-width: 979px) {\n    .row {\n        margin-left: -20px;\n        *zoom: 1;\n    }\n\n    .row:before,\n    .row:after {\n        display: table;\n        line-height: 0;\n        content: \"\";\n    }\n\n    .row:after {\n        clear: both;\n    }\n\n    [class*=\"span\"] {\n        float: left;\n        min-height: 1px;\n        margin-left: 20px;\n    }\n\n    .container,\n    .navbar-static-top .container,\n    .navbar-fixed-top .container,\n    .navbar-fixed-bottom .container {\n        width: 724px;\n    }\n\n    .span12 {\n        width: 724px;\n    }\n\n    .span11 {\n        width: 662px;\n    }\n\n    .span10 {\n        width: 600px;\n    }\n\n    .span9 {\n        width: 538px;\n    }\n\n    .span8 {\n        width: 476px;\n    }\n\n    .span7 {\n        width: 414px;\n    }\n\n    .span6 {\n        width: 352px;\n    }\n\n    .span5 {\n        width: 290px;\n    }\n\n    .span4 {\n        width: 228px;\n    }\n\n    .span3 {\n        width: 166px;\n    }\n\n    .span2 {\n        width: 104px;\n    }\n\n    .span1 {\n        width: 42px;\n    }\n\n    .offset12 {\n        margin-left: 764px;\n    }\n\n    .offset11 {\n        margin-left: 702px;\n    }\n\n    .offset10 {\n        margin-left: 640px;\n    }\n\n    .offset9 {\n        margin-left: 578px;\n    }\n\n    .offset8 {\n        margin-left: 516px;\n    }\n\n    .offset7 {\n        margin-left: 454px;\n    }\n\n    .offset6 {\n        margin-left: 392px;\n    }\n\n    .offset5 {\n        margin-left: 330px;\n    }\n\n    .offset4 {\n        margin-left: 268px;\n    }\n\n    .offset3 {\n        margin-left: 206px;\n    }\n\n    .offset2 {\n        margin-left: 144px;\n    }\n\n    .offset1 {\n        margin-left: 82px;\n    }\n\n    .row-fluid {\n        width: 100%;\n        *zoom: 1;\n    }\n\n    .row-fluid:before,\n    .row-fluid:after {\n        display: table;\n        line-height: 0;\n        content: \"\";\n    }\n\n    .row-fluid:after {\n        clear: both;\n    }\n\n    .row-fluid [class*=\"span\"] {\n        display: block;\n        float: left;\n        width: 100%;\n        min-height: 30px;\n        margin-left: 2.7624309392265194%;\n        *margin-left: 2.709239449864817%;\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n    }\n\n    .row-fluid [class*=\"span\"]:first-child {\n        margin-left: 0;\n    }\n\n    .row-fluid .controls-row [class*=\"span\"] + [class*=\"span\"] {\n        margin-left: 2.7624309392265194%;\n    }\n\n    .row-fluid .span12 {\n        width: 100%;\n        *width: 99.94680851063829%;\n    }\n\n    .row-fluid .span11 {\n        width: 91.43646408839778%;\n        *width: 91.38327259903608%;\n    }\n\n    .row-fluid .span10 {\n        width: 82.87292817679558%;\n        *width: 82.81973668743387%;\n    }\n\n    .row-fluid .span9 {\n        width: 74.30939226519337%;\n        *width: 74.25620077583166%;\n    }\n\n    .row-fluid .span8 {\n        width: 65.74585635359117%;\n        *width: 65.69266486422946%;\n    }\n\n    .row-fluid .span7 {\n        width: 57.18232044198895%;\n        *width: 57.12912895262725%;\n    }\n\n    .row-fluid .span6 {\n        width: 48.61878453038674%;\n        *width: 48.56559304102504%;\n    }\n\n    .row-fluid .span5 {\n        width: 40.05524861878453%;\n        *width: 40.00205712942283%;\n    }\n\n    .row-fluid .span4 {\n        width: 31.491712707182323%;\n        *width: 31.43852121782062%;\n    }\n\n    .row-fluid .span3 {\n        width: 22.92817679558011%;\n        *width: 22.87498530621841%;\n    }\n\n    .row-fluid .span2 {\n        width: 14.3646408839779%;\n        *width: 14.311449394616199%;\n    }\n\n    .row-fluid .span1 {\n        width: 5.801104972375691%;\n        *width: 5.747913483013988%;\n    }\n\n    .row-fluid .offset12 {\n        margin-left: 105.52486187845304%;\n        *margin-left: 105.41847889972962%;\n    }\n\n    .row-fluid .offset12:first-child {\n        margin-left: 102.76243093922652%;\n        *margin-left: 102.6560479605031%;\n    }\n\n    .row-fluid .offset11 {\n        margin-left: 96.96132596685082%;\n        *margin-left: 96.8549429881274%;\n    }\n\n    .row-fluid .offset11:first-child {\n        margin-left: 94.1988950276243%;\n        *margin-left: 94.09251204890089%;\n    }\n\n    .row-fluid .offset10 {\n        margin-left: 88.39779005524862%;\n        *margin-left: 88.2914070765252%;\n    }\n\n    .row-fluid .offset10:first-child {\n        margin-left: 85.6353591160221%;\n        *margin-left: 85.52897613729868%;\n    }\n\n    .row-fluid .offset9 {\n        margin-left: 79.8342541436464%;\n        *margin-left: 79.72787116492299%;\n    }\n\n    .row-fluid .offset9:first-child {\n        margin-left: 77.07182320441989%;\n        *margin-left: 76.96544022569647%;\n    }\n\n    .row-fluid .offset8 {\n        margin-left: 71.2707182320442%;\n        *margin-left: 71.16433525332079%;\n    }\n\n    .row-fluid .offset8:first-child {\n        margin-left: 68.50828729281768%;\n        *margin-left: 68.40190431409427%;\n    }\n\n    .row-fluid .offset7 {\n        margin-left: 62.70718232044199%;\n        *margin-left: 62.600799341718584%;\n    }\n\n    .row-fluid .offset7:first-child {\n        margin-left: 59.94475138121547%;\n        *margin-left: 59.838368402492065%;\n    }\n\n    .row-fluid .offset6 {\n        margin-left: 54.14364640883978%;\n        *margin-left: 54.037263430116376%;\n    }\n\n    .row-fluid .offset6:first-child {\n        margin-left: 51.38121546961326%;\n        *margin-left: 51.27483249088986%;\n    }\n\n    .row-fluid .offset5 {\n        margin-left: 45.58011049723757%;\n        *margin-left: 45.47372751851417%;\n    }\n\n    .row-fluid .offset5:first-child {\n        margin-left: 42.81767955801105%;\n        *margin-left: 42.71129657928765%;\n    }\n\n    .row-fluid .offset4 {\n        margin-left: 37.01657458563536%;\n        *margin-left: 36.91019160691196%;\n    }\n\n    .row-fluid .offset4:first-child {\n        margin-left: 34.25414364640884%;\n        *margin-left: 34.14776066768544%;\n    }\n\n    .row-fluid .offset3 {\n        margin-left: 28.45303867403315%;\n        *margin-left: 28.346655695309746%;\n    }\n\n    .row-fluid .offset3:first-child {\n        margin-left: 25.69060773480663%;\n        *margin-left: 25.584224756083227%;\n    }\n\n    .row-fluid .offset2 {\n        margin-left: 19.88950276243094%;\n        *margin-left: 19.783119783707537%;\n    }\n\n    .row-fluid .offset2:first-child {\n        margin-left: 17.12707182320442%;\n        *margin-left: 17.02068884448102%;\n    }\n\n    .row-fluid .offset1 {\n        margin-left: 11.32596685082873%;\n        *margin-left: 11.219583872105325%;\n    }\n\n    .row-fluid .offset1:first-child {\n        margin-left: 8.56353591160221%;\n        *margin-left: 8.457152932878806%;\n    }\n\n    input,\n    textarea,\n    .uneditable-input {\n        margin-left: 0;\n    }\n\n    .controls-row [class*=\"span\"] + [class*=\"span\"] {\n        margin-left: 20px;\n    }\n\n    input.span12,\n    textarea.span12,\n    .uneditable-input.span12 {\n        width: 710px;\n    }\n\n    input.span11,\n    textarea.span11,\n    .uneditable-input.span11 {\n        width: 648px;\n    }\n\n    input.span10,\n    textarea.span10,\n    .uneditable-input.span10 {\n        width: 586px;\n    }\n\n    input.span9,\n    textarea.span9,\n    .uneditable-input.span9 {\n        width: 524px;\n    }\n\n    input.span8,\n    textarea.span8,\n    .uneditable-input.span8 {\n        width: 462px;\n    }\n\n    input.span7,\n    textarea.span7,\n    .uneditable-input.span7 {\n        width: 400px;\n    }\n\n    input.span6,\n    textarea.span6,\n    .uneditable-input.span6 {\n        width: 338px;\n    }\n\n    input.span5,\n    textarea.span5,\n    .uneditable-input.span5 {\n        width: 276px;\n    }\n\n    input.span4,\n    textarea.span4,\n    .uneditable-input.span4 {\n        width: 214px;\n    }\n\n    input.span3,\n    textarea.span3,\n    .uneditable-input.span3 {\n        width: 152px;\n    }\n\n    input.span2,\n    textarea.span2,\n    .uneditable-input.span2 {\n        width: 90px;\n    }\n\n    input.span1,\n    textarea.span1,\n    .uneditable-input.span1 {\n        width: 28px;\n    }\n}\n\n@media (max-width: 767px) {\n    body {\n        padding-right: 20px;\n        padding-left: 20px;\n    }\n\n    .navbar-fixed-top,\n    .navbar-fixed-bottom,\n    .navbar-static-top {\n        margin-right: -20px;\n        margin-left: -20px;\n    }\n\n    .container-fluid {\n        padding: 0;\n    }\n\n    .dl-horizontal dt {\n        float: none;\n        width: auto;\n        clear: none;\n        text-align: left;\n    }\n\n    .dl-horizontal dd {\n        margin-left: 0;\n    }\n\n    .container {\n        width: auto;\n    }\n\n    .row-fluid {\n        width: 100%;\n    }\n\n    .row,\n    .thumbnails {\n        margin-left: 0;\n    }\n\n    .thumbnails > li {\n        float: none;\n        margin-left: 0;\n    }\n\n    [class*=\"span\"],\n    .uneditable-input[class*=\"span\"],\n    .row-fluid [class*=\"span\"] {\n        display: block;\n        float: none;\n        width: 100%;\n        margin-left: 0;\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n    }\n\n    .span12,\n    .row-fluid .span12 {\n        width: 100%;\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n    }\n\n    .row-fluid [class*=\"offset\"]:first-child {\n        margin-left: 0;\n    }\n\n    .input-large,\n    .input-xlarge,\n    .input-xxlarge,\n    input[class*=\"span\"],\n    select[class*=\"span\"],\n    textarea[class*=\"span\"],\n    .uneditable-input {\n        display: block;\n        width: 100%;\n        min-height: 30px;\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n    }\n\n    .input-prepend input,\n    .input-append input,\n    .input-prepend input[class*=\"span\"],\n    .input-append input[class*=\"span\"] {\n        display: inline-block;\n        width: auto;\n    }\n\n    .controls-row [class*=\"span\"] + [class*=\"span\"] {\n        margin-left: 0;\n    }\n\n    .modal {\n        position: fixed;\n        top: 20px;\n        right: 20px;\n        left: 20px;\n        width: auto;\n        margin: 0;\n    }\n\n    .modal.fade {\n        top: -100px;\n    }\n\n    .modal.fade.in {\n        top: 20px;\n    }\n}\n\n@media (max-width: 480px) {\n    .nav-collapse {\n        -webkit-transform: translate3d(0, 0, 0);\n    }\n\n    .page-header h1 small {\n        display: block;\n        line-height: 20px;\n    }\n\n    input[type=\"checkbox\"],\n    input[type=\"radio\"] {\n        border: 1px solid #ccc;\n    }\n\n    .form-horizontal .control-label {\n        float: none;\n        width: auto;\n        padding-top: 0;\n        text-align: left;\n    }\n\n    .form-horizontal .controls {\n        margin-left: 0;\n    }\n\n    .form-horizontal .control-list {\n        padding-top: 0;\n    }\n\n    .form-horizontal .form-actions {\n        padding-right: 10px;\n        padding-left: 10px;\n    }\n\n    .media .pull-left,\n    .media .pull-right {\n        display: block;\n        float: none;\n        margin-bottom: 10px;\n    }\n\n    .media-object {\n        margin-right: 0;\n        margin-left: 0;\n    }\n\n    .modal {\n        top: 10px;\n        right: 10px;\n        left: 10px;\n    }\n\n    .modal-header .close {\n        padding: 10px;\n        margin: -10px;\n    }\n\n    .carousel-caption {\n        position: static;\n    }\n}\n\n@media (max-width: 979px) {\n    body {\n        padding-top: 0;\n    }\n\n    .navbar-fixed-top,\n    .navbar-fixed-bottom {\n        position: static;\n    }\n\n    .navbar-fixed-top {\n        margin-bottom: 20px;\n    }\n\n    .navbar-fixed-bottom {\n        margin-top: 20px;\n    }\n\n    .navbar-fixed-top .navbar-inner,\n    .navbar-fixed-bottom .navbar-inner {\n        padding: 5px;\n    }\n\n    .navbar .container {\n        width: auto;\n        padding: 0;\n    }\n\n    .navbar .brand {\n        padding-right: 10px;\n        padding-left: 10px;\n        margin: 0 0 0 -5px;\n    }\n\n    .nav-collapse {\n        clear: both;\n    }\n\n    .nav-collapse .nav {\n        float: none;\n        margin: 0 0 10px;\n    }\n\n    .nav-collapse .nav > li {\n        float: none;\n    }\n\n    .nav-collapse .nav > li > a {\n        margin-bottom: 2px;\n    }\n\n    .nav-collapse .nav > .divider-vertical {\n        display: none;\n    }\n\n    .nav-collapse .nav .nav-header {\n        color: #777777;\n        text-shadow: none;\n    }\n\n    .nav-collapse .nav > li > a,\n    .nav-collapse .dropdown-menu a {\n        padding: 9px 15px;\n        font-weight: bold;\n        color: #777777;\n        -webkit-border-radius: 3px;\n        -moz-border-radius: 3px;\n        border-radius: 3px;\n    }\n\n    .nav-collapse .btn {\n        padding: 4px 10px 4px;\n        font-weight: normal;\n        -webkit-border-radius: 4px;\n        -moz-border-radius: 4px;\n        border-radius: 4px;\n    }\n\n    .nav-collapse .dropdown-menu li + li a {\n        margin-bottom: 2px;\n    }\n\n    .nav-collapse .nav > li > a:hover,\n    .nav-collapse .nav > li > a:focus,\n    .nav-collapse .dropdown-menu a:hover,\n    .nav-collapse .dropdown-menu a:focus {\n        background-color: #f2f2f2;\n    }\n\n    .navbar-inverse .nav-collapse .nav > li > a,\n    .navbar-inverse .nav-collapse .dropdown-menu a {\n        color: #999999;\n    }\n\n    .navbar-inverse .nav-collapse .nav > li > a:hover,\n    .navbar-inverse .nav-collapse .nav > li > a:focus,\n    .navbar-inverse .nav-collapse .dropdown-menu a:hover,\n    .navbar-inverse .nav-collapse .dropdown-menu a:focus {\n        background-color: #111111;\n    }\n\n    .nav-collapse.in .btn-group {\n        padding: 0;\n        margin-top: 5px;\n    }\n\n    .nav-collapse .dropdown-menu {\n        position: static;\n        top: auto;\n        left: auto;\n        display: none;\n        float: none;\n        max-width: none;\n        padding: 0;\n        margin: 0 15px;\n        background-color: transparent;\n        border: none;\n        -webkit-border-radius: 0;\n        -moz-border-radius: 0;\n        border-radius: 0;\n        -webkit-box-shadow: none;\n        -moz-box-shadow: none;\n        box-shadow: none;\n    }\n\n    .nav-collapse .open > .dropdown-menu {\n        display: block;\n    }\n\n    .nav-collapse .dropdown-menu:before,\n    .nav-collapse .dropdown-menu:after {\n        display: none;\n    }\n\n    .nav-collapse .dropdown-menu .divider {\n        display: none;\n    }\n\n    .nav-collapse .nav > li > .dropdown-menu:before,\n    .nav-collapse .nav > li > .dropdown-menu:after {\n        display: none;\n    }\n\n    .nav-collapse .navbar-form,\n    .nav-collapse .navbar-search {\n        float: none;\n        padding: 10px 15px;\n        margin: 10px 0;\n        border-top: 1px solid #f2f2f2;\n        border-bottom: 1px solid #f2f2f2;\n        -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n        -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n    }\n\n    .navbar-inverse .nav-collapse .navbar-form,\n    .navbar-inverse .nav-collapse .navbar-search {\n        border-top-color: #111111;\n        border-bottom-color: #111111;\n    }\n\n    .navbar .nav-collapse .nav.pull-right {\n        float: none;\n        margin-left: 0;\n    }\n\n    .nav-collapse,\n    .nav-collapse.collapse {\n        height: 0;\n        overflow: hidden;\n    }\n\n    .navbar .btn-navbar {\n        display: block;\n    }\n\n    .navbar-static .navbar-inner {\n        padding-right: 10px;\n        padding-left: 10px;\n    }\n}\n\n@media (min-width: 980px) {\n    .nav-collapse.collapse {\n        height: auto !important;\n        overflow: visible !important;\n    }\n}\n"
  },
  {
    "path": "public/libs/bootstrap/css/bootstrap.css",
    "content": "/*!\n * Bootstrap v2.3.1\n *\n * Copyright 2012 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n\n.clearfix {\n    *zoom: 1;\n}\n\n.clearfix:before,\n.clearfix:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.clearfix:after {\n    clear: both;\n}\n\n.hide-text {\n    font: 0/0 a;\n    color: transparent;\n    text-shadow: none;\n    background-color: transparent;\n    border: 0;\n}\n\n.input-block-level {\n    display: block;\n    width: 100%;\n    min-height: 30px;\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nnav,\nsection {\n    display: block;\n}\n\naudio,\ncanvas,\nvideo {\n    display: inline-block;\n    *display: inline;\n    *zoom: 1;\n}\n\naudio:not([controls]) {\n    display: none;\n}\n\nhtml {\n    font-size: 100%;\n    -webkit-text-size-adjust: 100%;\n    -ms-text-size-adjust: 100%;\n}\n\na:focus {\n    outline: thin dotted #333;\n    outline: 5px auto -webkit-focus-ring-color;\n    outline-offset: -2px;\n}\n\na:hover,\na:active {\n    outline: 0;\n}\n\nsub,\nsup {\n    position: relative;\n    font-size: 75%;\n    line-height: 0;\n    vertical-align: baseline;\n}\n\nsup {\n    top: -0.5em;\n}\n\nsub {\n    bottom: -0.25em;\n}\n\nimg {\n    width: auto\\9;\n    height: auto;\n    max-width: 100%;\n    vertical-align: middle;\n    border: 0;\n    -ms-interpolation-mode: bicubic;\n}\n\n#map_canvas img,\n.google-maps img {\n    max-width: none;\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n    margin: 0;\n    font-size: 100%;\n    vertical-align: middle;\n}\n\nbutton,\ninput {\n    *overflow: visible;\n    line-height: normal;\n}\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n    padding: 0;\n    border: 0;\n}\n\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n    cursor: pointer;\n    -webkit-appearance: button;\n}\n\nlabel,\nselect,\nbutton,\ninput[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"],\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n    cursor: pointer;\n}\n\ninput[type=\"search\"] {\n    -webkit-box-sizing: content-box;\n    -moz-box-sizing: content-box;\n    box-sizing: content-box;\n    -webkit-appearance: textfield;\n}\n\ninput[type=\"search\"]::-webkit-search-decoration,\ninput[type=\"search\"]::-webkit-search-cancel-button {\n    -webkit-appearance: none;\n}\n\ntextarea {\n    overflow: auto;\n    vertical-align: top;\n}\n\n@media print {\n    * {\n        color: #000 !important;\n        text-shadow: none !important;\n        background: transparent !important;\n        box-shadow: none !important;\n    }\n\n    a,\n    a:visited {\n        text-decoration: underline;\n    }\n\n    a[href]:after {\n        content: \" (\" attr(href) \")\";\n    }\n\n    abbr[title]:after {\n        content: \" (\" attr(title) \")\";\n    }\n\n    .ir a:after,\n    a[href^=\"javascript:\"]:after,\n    a[href^=\"#\"]:after {\n        content: \"\";\n    }\n\n    pre,\n    blockquote {\n        border: 1px solid #999;\n        page-break-inside: avoid;\n    }\n\n    thead {\n        display: table-header-group;\n    }\n\n    tr,\n    img {\n        page-break-inside: avoid;\n    }\n\n    img {\n        max-width: 100% !important;\n    }\n\n    @page {\n        margin: 0.5cm;\n    }\n\n    p,\n    h2,\n    h3 {\n        orphans: 3;\n        widows: 3;\n    }\n\n    h2,\n    h3 {\n        page-break-after: avoid;\n    }\n}\n\nbody {\n    margin: 0;\n    font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n    font-size: 14px;\n    line-height: 20px;\n    color: #333333;\n    background-color: #ffffff;\n}\n\na {\n    color: #0088cc;\n    text-decoration: none;\n}\n\na:hover,\na:focus {\n    color: #005580;\n    text-decoration: underline;\n}\n\n.img-rounded {\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n}\n\n.img-polaroid {\n    padding: 4px;\n    background-color: #fff;\n    border: 1px solid #ccc;\n    border: 1px solid rgba(0, 0, 0, 0.2);\n    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.img-circle {\n    -webkit-border-radius: 500px;\n    -moz-border-radius: 500px;\n    border-radius: 500px;\n}\n\n.row {\n    margin-left: -20px;\n    *zoom: 1;\n}\n\n.row:before,\n.row:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.row:after {\n    clear: both;\n}\n\n[class*=\"span\"] {\n    float: left;\n    min-height: 1px;\n    margin-left: 20px;\n}\n\n.container,\n.navbar-static-top .container,\n.navbar-fixed-top .container,\n.navbar-fixed-bottom .container {\n    width: 940px;\n}\n\n.span12 {\n    width: 940px;\n}\n\n.span11 {\n    width: 860px;\n}\n\n.span10 {\n    width: 780px;\n}\n\n.span9 {\n    width: 700px;\n}\n\n.span8 {\n    width: 620px;\n}\n\n.span7 {\n    width: 540px;\n}\n\n.span6 {\n    width: 460px;\n}\n\n.span5 {\n    width: 380px;\n}\n\n.span4 {\n    width: 300px;\n}\n\n.span3 {\n    width: 220px;\n}\n\n.span2 {\n    width: 140px;\n}\n\n.span1 {\n    width: 60px;\n}\n\n.offset12 {\n    margin-left: 980px;\n}\n\n.offset11 {\n    margin-left: 900px;\n}\n\n.offset10 {\n    margin-left: 820px;\n}\n\n.offset9 {\n    margin-left: 740px;\n}\n\n.offset8 {\n    margin-left: 660px;\n}\n\n.offset7 {\n    margin-left: 580px;\n}\n\n.offset6 {\n    margin-left: 500px;\n}\n\n.offset5 {\n    margin-left: 420px;\n}\n\n.offset4 {\n    margin-left: 340px;\n}\n\n.offset3 {\n    margin-left: 260px;\n}\n\n.offset2 {\n    margin-left: 180px;\n}\n\n.offset1 {\n    margin-left: 100px;\n}\n\n.row-fluid {\n    width: 100%;\n    *zoom: 1;\n}\n\n.row-fluid:before,\n.row-fluid:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.row-fluid:after {\n    clear: both;\n}\n\n.row-fluid [class*=\"span\"] {\n    display: block;\n    float: left;\n    width: 100%;\n    min-height: 30px;\n    margin-left: 2.127659574468085%;\n    *margin-left: 2.074468085106383%;\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n\n.row-fluid [class*=\"span\"]:first-child {\n    margin-left: 0;\n}\n\n.row-fluid .controls-row [class*=\"span\"] + [class*=\"span\"] {\n    margin-left: 2.127659574468085%;\n}\n\n.row-fluid .span12 {\n    width: 100%;\n    *width: 99.94680851063829%;\n}\n\n.row-fluid .span11 {\n    width: 91.48936170212765%;\n    *width: 91.43617021276594%;\n}\n\n.row-fluid .span10 {\n    width: 82.97872340425532%;\n    *width: 82.92553191489361%;\n}\n\n.row-fluid .span9 {\n    width: 74.46808510638297%;\n    *width: 74.41489361702126%;\n}\n\n.row-fluid .span8 {\n    width: 65.95744680851064%;\n    *width: 65.90425531914893%;\n}\n\n.row-fluid .span7 {\n    width: 57.44680851063829%;\n    *width: 57.39361702127659%;\n}\n\n.row-fluid .span6 {\n    width: 48.93617021276595%;\n    *width: 48.88297872340425%;\n}\n\n.row-fluid .span5 {\n    width: 40.42553191489362%;\n    *width: 40.37234042553192%;\n}\n\n.row-fluid .span4 {\n    width: 31.914893617021278%;\n    *width: 31.861702127659576%;\n}\n\n.row-fluid .span3 {\n    width: 23.404255319148934%;\n    *width: 23.351063829787233%;\n}\n\n.row-fluid .span2 {\n    width: 14.893617021276595%;\n    *width: 14.840425531914894%;\n}\n\n.row-fluid .span1 {\n    width: 6.382978723404255%;\n    *width: 6.329787234042553%;\n}\n\n.row-fluid .offset12 {\n    margin-left: 104.25531914893617%;\n    *margin-left: 104.14893617021275%;\n}\n\n.row-fluid .offset12:first-child {\n    margin-left: 102.12765957446808%;\n    *margin-left: 102.02127659574467%;\n}\n\n.row-fluid .offset11 {\n    margin-left: 95.74468085106382%;\n    *margin-left: 95.6382978723404%;\n}\n\n.row-fluid .offset11:first-child {\n    margin-left: 93.61702127659574%;\n    *margin-left: 93.51063829787232%;\n}\n\n.row-fluid .offset10 {\n    margin-left: 87.23404255319149%;\n    *margin-left: 87.12765957446807%;\n}\n\n.row-fluid .offset10:first-child {\n    margin-left: 85.1063829787234%;\n    *margin-left: 84.99999999999999%;\n}\n\n.row-fluid .offset9 {\n    margin-left: 78.72340425531914%;\n    *margin-left: 78.61702127659572%;\n}\n\n.row-fluid .offset9:first-child {\n    margin-left: 76.59574468085106%;\n    *margin-left: 76.48936170212764%;\n}\n\n.row-fluid .offset8 {\n    margin-left: 70.2127659574468%;\n    *margin-left: 70.10638297872339%;\n}\n\n.row-fluid .offset8:first-child {\n    margin-left: 68.08510638297872%;\n    *margin-left: 67.9787234042553%;\n}\n\n.row-fluid .offset7 {\n    margin-left: 61.70212765957446%;\n    *margin-left: 61.59574468085106%;\n}\n\n.row-fluid .offset7:first-child {\n    margin-left: 59.574468085106375%;\n    *margin-left: 59.46808510638297%;\n}\n\n.row-fluid .offset6 {\n    margin-left: 53.191489361702125%;\n    *margin-left: 53.085106382978715%;\n}\n\n.row-fluid .offset6:first-child {\n    margin-left: 51.063829787234035%;\n    *margin-left: 50.95744680851063%;\n}\n\n.row-fluid .offset5 {\n    margin-left: 44.68085106382979%;\n    *margin-left: 44.57446808510638%;\n}\n\n.row-fluid .offset5:first-child {\n    margin-left: 42.5531914893617%;\n    *margin-left: 42.4468085106383%;\n}\n\n.row-fluid .offset4 {\n    margin-left: 36.170212765957444%;\n    *margin-left: 36.06382978723405%;\n}\n\n.row-fluid .offset4:first-child {\n    margin-left: 34.04255319148936%;\n    *margin-left: 33.93617021276596%;\n}\n\n.row-fluid .offset3 {\n    margin-left: 27.659574468085104%;\n    *margin-left: 27.5531914893617%;\n}\n\n.row-fluid .offset3:first-child {\n    margin-left: 25.53191489361702%;\n    *margin-left: 25.425531914893618%;\n}\n\n.row-fluid .offset2 {\n    margin-left: 19.148936170212764%;\n    *margin-left: 19.04255319148936%;\n}\n\n.row-fluid .offset2:first-child {\n    margin-left: 17.02127659574468%;\n    *margin-left: 16.914893617021278%;\n}\n\n.row-fluid .offset1 {\n    margin-left: 10.638297872340425%;\n    *margin-left: 10.53191489361702%;\n}\n\n.row-fluid .offset1:first-child {\n    margin-left: 8.51063829787234%;\n    *margin-left: 8.404255319148938%;\n}\n\n[class*=\"span\"].hide,\n.row-fluid [class*=\"span\"].hide {\n    display: none;\n}\n\n[class*=\"span\"].pull-right,\n.row-fluid [class*=\"span\"].pull-right {\n    float: right;\n}\n\n.container {\n    margin-right: auto;\n    margin-left: auto;\n    *zoom: 1;\n}\n\n.container:before,\n.container:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.container:after {\n    clear: both;\n}\n\n.container-fluid {\n    padding-right: 20px;\n    padding-left: 20px;\n    *zoom: 1;\n}\n\n.container-fluid:before,\n.container-fluid:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.container-fluid:after {\n    clear: both;\n}\n\np {\n    margin: 0 0 10px;\n}\n\n.lead {\n    margin-bottom: 20px;\n    font-size: 21px;\n    font-weight: 200;\n    line-height: 30px;\n}\n\nsmall {\n    font-size: 85%;\n}\n\nstrong {\n    font-weight: bold;\n}\n\nem {\n    font-style: italic;\n}\n\ncite {\n    font-style: normal;\n}\n\n.muted {\n    color: #999999;\n}\n\na.muted:hover,\na.muted:focus {\n    color: #808080;\n}\n\n.text-warning {\n    color: #c09853;\n}\n\na.text-warning:hover,\na.text-warning:focus {\n    color: #a47e3c;\n}\n\n.text-error {\n    color: #b94a48;\n}\n\na.text-error:hover,\na.text-error:focus {\n    color: #953b39;\n}\n\n.text-info {\n    color: #3a87ad;\n}\n\na.text-info:hover,\na.text-info:focus {\n    color: #2d6987;\n}\n\n.text-success {\n    color: #468847;\n}\n\na.text-success:hover,\na.text-success:focus {\n    color: #356635;\n}\n\n.text-left {\n    text-align: left;\n}\n\n.text-right {\n    text-align: right;\n}\n\n.text-center {\n    text-align: center;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin: 10px 0;\n    font-family: inherit;\n    font-weight: bold;\n    line-height: 20px;\n    color: inherit;\n    text-rendering: optimizelegibility;\n}\n\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small {\n    font-weight: normal;\n    line-height: 1;\n    color: #999999;\n}\n\nh1,\nh2,\nh3 {\n    line-height: 40px;\n}\n\nh1 {\n    font-size: 38.5px;\n}\n\nh2 {\n    font-size: 31.5px;\n}\n\nh3 {\n    font-size: 24.5px;\n}\n\nh4 {\n    font-size: 17.5px;\n}\n\nh5 {\n    font-size: 14px;\n}\n\nh6 {\n    font-size: 11.9px;\n}\n\nh1 small {\n    font-size: 24.5px;\n}\n\nh2 small {\n    font-size: 17.5px;\n}\n\nh3 small {\n    font-size: 14px;\n}\n\nh4 small {\n    font-size: 14px;\n}\n\n.page-header {\n    padding-bottom: 9px;\n    margin: 20px 0 30px;\n    border-bottom: 1px solid #eeeeee;\n}\n\nul,\nol {\n    padding: 0;\n    margin: 0 0 10px 25px;\n}\n\nul ul,\nul ol,\nol ol,\nol ul {\n    margin-bottom: 0;\n}\n\nli {\n    line-height: 20px;\n}\n\nul.unstyled,\nol.unstyled {\n    margin-left: 0;\n    list-style: none;\n}\n\nul.inline,\nol.inline {\n    margin-left: 0;\n    list-style: none;\n}\n\nul.inline > li,\nol.inline > li {\n    display: inline-block;\n    *display: inline;\n    padding-right: 5px;\n    padding-left: 5px;\n    *zoom: 1;\n}\n\ndl {\n    margin-bottom: 20px;\n}\n\ndt,\ndd {\n    line-height: 20px;\n}\n\ndt {\n    font-weight: bold;\n}\n\ndd {\n    margin-left: 10px;\n}\n\n.dl-horizontal {\n    *zoom: 1;\n}\n\n.dl-horizontal:before,\n.dl-horizontal:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.dl-horizontal:after {\n    clear: both;\n}\n\n.dl-horizontal dt {\n    float: left;\n    width: 160px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n}\n\n.dl-horizontal dd {\n    margin-left: 180px;\n}\n\nhr {\n    margin: 20px 0;\n    border: 0;\n    border-top: 1px solid #eeeeee;\n    border-bottom: 1px solid #ffffff;\n}\n\nabbr[title],\nabbr[data-original-title] {\n    cursor: help;\n    border-bottom: 1px dotted #999999;\n}\n\nabbr.initialism {\n    font-size: 90%;\n    text-transform: uppercase;\n}\n\nblockquote {\n    padding: 0 0 0 15px;\n    margin: 0 0 20px;\n    border-left: 5px solid #eeeeee;\n}\n\nblockquote p {\n    margin-bottom: 0;\n    font-size: 17.5px;\n    font-weight: 300;\n    line-height: 1.25;\n}\n\nblockquote small {\n    display: block;\n    line-height: 20px;\n    color: #999999;\n}\n\nblockquote small:before {\n    content: '\\2014 \\00A0';\n}\n\nblockquote.pull-right {\n    float: right;\n    padding-right: 15px;\n    padding-left: 0;\n    border-right: 5px solid #eeeeee;\n    border-left: 0;\n}\n\nblockquote.pull-right p,\nblockquote.pull-right small {\n    text-align: right;\n}\n\nblockquote.pull-right small:before {\n    content: '';\n}\n\nblockquote.pull-right small:after {\n    content: '\\00A0 \\2014';\n}\n\nq:before,\nq:after,\nblockquote:before,\nblockquote:after {\n    content: \"\";\n}\n\naddress {\n    display: block;\n    margin-bottom: 20px;\n    font-style: normal;\n    line-height: 20px;\n}\n\ncode,\npre {\n    padding: 0 3px 2px;\n    font-family: Monaco, Menlo, Consolas, \"Courier New\", monospace;\n    font-size: 12px;\n    color: #333333;\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    border-radius: 3px;\n}\n\ncode {\n    padding: 2px 4px;\n    color: #d14;\n    white-space: nowrap;\n    background-color: #f7f7f9;\n    border: 1px solid #e1e1e8;\n}\n\npre {\n    display: block;\n    padding: 9.5px;\n    margin: 0 0 10px;\n    font-size: 13px;\n    line-height: 20px;\n    word-break: break-all;\n    word-wrap: break-word;\n    white-space: pre;\n    white-space: pre-wrap;\n    background-color: #f5f5f5;\n    border: 1px solid #ccc;\n    border: 1px solid rgba(0, 0, 0, 0.15);\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\npre.prettyprint {\n    margin-bottom: 20px;\n}\n\npre code {\n    padding: 0;\n    color: inherit;\n    white-space: pre;\n    white-space: pre-wrap;\n    background-color: transparent;\n    border: 0;\n}\n\n.pre-scrollable {\n    max-height: 340px;\n    overflow-y: scroll;\n}\n\nform {\n    margin: 0 0 20px;\n}\n\nfieldset {\n    padding: 0;\n    margin: 0;\n    border: 0;\n}\n\nlegend {\n    display: block;\n    width: 100%;\n    padding: 0;\n    margin-bottom: 20px;\n    font-size: 21px;\n    line-height: 40px;\n    color: #333333;\n    border: 0;\n    border-bottom: 1px solid #e5e5e5;\n}\n\nlegend small {\n    font-size: 15px;\n    color: #999999;\n}\n\nlabel,\ninput,\nbutton,\nselect,\ntextarea {\n    font-size: 14px;\n    font-weight: normal;\n    line-height: 20px;\n}\n\ninput,\nbutton,\nselect,\ntextarea {\n    font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n}\n\nlabel {\n    display: block;\n    margin-bottom: 5px;\n}\n\nselect,\ntextarea,\ninput[type=\"text\"],\ninput[type=\"password\"],\ninput[type=\"datetime\"],\ninput[type=\"datetime-local\"],\ninput[type=\"date\"],\ninput[type=\"month\"],\ninput[type=\"time\"],\ninput[type=\"week\"],\ninput[type=\"number\"],\ninput[type=\"email\"],\ninput[type=\"url\"],\ninput[type=\"search\"],\ninput[type=\"tel\"],\ninput[type=\"color\"],\n.uneditable-input {\n    display: inline-block;\n    height: 20px;\n    padding: 4px 6px;\n    margin-bottom: 10px;\n    font-size: 14px;\n    line-height: 20px;\n    color: #555555;\n    vertical-align: middle;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\ninput,\ntextarea,\n.uneditable-input {\n    width: 206px;\n}\n\ntextarea {\n    height: auto;\n}\n\ntextarea,\ninput[type=\"text\"],\ninput[type=\"password\"],\ninput[type=\"datetime\"],\ninput[type=\"datetime-local\"],\ninput[type=\"date\"],\ninput[type=\"month\"],\ninput[type=\"time\"],\ninput[type=\"week\"],\ninput[type=\"number\"],\ninput[type=\"email\"],\ninput[type=\"url\"],\ninput[type=\"search\"],\ninput[type=\"tel\"],\ninput[type=\"color\"],\n.uneditable-input {\n    background-color: #ffffff;\n    border: 1px solid #cccccc;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;\n    -moz-transition: border linear 0.2s, box-shadow linear 0.2s;\n    -o-transition: border linear 0.2s, box-shadow linear 0.2s;\n    transition: border linear 0.2s, box-shadow linear 0.2s;\n}\n\ntextarea:focus,\ninput[type=\"text\"]:focus,\ninput[type=\"password\"]:focus,\ninput[type=\"datetime\"]:focus,\ninput[type=\"datetime-local\"]:focus,\ninput[type=\"date\"]:focus,\ninput[type=\"month\"]:focus,\ninput[type=\"time\"]:focus,\ninput[type=\"week\"]:focus,\ninput[type=\"number\"]:focus,\ninput[type=\"email\"]:focus,\ninput[type=\"url\"]:focus,\ninput[type=\"search\"]:focus,\ninput[type=\"tel\"]:focus,\ninput[type=\"color\"]:focus,\n.uneditable-input:focus {\n    border-color: rgba(82, 168, 236, 0.8);\n    outline: 0;\n    outline: thin dotted \\9;\n    /* IE6-9 */\n\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n    margin: 4px 0 0;\n    margin-top: 1px \\9;\n    *margin-top: 0;\n    line-height: normal;\n}\n\ninput[type=\"file\"],\ninput[type=\"image\"],\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"],\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n    width: auto;\n}\n\nselect,\ninput[type=\"file\"] {\n    height: 30px;\n    /* In IE7, the height of the select element cannot be changed by height, only font-size */\n\n    *margin-top: 4px;\n    /* For IE7, add top margin to align select with labels */\n\n    line-height: 30px;\n}\n\nselect {\n    width: 220px;\n    background-color: #ffffff;\n    border: 1px solid #cccccc;\n}\n\nselect[multiple],\nselect[size] {\n    height: auto;\n}\n\nselect:focus,\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n    outline: thin dotted #333;\n    outline: 5px auto -webkit-focus-ring-color;\n    outline-offset: -2px;\n}\n\n.uneditable-input,\n.uneditable-textarea {\n    color: #999999;\n    cursor: not-allowed;\n    background-color: #fcfcfc;\n    border-color: #cccccc;\n    -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n    -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);\n}\n\n.uneditable-input {\n    overflow: hidden;\n    white-space: nowrap;\n}\n\n.uneditable-textarea {\n    width: auto;\n    height: auto;\n}\n\ninput:-moz-placeholder,\ntextarea:-moz-placeholder {\n    color: #999999;\n}\n\ninput:-ms-input-placeholder,\ntextarea:-ms-input-placeholder {\n    color: #999999;\n}\n\ninput::-webkit-input-placeholder,\ntextarea::-webkit-input-placeholder {\n    color: #999999;\n}\n\n.radio,\n.checkbox {\n    min-height: 20px;\n    padding-left: 20px;\n}\n\n.radio input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"] {\n    float: left;\n    margin-left: -20px;\n}\n\n.controls > .radio:first-child,\n.controls > .checkbox:first-child {\n    padding-top: 5px;\n}\n\n.radio.inline,\n.checkbox.inline {\n    display: inline-block;\n    padding-top: 5px;\n    margin-bottom: 0;\n    vertical-align: middle;\n}\n\n.radio.inline + .radio.inline,\n.checkbox.inline + .checkbox.inline {\n    margin-left: 10px;\n}\n\n.input-mini {\n    width: 60px;\n}\n\n.input-small {\n    width: 90px;\n}\n\n.input-medium {\n    width: 150px;\n}\n\n.input-large {\n    width: 210px;\n}\n\n.input-xlarge {\n    width: 270px;\n}\n\n.input-xxlarge {\n    width: 530px;\n}\n\ninput[class*=\"span\"],\nselect[class*=\"span\"],\ntextarea[class*=\"span\"],\n.uneditable-input[class*=\"span\"],\n.row-fluid input[class*=\"span\"],\n.row-fluid select[class*=\"span\"],\n.row-fluid textarea[class*=\"span\"],\n.row-fluid .uneditable-input[class*=\"span\"] {\n    float: none;\n    margin-left: 0;\n}\n\n.input-append input[class*=\"span\"],\n.input-append .uneditable-input[class*=\"span\"],\n.input-prepend input[class*=\"span\"],\n.input-prepend .uneditable-input[class*=\"span\"],\n.row-fluid input[class*=\"span\"],\n.row-fluid select[class*=\"span\"],\n.row-fluid textarea[class*=\"span\"],\n.row-fluid .uneditable-input[class*=\"span\"],\n.row-fluid .input-prepend [class*=\"span\"],\n.row-fluid .input-append [class*=\"span\"] {\n    display: inline-block;\n}\n\ninput,\ntextarea,\n.uneditable-input {\n    margin-left: 0;\n}\n\n.controls-row [class*=\"span\"] + [class*=\"span\"] {\n    margin-left: 20px;\n}\n\ninput.span12,\ntextarea.span12,\n.uneditable-input.span12 {\n    width: 926px;\n}\n\ninput.span11,\ntextarea.span11,\n.uneditable-input.span11 {\n    width: 846px;\n}\n\ninput.span10,\ntextarea.span10,\n.uneditable-input.span10 {\n    width: 766px;\n}\n\ninput.span9,\ntextarea.span9,\n.uneditable-input.span9 {\n    width: 686px;\n}\n\ninput.span8,\ntextarea.span8,\n.uneditable-input.span8 {\n    width: 606px;\n}\n\ninput.span7,\ntextarea.span7,\n.uneditable-input.span7 {\n    width: 526px;\n}\n\ninput.span6,\ntextarea.span6,\n.uneditable-input.span6 {\n    width: 446px;\n}\n\ninput.span5,\ntextarea.span5,\n.uneditable-input.span5 {\n    width: 366px;\n}\n\ninput.span4,\ntextarea.span4,\n.uneditable-input.span4 {\n    width: 286px;\n}\n\ninput.span3,\ntextarea.span3,\n.uneditable-input.span3 {\n    width: 206px;\n}\n\ninput.span2,\ntextarea.span2,\n.uneditable-input.span2 {\n    width: 126px;\n}\n\ninput.span1,\ntextarea.span1,\n.uneditable-input.span1 {\n    width: 46px;\n}\n\n.controls-row {\n    *zoom: 1;\n}\n\n.controls-row:before,\n.controls-row:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.controls-row:after {\n    clear: both;\n}\n\n.controls-row [class*=\"span\"],\n.row-fluid .controls-row [class*=\"span\"] {\n    float: left;\n}\n\n.controls-row .checkbox[class*=\"span\"],\n.controls-row .radio[class*=\"span\"] {\n    padding-top: 5px;\n}\n\ninput[disabled],\nselect[disabled],\ntextarea[disabled],\ninput[readonly],\nselect[readonly],\ntextarea[readonly] {\n    cursor: not-allowed;\n    background-color: #eeeeee;\n}\n\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"][readonly],\ninput[type=\"checkbox\"][readonly] {\n    background-color: transparent;\n}\n\n.control-group.warning .control-label,\n.control-group.warning .help-block,\n.control-group.warning .help-inline {\n    color: #c09853;\n}\n\n.control-group.warning .checkbox,\n.control-group.warning .radio,\n.control-group.warning input,\n.control-group.warning select,\n.control-group.warning textarea {\n    color: #c09853;\n}\n\n.control-group.warning input,\n.control-group.warning select,\n.control-group.warning textarea {\n    border-color: #c09853;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n\n.control-group.warning input:focus,\n.control-group.warning select:focus,\n.control-group.warning textarea:focus {\n    border-color: #a47e3c;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;\n}\n\n.control-group.warning .input-prepend .add-on,\n.control-group.warning .input-append .add-on {\n    color: #c09853;\n    background-color: #fcf8e3;\n    border-color: #c09853;\n}\n\n.control-group.error .control-label,\n.control-group.error .help-block,\n.control-group.error .help-inline {\n    color: #b94a48;\n}\n\n.control-group.error .checkbox,\n.control-group.error .radio,\n.control-group.error input,\n.control-group.error select,\n.control-group.error textarea {\n    color: #b94a48;\n}\n\n.control-group.error input,\n.control-group.error select,\n.control-group.error textarea {\n    border-color: #b94a48;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n\n.control-group.error input:focus,\n.control-group.error select:focus,\n.control-group.error textarea:focus {\n    border-color: #953b39;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;\n}\n\n.control-group.error .input-prepend .add-on,\n.control-group.error .input-append .add-on {\n    color: #b94a48;\n    background-color: #f2dede;\n    border-color: #b94a48;\n}\n\n.control-group.success .control-label,\n.control-group.success .help-block,\n.control-group.success .help-inline {\n    color: #468847;\n}\n\n.control-group.success .checkbox,\n.control-group.success .radio,\n.control-group.success input,\n.control-group.success select,\n.control-group.success textarea {\n    color: #468847;\n}\n\n.control-group.success input,\n.control-group.success select,\n.control-group.success textarea {\n    border-color: #468847;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n\n.control-group.success input:focus,\n.control-group.success select:focus,\n.control-group.success textarea:focus {\n    border-color: #356635;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;\n}\n\n.control-group.success .input-prepend .add-on,\n.control-group.success .input-append .add-on {\n    color: #468847;\n    background-color: #dff0d8;\n    border-color: #468847;\n}\n\n.control-group.info .control-label,\n.control-group.info .help-block,\n.control-group.info .help-inline {\n    color: #3a87ad;\n}\n\n.control-group.info .checkbox,\n.control-group.info .radio,\n.control-group.info input,\n.control-group.info select,\n.control-group.info textarea {\n    color: #3a87ad;\n}\n\n.control-group.info input,\n.control-group.info select,\n.control-group.info textarea {\n    border-color: #3a87ad;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n\n.control-group.info input:focus,\n.control-group.info select:focus,\n.control-group.info textarea:focus {\n    border-color: #2d6987;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;\n}\n\n.control-group.info .input-prepend .add-on,\n.control-group.info .input-append .add-on {\n    color: #3a87ad;\n    background-color: #d9edf7;\n    border-color: #3a87ad;\n}\n\ninput:focus:invalid,\ntextarea:focus:invalid,\nselect:focus:invalid {\n    color: #b94a48;\n    border-color: #ee5f5b;\n}\n\ninput:focus:invalid:focus,\ntextarea:focus:invalid:focus,\nselect:focus:invalid:focus {\n    border-color: #e9322d;\n    -webkit-box-shadow: 0 0 6px #f8b9b7;\n    -moz-box-shadow: 0 0 6px #f8b9b7;\n    box-shadow: 0 0 6px #f8b9b7;\n}\n\n.form-actions {\n    padding: 19px 20px 20px;\n    margin-top: 20px;\n    margin-bottom: 20px;\n    background-color: #f5f5f5;\n    border-top: 1px solid #e5e5e5;\n    *zoom: 1;\n}\n\n.form-actions:before,\n.form-actions:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.form-actions:after {\n    clear: both;\n}\n\n.help-block,\n.help-inline {\n    color: #595959;\n}\n\n.help-block {\n    display: block;\n    margin-bottom: 10px;\n}\n\n.help-inline {\n    display: inline-block;\n    *display: inline;\n    padding-left: 5px;\n    vertical-align: middle;\n    *zoom: 1;\n}\n\n.input-append,\n.input-prepend {\n    display: inline-block;\n    margin-bottom: 10px;\n    font-size: 0;\n    white-space: nowrap;\n    vertical-align: middle;\n}\n\n.input-append input,\n.input-prepend input,\n.input-append select,\n.input-prepend select,\n.input-append .uneditable-input,\n.input-prepend .uneditable-input,\n.input-append .dropdown-menu,\n.input-prepend .dropdown-menu,\n.input-append .popover,\n.input-prepend .popover {\n    font-size: 14px;\n}\n\n.input-append input,\n.input-prepend input,\n.input-append select,\n.input-prepend select,\n.input-append .uneditable-input,\n.input-prepend .uneditable-input {\n    position: relative;\n    margin-bottom: 0;\n    *margin-left: 0;\n    vertical-align: top;\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.input-append input:focus,\n.input-prepend input:focus,\n.input-append select:focus,\n.input-prepend select:focus,\n.input-append .uneditable-input:focus,\n.input-prepend .uneditable-input:focus {\n    z-index: 2;\n}\n\n.input-append .add-on,\n.input-prepend .add-on {\n    display: inline-block;\n    width: auto;\n    height: 20px;\n    min-width: 16px;\n    padding: 4px 5px;\n    font-size: 14px;\n    font-weight: normal;\n    line-height: 20px;\n    text-align: center;\n    text-shadow: 0 1px 0 #ffffff;\n    background-color: #eeeeee;\n    border: 1px solid #ccc;\n}\n\n.input-append .add-on,\n.input-prepend .add-on,\n.input-append .btn,\n.input-prepend .btn,\n.input-append .btn-group > .dropdown-toggle,\n.input-prepend .btn-group > .dropdown-toggle {\n    vertical-align: top;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.input-append .active,\n.input-prepend .active {\n    background-color: #a9dba9;\n    border-color: #46a546;\n}\n\n.input-prepend .add-on,\n.input-prepend .btn {\n    margin-right: -1px;\n}\n\n.input-prepend .add-on:first-child,\n.input-prepend .btn:first-child {\n    -webkit-border-radius: 4px 0 0 4px;\n    -moz-border-radius: 4px 0 0 4px;\n    border-radius: 4px 0 0 4px;\n}\n\n.input-append input,\n.input-append select,\n.input-append .uneditable-input {\n    -webkit-border-radius: 4px 0 0 4px;\n    -moz-border-radius: 4px 0 0 4px;\n    border-radius: 4px 0 0 4px;\n}\n\n.input-append input + .btn-group .btn:last-child,\n.input-append select + .btn-group .btn:last-child,\n.input-append .uneditable-input + .btn-group .btn:last-child {\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.input-append .add-on,\n.input-append .btn,\n.input-append .btn-group {\n    margin-left: -1px;\n}\n\n.input-append .add-on:last-child,\n.input-append .btn:last-child,\n.input-append .btn-group:last-child > .dropdown-toggle {\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.input-prepend.input-append input,\n.input-prepend.input-append select,\n.input-prepend.input-append .uneditable-input {\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.input-prepend.input-append input + .btn-group .btn,\n.input-prepend.input-append select + .btn-group .btn,\n.input-prepend.input-append .uneditable-input + .btn-group .btn {\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.input-prepend.input-append .add-on:first-child,\n.input-prepend.input-append .btn:first-child {\n    margin-right: -1px;\n    -webkit-border-radius: 4px 0 0 4px;\n    -moz-border-radius: 4px 0 0 4px;\n    border-radius: 4px 0 0 4px;\n}\n\n.input-prepend.input-append .add-on:last-child,\n.input-prepend.input-append .btn:last-child {\n    margin-left: -1px;\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.input-prepend.input-append .btn-group:first-child {\n    margin-left: 0;\n}\n\ninput.search-query {\n    padding-right: 14px;\n    padding-right: 4px \\9;\n    padding-left: 14px;\n    padding-left: 4px \\9;\n    /* IE7-8 doesn't have border-radius, so don't indent the padding */\n\n    margin-bottom: 0;\n    -webkit-border-radius: 15px;\n    -moz-border-radius: 15px;\n    border-radius: 15px;\n}\n\n/* Allow for input prepend/append in search forms */\n\n.form-search .input-append .search-query,\n.form-search .input-prepend .search-query {\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.form-search .input-append .search-query {\n    -webkit-border-radius: 14px 0 0 14px;\n    -moz-border-radius: 14px 0 0 14px;\n    border-radius: 14px 0 0 14px;\n}\n\n.form-search .input-append .btn {\n    -webkit-border-radius: 0 14px 14px 0;\n    -moz-border-radius: 0 14px 14px 0;\n    border-radius: 0 14px 14px 0;\n}\n\n.form-search .input-prepend .search-query {\n    -webkit-border-radius: 0 14px 14px 0;\n    -moz-border-radius: 0 14px 14px 0;\n    border-radius: 0 14px 14px 0;\n}\n\n.form-search .input-prepend .btn {\n    -webkit-border-radius: 14px 0 0 14px;\n    -moz-border-radius: 14px 0 0 14px;\n    border-radius: 14px 0 0 14px;\n}\n\n.form-search input,\n.form-inline input,\n.form-horizontal input,\n.form-search textarea,\n.form-inline textarea,\n.form-horizontal textarea,\n.form-search select,\n.form-inline select,\n.form-horizontal select,\n.form-search .help-inline,\n.form-inline .help-inline,\n.form-horizontal .help-inline,\n.form-search .uneditable-input,\n.form-inline .uneditable-input,\n.form-horizontal .uneditable-input,\n.form-search .input-prepend,\n.form-inline .input-prepend,\n.form-horizontal .input-prepend,\n.form-search .input-append,\n.form-inline .input-append,\n.form-horizontal .input-append {\n    display: inline-block;\n    *display: inline;\n    margin-bottom: 0;\n    vertical-align: middle;\n    *zoom: 1;\n}\n\n.form-search .hide,\n.form-inline .hide,\n.form-horizontal .hide {\n    display: none;\n}\n\n.form-search label,\n.form-inline label,\n.form-search .btn-group,\n.form-inline .btn-group {\n    display: inline-block;\n}\n\n.form-search .input-append,\n.form-inline .input-append,\n.form-search .input-prepend,\n.form-inline .input-prepend {\n    margin-bottom: 0;\n}\n\n.form-search .radio,\n.form-search .checkbox,\n.form-inline .radio,\n.form-inline .checkbox {\n    padding-left: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n}\n\n.form-search .radio input[type=\"radio\"],\n.form-search .checkbox input[type=\"checkbox\"],\n.form-inline .radio input[type=\"radio\"],\n.form-inline .checkbox input[type=\"checkbox\"] {\n    float: left;\n    margin-right: 3px;\n    margin-left: 0;\n}\n\n.control-group {\n    margin-bottom: 10px;\n}\n\nlegend + .control-group {\n    margin-top: 20px;\n    -webkit-margin-top-collapse: separate;\n}\n\n.form-horizontal .control-group {\n    margin-bottom: 20px;\n    *zoom: 1;\n}\n\n.form-horizontal .control-group:before,\n.form-horizontal .control-group:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.form-horizontal .control-group:after {\n    clear: both;\n}\n\n.form-horizontal .control-label {\n    float: left;\n    width: 160px;\n    padding-top: 5px;\n    text-align: right;\n}\n\n.form-horizontal .controls {\n    *display: inline-block;\n    *padding-left: 20px;\n    margin-left: 180px;\n    *margin-left: 0;\n}\n\n.form-horizontal .controls:first-child {\n    *padding-left: 180px;\n}\n\n.form-horizontal .help-block {\n    margin-bottom: 0;\n}\n\n.form-horizontal input + .help-block,\n.form-horizontal select + .help-block,\n.form-horizontal textarea + .help-block,\n.form-horizontal .uneditable-input + .help-block,\n.form-horizontal .input-prepend + .help-block,\n.form-horizontal .input-append + .help-block {\n    margin-top: 10px;\n}\n\n.form-horizontal .form-actions {\n    padding-left: 180px;\n}\n\ntable {\n    max-width: 100%;\n    background-color: transparent;\n    border-collapse: collapse;\n    border-spacing: 0;\n}\n\n.table {\n    width: 100%;\n    margin-bottom: 20px;\n}\n\n.table th,\n.table td {\n    padding: 8px;\n    line-height: 20px;\n    text-align: left;\n    vertical-align: top;\n    border-top: 1px solid #dddddd;\n}\n\n.table th {\n    font-weight: bold;\n}\n\n.table thead th {\n    vertical-align: bottom;\n}\n\n.table caption + thead tr:first-child th,\n.table caption + thead tr:first-child td,\n.table colgroup + thead tr:first-child th,\n.table colgroup + thead tr:first-child td,\n.table thead:first-child tr:first-child th,\n.table thead:first-child tr:first-child td {\n    border-top: 0;\n}\n\n.table tbody + tbody {\n    border-top: 2px solid #dddddd;\n}\n\n.table .table {\n    background-color: #ffffff;\n}\n\n.table-condensed th,\n.table-condensed td {\n    padding: 4px 5px;\n}\n\n.table-bordered {\n    border: 1px solid #dddddd;\n    border-collapse: separate;\n    *border-collapse: collapse;\n    border-left: 0;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.table-bordered th,\n.table-bordered td {\n    border-left: 1px solid #dddddd;\n}\n\n.table-bordered caption + thead tr:first-child th,\n.table-bordered caption + tbody tr:first-child th,\n.table-bordered caption + tbody tr:first-child td,\n.table-bordered colgroup + thead tr:first-child th,\n.table-bordered colgroup + tbody tr:first-child th,\n.table-bordered colgroup + tbody tr:first-child td,\n.table-bordered thead:first-child tr:first-child th,\n.table-bordered tbody:first-child tr:first-child th,\n.table-bordered tbody:first-child tr:first-child td {\n    border-top: 0;\n}\n\n.table-bordered thead:first-child tr:first-child > th:first-child,\n.table-bordered tbody:first-child tr:first-child > td:first-child,\n.table-bordered tbody:first-child tr:first-child > th:first-child {\n    -webkit-border-top-left-radius: 4px;\n    border-top-left-radius: 4px;\n    -moz-border-radius-topleft: 4px;\n}\n\n.table-bordered thead:first-child tr:first-child > th:last-child,\n.table-bordered tbody:first-child tr:first-child > td:last-child,\n.table-bordered tbody:first-child tr:first-child > th:last-child {\n    -webkit-border-top-right-radius: 4px;\n    border-top-right-radius: 4px;\n    -moz-border-radius-topright: 4px;\n}\n\n.table-bordered thead:last-child tr:last-child > th:first-child,\n.table-bordered tbody:last-child tr:last-child > td:first-child,\n.table-bordered tbody:last-child tr:last-child > th:first-child,\n.table-bordered tfoot:last-child tr:last-child > td:first-child,\n.table-bordered tfoot:last-child tr:last-child > th:first-child {\n    -webkit-border-bottom-left-radius: 4px;\n    border-bottom-left-radius: 4px;\n    -moz-border-radius-bottomleft: 4px;\n}\n\n.table-bordered thead:last-child tr:last-child > th:last-child,\n.table-bordered tbody:last-child tr:last-child > td:last-child,\n.table-bordered tbody:last-child tr:last-child > th:last-child,\n.table-bordered tfoot:last-child tr:last-child > td:last-child,\n.table-bordered tfoot:last-child tr:last-child > th:last-child {\n    -webkit-border-bottom-right-radius: 4px;\n    border-bottom-right-radius: 4px;\n    -moz-border-radius-bottomright: 4px;\n}\n\n.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {\n    -webkit-border-bottom-left-radius: 0;\n    border-bottom-left-radius: 0;\n    -moz-border-radius-bottomleft: 0;\n}\n\n.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {\n    -webkit-border-bottom-right-radius: 0;\n    border-bottom-right-radius: 0;\n    -moz-border-radius-bottomright: 0;\n}\n\n.table-bordered caption + thead tr:first-child th:first-child,\n.table-bordered caption + tbody tr:first-child td:first-child,\n.table-bordered colgroup + thead tr:first-child th:first-child,\n.table-bordered colgroup + tbody tr:first-child td:first-child {\n    -webkit-border-top-left-radius: 4px;\n    border-top-left-radius: 4px;\n    -moz-border-radius-topleft: 4px;\n}\n\n.table-bordered caption + thead tr:first-child th:last-child,\n.table-bordered caption + tbody tr:first-child td:last-child,\n.table-bordered colgroup + thead tr:first-child th:last-child,\n.table-bordered colgroup + tbody tr:first-child td:last-child {\n    -webkit-border-top-right-radius: 4px;\n    border-top-right-radius: 4px;\n    -moz-border-radius-topright: 4px;\n}\n\n.table-striped tbody > tr:nth-child(odd) > td,\n.table-striped tbody > tr:nth-child(odd) > th {\n    background-color: #f9f9f9;\n}\n\n.table-hover tbody tr:hover > td,\n.table-hover tbody tr:hover > th {\n    background-color: #f5f5f5;\n}\n\ntable td[class*=\"span\"],\ntable th[class*=\"span\"],\n.row-fluid table td[class*=\"span\"],\n.row-fluid table th[class*=\"span\"] {\n    display: table-cell;\n    float: none;\n    margin-left: 0;\n}\n\n.table td.span1,\n.table th.span1 {\n    float: none;\n    width: 44px;\n    margin-left: 0;\n}\n\n.table td.span2,\n.table th.span2 {\n    float: none;\n    width: 124px;\n    margin-left: 0;\n}\n\n.table td.span3,\n.table th.span3 {\n    float: none;\n    width: 204px;\n    margin-left: 0;\n}\n\n.table td.span4,\n.table th.span4 {\n    float: none;\n    width: 284px;\n    margin-left: 0;\n}\n\n.table td.span5,\n.table th.span5 {\n    float: none;\n    width: 364px;\n    margin-left: 0;\n}\n\n.table td.span6,\n.table th.span6 {\n    float: none;\n    width: 444px;\n    margin-left: 0;\n}\n\n.table td.span7,\n.table th.span7 {\n    float: none;\n    width: 524px;\n    margin-left: 0;\n}\n\n.table td.span8,\n.table th.span8 {\n    float: none;\n    width: 604px;\n    margin-left: 0;\n}\n\n.table td.span9,\n.table th.span9 {\n    float: none;\n    width: 684px;\n    margin-left: 0;\n}\n\n.table td.span10,\n.table th.span10 {\n    float: none;\n    width: 764px;\n    margin-left: 0;\n}\n\n.table td.span11,\n.table th.span11 {\n    float: none;\n    width: 844px;\n    margin-left: 0;\n}\n\n.table td.span12,\n.table th.span12 {\n    float: none;\n    width: 924px;\n    margin-left: 0;\n}\n\n.table tbody tr.success > td {\n    background-color: #dff0d8;\n}\n\n.table tbody tr.error > td {\n    background-color: #f2dede;\n}\n\n.table tbody tr.warning > td {\n    background-color: #fcf8e3;\n}\n\n.table tbody tr.info > td {\n    background-color: #d9edf7;\n}\n\n.table-hover tbody tr.success:hover > td {\n    background-color: #d0e9c6;\n}\n\n.table-hover tbody tr.error:hover > td {\n    background-color: #ebcccc;\n}\n\n.table-hover tbody tr.warning:hover > td {\n    background-color: #faf2cc;\n}\n\n.table-hover tbody tr.info:hover > td {\n    background-color: #c4e3f3;\n}\n\n[class^=\"icon-\"],\n[class*=\" icon-\"] {\n    display: inline-block;\n    width: 14px;\n    height: 14px;\n    margin-top: 1px;\n    *margin-right: .3em;\n    line-height: 14px;\n    vertical-align: text-top;\n    background-image: url(\"../img/glyphicons-halflings.png\");\n    background-position: 14px 14px;\n    background-repeat: no-repeat;\n}\n\n/* White icons with optional class, or on hover/focus/active states of certain elements */\n\n.icon-white,\n.nav-pills > .active > a > [class^=\"icon-\"],\n.nav-pills > .active > a > [class*=\" icon-\"],\n.nav-list > .active > a > [class^=\"icon-\"],\n.nav-list > .active > a > [class*=\" icon-\"],\n.navbar-inverse .nav > .active > a > [class^=\"icon-\"],\n.navbar-inverse .nav > .active > a > [class*=\" icon-\"],\n.dropdown-menu > li > a:hover > [class^=\"icon-\"],\n.dropdown-menu > li > a:focus > [class^=\"icon-\"],\n.dropdown-menu > li > a:hover > [class*=\" icon-\"],\n.dropdown-menu > li > a:focus > [class*=\" icon-\"],\n.dropdown-menu > .active > a > [class^=\"icon-\"],\n.dropdown-menu > .active > a > [class*=\" icon-\"],\n.dropdown-submenu:hover > a > [class^=\"icon-\"],\n.dropdown-submenu:focus > a > [class^=\"icon-\"],\n.dropdown-submenu:hover > a > [class*=\" icon-\"],\n.dropdown-submenu:focus > a > [class*=\" icon-\"] {\n    background-image: url(\"../img/glyphicons-halflings-white.png\");\n}\n\n.icon-glass {\n    background-position: 0 0;\n}\n\n.icon-music {\n    background-position: -24px 0;\n}\n\n.icon-search {\n    background-position: -48px 0;\n}\n\n.icon-envelope {\n    background-position: -72px 0;\n}\n\n.icon-heart {\n    background-position: -96px 0;\n}\n\n.icon-star {\n    background-position: -120px 0;\n}\n\n.icon-star-empty {\n    background-position: -144px 0;\n}\n\n.icon-user {\n    background-position: -168px 0;\n}\n\n.icon-film {\n    background-position: -192px 0;\n}\n\n.icon-th-large {\n    background-position: -216px 0;\n}\n\n.icon-th {\n    background-position: -240px 0;\n}\n\n.icon-th-list {\n    background-position: -264px 0;\n}\n\n.icon-ok {\n    background-position: -288px 0;\n}\n\n.icon-remove {\n    background-position: -312px 0;\n}\n\n.icon-zoom-in {\n    background-position: -336px 0;\n}\n\n.icon-zoom-out {\n    background-position: -360px 0;\n}\n\n.icon-off {\n    background-position: -384px 0;\n}\n\n.icon-signal {\n    background-position: -408px 0;\n}\n\n.icon-cog {\n    background-position: -432px 0;\n}\n\n.icon-trash {\n    background-position: -456px 0;\n}\n\n.icon-home {\n    background-position: 0 -24px;\n}\n\n.icon-file {\n    background-position: -24px -24px;\n}\n\n.icon-time {\n    background-position: -48px -24px;\n}\n\n.icon-road {\n    background-position: -72px -24px;\n}\n\n.icon-download-alt {\n    background-position: -96px -24px;\n}\n\n.icon-download {\n    background-position: -120px -24px;\n}\n\n.icon-upload {\n    background-position: -144px -24px;\n}\n\n.icon-inbox {\n    background-position: -168px -24px;\n}\n\n.icon-play-circle {\n    background-position: -192px -24px;\n}\n\n.icon-repeat {\n    background-position: -216px -24px;\n}\n\n.icon-refresh {\n    background-position: -240px -24px;\n}\n\n.icon-list-alt {\n    background-position: -264px -24px;\n}\n\n.icon-lock {\n    background-position: -287px -24px;\n}\n\n.icon-flag {\n    background-position: -312px -24px;\n}\n\n.icon-headphones {\n    background-position: -336px -24px;\n}\n\n.icon-volume-off {\n    background-position: -360px -24px;\n}\n\n.icon-volume-down {\n    background-position: -384px -24px;\n}\n\n.icon-volume-up {\n    background-position: -408px -24px;\n}\n\n.icon-qrcode {\n    background-position: -432px -24px;\n}\n\n.icon-barcode {\n    background-position: -456px -24px;\n}\n\n.icon-tag {\n    background-position: 0 -48px;\n}\n\n.icon-tags {\n    background-position: -25px -48px;\n}\n\n.icon-book {\n    background-position: -48px -48px;\n}\n\n.icon-bookmark {\n    background-position: -72px -48px;\n}\n\n.icon-print {\n    background-position: -96px -48px;\n}\n\n.icon-camera {\n    background-position: -120px -48px;\n}\n\n.icon-font {\n    background-position: -144px -48px;\n}\n\n.icon-bold {\n    background-position: -167px -48px;\n}\n\n.icon-italic {\n    background-position: -192px -48px;\n}\n\n.icon-text-height {\n    background-position: -216px -48px;\n}\n\n.icon-text-width {\n    background-position: -240px -48px;\n}\n\n.icon-align-left {\n    background-position: -264px -48px;\n}\n\n.icon-align-center {\n    background-position: -288px -48px;\n}\n\n.icon-align-right {\n    background-position: -312px -48px;\n}\n\n.icon-align-justify {\n    background-position: -336px -48px;\n}\n\n.icon-list {\n    background-position: -360px -48px;\n}\n\n.icon-indent-left {\n    background-position: -384px -48px;\n}\n\n.icon-indent-right {\n    background-position: -408px -48px;\n}\n\n.icon-facetime-video {\n    background-position: -432px -48px;\n}\n\n.icon-picture {\n    background-position: -456px -48px;\n}\n\n.icon-pencil {\n    background-position: 0 -72px;\n}\n\n.icon-map-marker {\n    background-position: -24px -72px;\n}\n\n.icon-adjust {\n    background-position: -48px -72px;\n}\n\n.icon-tint {\n    background-position: -72px -72px;\n}\n\n.icon-edit {\n    background-position: -96px -72px;\n}\n\n.icon-share {\n    background-position: -120px -72px;\n}\n\n.icon-check {\n    background-position: -144px -72px;\n}\n\n.icon-move {\n    background-position: -168px -72px;\n}\n\n.icon-step-backward {\n    background-position: -192px -72px;\n}\n\n.icon-fast-backward {\n    background-position: -216px -72px;\n}\n\n.icon-backward {\n    background-position: -240px -72px;\n}\n\n.icon-play {\n    background-position: -264px -72px;\n}\n\n.icon-pause {\n    background-position: -288px -72px;\n}\n\n.icon-stop {\n    background-position: -312px -72px;\n}\n\n.icon-forward {\n    background-position: -336px -72px;\n}\n\n.icon-fast-forward {\n    background-position: -360px -72px;\n}\n\n.icon-step-forward {\n    background-position: -384px -72px;\n}\n\n.icon-eject {\n    background-position: -408px -72px;\n}\n\n.icon-chevron-left {\n    background-position: -432px -72px;\n}\n\n.icon-chevron-right {\n    background-position: -456px -72px;\n}\n\n.icon-plus-sign {\n    background-position: 0 -96px;\n}\n\n.icon-minus-sign {\n    background-position: -24px -96px;\n}\n\n.icon-remove-sign {\n    background-position: -48px -96px;\n}\n\n.icon-ok-sign {\n    background-position: -72px -96px;\n}\n\n.icon-question-sign {\n    background-position: -96px -96px;\n}\n\n.icon-info-sign {\n    background-position: -120px -96px;\n}\n\n.icon-screenshot {\n    background-position: -144px -96px;\n}\n\n.icon-remove-circle {\n    background-position: -168px -96px;\n}\n\n.icon-ok-circle {\n    background-position: -192px -96px;\n}\n\n.icon-ban-circle {\n    background-position: -216px -96px;\n}\n\n.icon-arrow-left {\n    background-position: -240px -96px;\n}\n\n.icon-arrow-right {\n    background-position: -264px -96px;\n}\n\n.icon-arrow-up {\n    background-position: -289px -96px;\n}\n\n.icon-arrow-down {\n    background-position: -312px -96px;\n}\n\n.icon-share-alt {\n    background-position: -336px -96px;\n}\n\n.icon-resize-full {\n    background-position: -360px -96px;\n}\n\n.icon-resize-small {\n    background-position: -384px -96px;\n}\n\n.icon-plus {\n    background-position: -408px -96px;\n}\n\n.icon-minus {\n    background-position: -433px -96px;\n}\n\n.icon-asterisk {\n    background-position: -456px -96px;\n}\n\n.icon-exclamation-sign {\n    background-position: 0 -120px;\n}\n\n.icon-gift {\n    background-position: -24px -120px;\n}\n\n.icon-leaf {\n    background-position: -48px -120px;\n}\n\n.icon-fire {\n    background-position: -72px -120px;\n}\n\n.icon-eye-open {\n    background-position: -96px -120px;\n}\n\n.icon-eye-close {\n    background-position: -120px -120px;\n}\n\n.icon-warning-sign {\n    background-position: -144px -120px;\n}\n\n.icon-plane {\n    background-position: -168px -120px;\n}\n\n.icon-calendar {\n    background-position: -192px -120px;\n}\n\n.icon-random {\n    width: 16px;\n    background-position: -216px -120px;\n}\n\n.icon-comment {\n    background-position: -240px -120px;\n}\n\n.icon-magnet {\n    background-position: -264px -120px;\n}\n\n.icon-chevron-up {\n    background-position: -288px -120px;\n}\n\n.icon-chevron-down {\n    background-position: -313px -119px;\n}\n\n.icon-retweet {\n    background-position: -336px -120px;\n}\n\n.icon-shopping-cart {\n    background-position: -360px -120px;\n}\n\n.icon-folder-close {\n    width: 16px;\n    background-position: -384px -120px;\n}\n\n.icon-folder-open {\n    width: 16px;\n    background-position: -408px -120px;\n}\n\n.icon-resize-vertical {\n    background-position: -432px -119px;\n}\n\n.icon-resize-horizontal {\n    background-position: -456px -118px;\n}\n\n.icon-hdd {\n    background-position: 0 -144px;\n}\n\n.icon-bullhorn {\n    background-position: -24px -144px;\n}\n\n.icon-bell {\n    background-position: -48px -144px;\n}\n\n.icon-certificate {\n    background-position: -72px -144px;\n}\n\n.icon-thumbs-up {\n    background-position: -96px -144px;\n}\n\n.icon-thumbs-down {\n    background-position: -120px -144px;\n}\n\n.icon-hand-right {\n    background-position: -144px -144px;\n}\n\n.icon-hand-left {\n    background-position: -168px -144px;\n}\n\n.icon-hand-up {\n    background-position: -192px -144px;\n}\n\n.icon-hand-down {\n    background-position: -216px -144px;\n}\n\n.icon-circle-arrow-right {\n    background-position: -240px -144px;\n}\n\n.icon-circle-arrow-left {\n    background-position: -264px -144px;\n}\n\n.icon-circle-arrow-up {\n    background-position: -288px -144px;\n}\n\n.icon-circle-arrow-down {\n    background-position: -312px -144px;\n}\n\n.icon-globe {\n    background-position: -336px -144px;\n}\n\n.icon-wrench {\n    background-position: -360px -144px;\n}\n\n.icon-tasks {\n    background-position: -384px -144px;\n}\n\n.icon-filter {\n    background-position: -408px -144px;\n}\n\n.icon-briefcase {\n    background-position: -432px -144px;\n}\n\n.icon-fullscreen {\n    background-position: -456px -144px;\n}\n\n.dropup,\n.dropdown {\n    position: relative;\n}\n\n.dropdown-toggle {\n    *margin-bottom: -3px;\n}\n\n.dropdown-toggle:active,\n.open .dropdown-toggle {\n    outline: 0;\n}\n\n.caret {\n    display: inline-block;\n    width: 0;\n    height: 0;\n    vertical-align: top;\n    border-top: 4px solid #000000;\n    border-right: 4px solid transparent;\n    border-left: 4px solid transparent;\n    content: \"\";\n}\n\n.dropdown .caret {\n    margin-top: 8px;\n    margin-left: 2px;\n}\n\n.dropdown-menu {\n    position: absolute;\n    top: 100%;\n    left: 0;\n    z-index: 1000;\n    display: none;\n    float: left;\n    min-width: 160px;\n    padding: 5px 0;\n    margin: 2px 0 0;\n    list-style: none;\n    background-color: #ffffff;\n    border: 1px solid #ccc;\n    border: 1px solid rgba(0, 0, 0, 0.2);\n    *border-right-width: 2px;\n    *border-bottom-width: 2px;\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    -webkit-background-clip: padding-box;\n    -moz-background-clip: padding;\n    background-clip: padding-box;\n}\n\n.dropdown-menu.pull-right {\n    right: 0;\n    left: auto;\n}\n\n.dropdown-menu .divider {\n    *width: 100%;\n    height: 1px;\n    margin: 9px 1px;\n    *margin: -5px 0 5px;\n    overflow: hidden;\n    background-color: #e5e5e5;\n    border-bottom: 1px solid #ffffff;\n}\n\n.dropdown-menu > li > a {\n    display: block;\n    padding: 3px 20px;\n    clear: both;\n    font-weight: normal;\n    line-height: 20px;\n    color: #333333;\n    white-space: nowrap;\n}\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus,\n.dropdown-submenu:hover > a,\n.dropdown-submenu:focus > a {\n    color: #ffffff;\n    text-decoration: none;\n    background-color: #0081c2;\n    background-image: -moz-linear-gradient(top, #0088cc, #0077b3);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));\n    background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);\n    background-image: -o-linear-gradient(top, #0088cc, #0077b3);\n    background-image: linear-gradient(to bottom, #0088cc, #0077b3);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);\n}\n\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n    color: #ffffff;\n    text-decoration: none;\n    background-color: #0081c2;\n    background-image: -moz-linear-gradient(top, #0088cc, #0077b3);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));\n    background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);\n    background-image: -o-linear-gradient(top, #0088cc, #0077b3);\n    background-image: linear-gradient(to bottom, #0088cc, #0077b3);\n    background-repeat: repeat-x;\n    outline: 0;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);\n}\n\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n    color: #999999;\n}\n\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n    text-decoration: none;\n    cursor: default;\n    background-color: transparent;\n    background-image: none;\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.open {\n    *z-index: 1000;\n}\n\n.open > .dropdown-menu {\n    display: block;\n}\n\n.pull-right > .dropdown-menu {\n    right: 0;\n    left: auto;\n}\n\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n    border-top: 0;\n    border-bottom: 4px solid #000000;\n    content: \"\";\n}\n\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n    top: auto;\n    bottom: 100%;\n    margin-bottom: 1px;\n}\n\n.dropdown-submenu {\n    position: relative;\n}\n\n.dropdown-submenu > .dropdown-menu {\n    top: 0;\n    left: 100%;\n    margin-top: -6px;\n    margin-left: -1px;\n    -webkit-border-radius: 0 6px 6px 6px;\n    -moz-border-radius: 0 6px 6px 6px;\n    border-radius: 0 6px 6px 6px;\n}\n\n.dropdown-submenu:hover > .dropdown-menu {\n    display: block;\n}\n\n.dropup .dropdown-submenu > .dropdown-menu {\n    top: auto;\n    bottom: 0;\n    margin-top: 0;\n    margin-bottom: -2px;\n    -webkit-border-radius: 5px 5px 5px 0;\n    -moz-border-radius: 5px 5px 5px 0;\n    border-radius: 5px 5px 5px 0;\n}\n\n.dropdown-submenu > a:after {\n    display: block;\n    float: right;\n    width: 0;\n    height: 0;\n    margin-top: 5px;\n    margin-right: -10px;\n    border-color: transparent;\n    border-left-color: #cccccc;\n    border-style: solid;\n    border-width: 5px 0 5px 5px;\n    content: \" \";\n}\n\n.dropdown-submenu:hover > a:after {\n    border-left-color: #ffffff;\n}\n\n.dropdown-submenu.pull-left {\n    float: none;\n}\n\n.dropdown-submenu.pull-left > .dropdown-menu {\n    left: -100%;\n    margin-left: 10px;\n    -webkit-border-radius: 6px 0 6px 6px;\n    -moz-border-radius: 6px 0 6px 6px;\n    border-radius: 6px 0 6px 6px;\n}\n\n.dropdown .dropdown-menu .nav-header {\n    padding-right: 20px;\n    padding-left: 20px;\n}\n\n.typeahead {\n    z-index: 1051;\n    margin-top: 2px;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.well {\n    min-height: 20px;\n    padding: 19px;\n    margin-bottom: 20px;\n    background-color: #f5f5f5;\n    border: 1px solid #e3e3e3;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n\n.well blockquote {\n    border-color: #ddd;\n    border-color: rgba(0, 0, 0, 0.15);\n}\n\n.well-large {\n    padding: 24px;\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n}\n\n.well-small {\n    padding: 9px;\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    border-radius: 3px;\n}\n\n.fade {\n    opacity: 0;\n    -webkit-transition: opacity 0.15s linear;\n    -moz-transition: opacity 0.15s linear;\n    -o-transition: opacity 0.15s linear;\n    transition: opacity 0.15s linear;\n}\n\n.fade.in {\n    opacity: 1;\n}\n\n.collapse {\n    position: relative;\n    height: 0;\n    overflow: hidden;\n    -webkit-transition: height 0.35s ease;\n    -moz-transition: height 0.35s ease;\n    -o-transition: height 0.35s ease;\n    transition: height 0.35s ease;\n}\n\n.collapse.in {\n    height: auto;\n}\n\n.close {\n    float: right;\n    font-size: 20px;\n    font-weight: bold;\n    line-height: 20px;\n    color: #000000;\n    text-shadow: 0 1px 0 #ffffff;\n    opacity: 0.2;\n    filter: alpha(opacity=20);\n}\n\n.close:hover,\n.close:focus {\n    color: #000000;\n    text-decoration: none;\n    cursor: pointer;\n    opacity: 0.4;\n    filter: alpha(opacity=40);\n}\n\nbutton.close {\n    padding: 0;\n    cursor: pointer;\n    background: transparent;\n    border: 0;\n    -webkit-appearance: none;\n}\n\n.btn {\n    display: inline-block;\n    *display: inline;\n    padding: 4px 12px;\n    margin-bottom: 0;\n    *margin-left: .3em;\n    font-size: 14px;\n    line-height: 20px;\n    color: #333333;\n    text-align: center;\n    text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);\n    vertical-align: middle;\n    cursor: pointer;\n    background-color: #f5f5f5;\n    *background-color: #e6e6e6;\n    background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));\n    background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);\n    background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);\n    background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);\n    background-repeat: repeat-x;\n    border: 1px solid #cccccc;\n    *border: 0;\n    border-color: #e6e6e6 #e6e6e6 #bfbfbf;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    border-bottom-color: #b3b3b3;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n    *zoom: 1;\n    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn:hover,\n.btn:focus,\n.btn:active,\n.btn.active,\n.btn.disabled,\n.btn[disabled] {\n    color: #333333;\n    background-color: #e6e6e6;\n    *background-color: #d9d9d9;\n}\n\n.btn:active,\n.btn.active {\n    background-color: #cccccc \\9;\n}\n\n.btn:first-child {\n    *margin-left: 0;\n}\n\n.btn:hover,\n.btn:focus {\n    color: #333333;\n    text-decoration: none;\n    background-position: 0 -15px;\n    -webkit-transition: background-position 0.1s linear;\n    -moz-transition: background-position 0.1s linear;\n    -o-transition: background-position 0.1s linear;\n    transition: background-position 0.1s linear;\n}\n\n.btn:focus {\n    outline: thin dotted #333;\n    outline: 5px auto -webkit-focus-ring-color;\n    outline-offset: -2px;\n}\n\n.btn.active,\n.btn:active {\n    background-image: none;\n    outline: 0;\n    -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn.disabled,\n.btn[disabled] {\n    cursor: default;\n    background-image: none;\n    opacity: 0.65;\n    filter: alpha(opacity=65);\n    -webkit-box-shadow: none;\n    -moz-box-shadow: none;\n    box-shadow: none;\n}\n\n.btn-large {\n    padding: 11px 19px;\n    font-size: 17.5px;\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n}\n\n.btn-large [class^=\"icon-\"],\n.btn-large [class*=\" icon-\"] {\n    margin-top: 4px;\n}\n\n.btn-small {\n    padding: 2px 10px;\n    font-size: 11.9px;\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    border-radius: 3px;\n}\n\n.btn-small [class^=\"icon-\"],\n.btn-small [class*=\" icon-\"] {\n    margin-top: 0;\n}\n\n.btn-mini [class^=\"icon-\"],\n.btn-mini [class*=\" icon-\"] {\n    margin-top: -1px;\n}\n\n.btn-mini {\n    padding: 0 6px;\n    font-size: 10.5px;\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    border-radius: 3px;\n}\n\n.btn-block {\n    display: block;\n    width: 100%;\n    padding-right: 0;\n    padding-left: 0;\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n\n.btn-block + .btn-block {\n    margin-top: 5px;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n    width: 100%;\n}\n\n.btn-primary.active,\n.btn-warning.active,\n.btn-danger.active,\n.btn-success.active,\n.btn-info.active,\n.btn-inverse.active {\n    color: rgba(255, 255, 255, 0.75);\n}\n\n.btn-primary {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #006dcc;\n    *background-color: #0044cc;\n    background-image: -moz-linear-gradient(top, #0088cc, #0044cc);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));\n    background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);\n    background-image: -o-linear-gradient(top, #0088cc, #0044cc);\n    background-image: linear-gradient(to bottom, #0088cc, #0044cc);\n    background-repeat: repeat-x;\n    border-color: #0044cc #0044cc #002a80;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary:active,\n.btn-primary.active,\n.btn-primary.disabled,\n.btn-primary[disabled] {\n    color: #ffffff;\n    background-color: #0044cc;\n    *background-color: #003bb3;\n}\n\n.btn-primary:active,\n.btn-primary.active {\n    background-color: #003399 \\9;\n}\n\n.btn-warning {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #faa732;\n    *background-color: #f89406;\n    background-image: -moz-linear-gradient(top, #fbb450, #f89406);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));\n    background-image: -webkit-linear-gradient(top, #fbb450, #f89406);\n    background-image: -o-linear-gradient(top, #fbb450, #f89406);\n    background-image: linear-gradient(to bottom, #fbb450, #f89406);\n    background-repeat: repeat-x;\n    border-color: #f89406 #f89406 #ad6704;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning:active,\n.btn-warning.active,\n.btn-warning.disabled,\n.btn-warning[disabled] {\n    color: #ffffff;\n    background-color: #f89406;\n    *background-color: #df8505;\n}\n\n.btn-warning:active,\n.btn-warning.active {\n    background-color: #c67605 \\9;\n}\n\n.btn-danger {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #da4f49;\n    *background-color: #bd362f;\n    background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));\n    background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);\n    background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);\n    background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);\n    background-repeat: repeat-x;\n    border-color: #bd362f #bd362f #802420;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger:active,\n.btn-danger.active,\n.btn-danger.disabled,\n.btn-danger[disabled] {\n    color: #ffffff;\n    background-color: #bd362f;\n    *background-color: #a9302a;\n}\n\n.btn-danger:active,\n.btn-danger.active {\n    background-color: #942a25 \\9;\n}\n\n.btn-success {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #5bb75b;\n    *background-color: #51a351;\n    background-image: -moz-linear-gradient(top, #62c462, #51a351);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));\n    background-image: -webkit-linear-gradient(top, #62c462, #51a351);\n    background-image: -o-linear-gradient(top, #62c462, #51a351);\n    background-image: linear-gradient(to bottom, #62c462, #51a351);\n    background-repeat: repeat-x;\n    border-color: #51a351 #51a351 #387038;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-success:hover,\n.btn-success:focus,\n.btn-success:active,\n.btn-success.active,\n.btn-success.disabled,\n.btn-success[disabled] {\n    color: #ffffff;\n    background-color: #51a351;\n    *background-color: #499249;\n}\n\n.btn-success:active,\n.btn-success.active {\n    background-color: #408140 \\9;\n}\n\n.btn-info {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #49afcd;\n    *background-color: #2f96b4;\n    background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));\n    background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);\n    background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);\n    background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);\n    background-repeat: repeat-x;\n    border-color: #2f96b4 #2f96b4 #1f6377;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-info:hover,\n.btn-info:focus,\n.btn-info:active,\n.btn-info.active,\n.btn-info.disabled,\n.btn-info[disabled] {\n    color: #ffffff;\n    background-color: #2f96b4;\n    *background-color: #2a85a0;\n}\n\n.btn-info:active,\n.btn-info.active {\n    background-color: #24748c \\9;\n}\n\n.btn-inverse {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #363636;\n    *background-color: #222222;\n    background-image: -moz-linear-gradient(top, #444444, #222222);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));\n    background-image: -webkit-linear-gradient(top, #444444, #222222);\n    background-image: -o-linear-gradient(top, #444444, #222222);\n    background-image: linear-gradient(to bottom, #444444, #222222);\n    background-repeat: repeat-x;\n    border-color: #222222 #222222 #000000;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.btn-inverse:hover,\n.btn-inverse:focus,\n.btn-inverse:active,\n.btn-inverse.active,\n.btn-inverse.disabled,\n.btn-inverse[disabled] {\n    color: #ffffff;\n    background-color: #222222;\n    *background-color: #151515;\n}\n\n.btn-inverse:active,\n.btn-inverse.active {\n    background-color: #080808 \\9;\n}\n\nbutton.btn,\ninput[type=\"submit\"].btn {\n    *padding-top: 3px;\n    *padding-bottom: 3px;\n}\n\nbutton.btn::-moz-focus-inner,\ninput[type=\"submit\"].btn::-moz-focus-inner {\n    padding: 0;\n    border: 0;\n}\n\nbutton.btn.btn-large,\ninput[type=\"submit\"].btn.btn-large {\n    *padding-top: 7px;\n    *padding-bottom: 7px;\n}\n\nbutton.btn.btn-small,\ninput[type=\"submit\"].btn.btn-small {\n    *padding-top: 3px;\n    *padding-bottom: 3px;\n}\n\nbutton.btn.btn-mini,\ninput[type=\"submit\"].btn.btn-mini {\n    *padding-top: 1px;\n    *padding-bottom: 1px;\n}\n\n.btn-link,\n.btn-link:active,\n.btn-link[disabled] {\n    background-color: transparent;\n    background-image: none;\n    -webkit-box-shadow: none;\n    -moz-box-shadow: none;\n    box-shadow: none;\n}\n\n.btn-link {\n    color: #0088cc;\n    cursor: pointer;\n    border-color: transparent;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.btn-link:hover,\n.btn-link:focus {\n    color: #005580;\n    text-decoration: underline;\n    background-color: transparent;\n}\n\n.btn-link[disabled]:hover,\n.btn-link[disabled]:focus {\n    color: #333333;\n    text-decoration: none;\n}\n\n.btn-group {\n    position: relative;\n    display: inline-block;\n    *display: inline;\n    *margin-left: .3em;\n    font-size: 0;\n    white-space: nowrap;\n    vertical-align: middle;\n    *zoom: 1;\n}\n\n.btn-group:first-child {\n    *margin-left: 0;\n}\n\n.btn-group + .btn-group {\n    margin-left: 5px;\n}\n\n.btn-toolbar {\n    margin-top: 10px;\n    margin-bottom: 10px;\n    font-size: 0;\n}\n\n.btn-toolbar > .btn + .btn,\n.btn-toolbar > .btn-group + .btn,\n.btn-toolbar > .btn + .btn-group {\n    margin-left: 5px;\n}\n\n.btn-group > .btn {\n    position: relative;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.btn-group > .btn + .btn {\n    margin-left: -1px;\n}\n\n.btn-group > .btn,\n.btn-group > .dropdown-menu,\n.btn-group > .popover {\n    font-size: 14px;\n}\n\n.btn-group > .btn-mini {\n    font-size: 10.5px;\n}\n\n.btn-group > .btn-small {\n    font-size: 11.9px;\n}\n\n.btn-group > .btn-large {\n    font-size: 17.5px;\n}\n\n.btn-group > .btn:first-child {\n    margin-left: 0;\n    -webkit-border-bottom-left-radius: 4px;\n    border-bottom-left-radius: 4px;\n    -webkit-border-top-left-radius: 4px;\n    border-top-left-radius: 4px;\n    -moz-border-radius-bottomleft: 4px;\n    -moz-border-radius-topleft: 4px;\n}\n\n.btn-group > .btn:last-child,\n.btn-group > .dropdown-toggle {\n    -webkit-border-top-right-radius: 4px;\n    border-top-right-radius: 4px;\n    -webkit-border-bottom-right-radius: 4px;\n    border-bottom-right-radius: 4px;\n    -moz-border-radius-topright: 4px;\n    -moz-border-radius-bottomright: 4px;\n}\n\n.btn-group > .btn.large:first-child {\n    margin-left: 0;\n    -webkit-border-bottom-left-radius: 6px;\n    border-bottom-left-radius: 6px;\n    -webkit-border-top-left-radius: 6px;\n    border-top-left-radius: 6px;\n    -moz-border-radius-bottomleft: 6px;\n    -moz-border-radius-topleft: 6px;\n}\n\n.btn-group > .btn.large:last-child,\n.btn-group > .large.dropdown-toggle {\n    -webkit-border-top-right-radius: 6px;\n    border-top-right-radius: 6px;\n    -webkit-border-bottom-right-radius: 6px;\n    border-bottom-right-radius: 6px;\n    -moz-border-radius-topright: 6px;\n    -moz-border-radius-bottomright: 6px;\n}\n\n.btn-group > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group > .btn:active,\n.btn-group > .btn.active {\n    z-index: 2;\n}\n\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n    outline: 0;\n}\n\n.btn-group > .btn + .dropdown-toggle {\n    *padding-top: 5px;\n    padding-right: 8px;\n    *padding-bottom: 5px;\n    padding-left: 8px;\n    -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n    box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn-group > .btn-mini + .dropdown-toggle {\n    *padding-top: 2px;\n    padding-right: 5px;\n    *padding-bottom: 2px;\n    padding-left: 5px;\n}\n\n.btn-group > .btn-small + .dropdown-toggle {\n    *padding-top: 5px;\n    *padding-bottom: 4px;\n}\n\n.btn-group > .btn-large + .dropdown-toggle {\n    *padding-top: 7px;\n    padding-right: 12px;\n    *padding-bottom: 7px;\n    padding-left: 12px;\n}\n\n.btn-group.open .dropdown-toggle {\n    background-image: none;\n    -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.btn-group.open .btn.dropdown-toggle {\n    background-color: #e6e6e6;\n}\n\n.btn-group.open .btn-primary.dropdown-toggle {\n    background-color: #0044cc;\n}\n\n.btn-group.open .btn-warning.dropdown-toggle {\n    background-color: #f89406;\n}\n\n.btn-group.open .btn-danger.dropdown-toggle {\n    background-color: #bd362f;\n}\n\n.btn-group.open .btn-success.dropdown-toggle {\n    background-color: #51a351;\n}\n\n.btn-group.open .btn-info.dropdown-toggle {\n    background-color: #2f96b4;\n}\n\n.btn-group.open .btn-inverse.dropdown-toggle {\n    background-color: #222222;\n}\n\n.btn .caret {\n    margin-top: 8px;\n    margin-left: 0;\n}\n\n.btn-large .caret {\n    margin-top: 6px;\n}\n\n.btn-large .caret {\n    border-top-width: 5px;\n    border-right-width: 5px;\n    border-left-width: 5px;\n}\n\n.btn-mini .caret,\n.btn-small .caret {\n    margin-top: 8px;\n}\n\n.dropup .btn-large .caret {\n    border-bottom-width: 5px;\n}\n\n.btn-primary .caret,\n.btn-warning .caret,\n.btn-danger .caret,\n.btn-info .caret,\n.btn-success .caret,\n.btn-inverse .caret {\n    border-top-color: #ffffff;\n    border-bottom-color: #ffffff;\n}\n\n.btn-group-vertical {\n    display: inline-block;\n    *display: inline;\n    /* IE7 inline-block hack */\n\n    *zoom: 1;\n}\n\n.btn-group-vertical > .btn {\n    display: block;\n    float: none;\n    max-width: 100%;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.btn-group-vertical > .btn + .btn {\n    margin-top: -1px;\n    margin-left: 0;\n}\n\n.btn-group-vertical > .btn:first-child {\n    -webkit-border-radius: 4px 4px 0 0;\n    -moz-border-radius: 4px 4px 0 0;\n    border-radius: 4px 4px 0 0;\n}\n\n.btn-group-vertical > .btn:last-child {\n    -webkit-border-radius: 0 0 4px 4px;\n    -moz-border-radius: 0 0 4px 4px;\n    border-radius: 0 0 4px 4px;\n}\n\n.btn-group-vertical > .btn-large:first-child {\n    -webkit-border-radius: 6px 6px 0 0;\n    -moz-border-radius: 6px 6px 0 0;\n    border-radius: 6px 6px 0 0;\n}\n\n.btn-group-vertical > .btn-large:last-child {\n    -webkit-border-radius: 0 0 6px 6px;\n    -moz-border-radius: 0 0 6px 6px;\n    border-radius: 0 0 6px 6px;\n}\n\n.alert {\n    padding: 8px 35px 8px 14px;\n    margin-bottom: 20px;\n    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n    background-color: #fcf8e3;\n    border: 1px solid #fbeed5;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.alert,\n.alert h4 {\n    color: #c09853;\n}\n\n.alert h4 {\n    margin: 0;\n}\n\n.alert .close {\n    position: relative;\n    top: -2px;\n    right: -21px;\n    line-height: 20px;\n}\n\n.alert-success {\n    color: #468847;\n    background-color: #dff0d8;\n    border-color: #d6e9c6;\n}\n\n.alert-success h4 {\n    color: #468847;\n}\n\n.alert-danger,\n.alert-error {\n    color: #b94a48;\n    background-color: #f2dede;\n    border-color: #eed3d7;\n}\n\n.alert-danger h4,\n.alert-error h4 {\n    color: #b94a48;\n}\n\n.alert-info {\n    color: #3a87ad;\n    background-color: #d9edf7;\n    border-color: #bce8f1;\n}\n\n.alert-info h4 {\n    color: #3a87ad;\n}\n\n.alert-block {\n    padding-top: 14px;\n    padding-bottom: 14px;\n}\n\n.alert-block > p,\n.alert-block > ul {\n    margin-bottom: 0;\n}\n\n.alert-block p + p {\n    margin-top: 5px;\n}\n\n.nav {\n    margin-bottom: 20px;\n    margin-left: 0;\n    list-style: none;\n}\n\n.nav > li > a {\n    display: block;\n}\n\n.nav > li > a:hover,\n.nav > li > a:focus {\n    text-decoration: none;\n    background-color: #eeeeee;\n}\n\n.nav > li > a > img {\n    max-width: none;\n}\n\n.nav > .pull-right {\n    float: right;\n}\n\n.nav-header {\n    display: block;\n    padding: 3px 15px;\n    font-size: 11px;\n    font-weight: bold;\n    line-height: 20px;\n    color: #999999;\n    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n    text-transform: uppercase;\n}\n\n.nav li + .nav-header {\n    margin-top: 9px;\n}\n\n.nav-list {\n    padding-right: 15px;\n    padding-left: 15px;\n    margin-bottom: 0;\n}\n\n.nav-list > li > a,\n.nav-list .nav-header {\n    margin-right: -15px;\n    margin-left: -15px;\n    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n}\n\n.nav-list > li > a {\n    padding: 3px 15px;\n}\n\n.nav-list > .active > a,\n.nav-list > .active > a:hover,\n.nav-list > .active > a:focus {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n    background-color: #0088cc;\n}\n\n.nav-list [class^=\"icon-\"],\n.nav-list [class*=\" icon-\"] {\n    margin-right: 2px;\n}\n\n.nav-list .divider {\n    *width: 100%;\n    height: 1px;\n    margin: 9px 1px;\n    *margin: -5px 0 5px;\n    overflow: hidden;\n    background-color: #e5e5e5;\n    border-bottom: 1px solid #ffffff;\n}\n\n.nav-tabs,\n.nav-pills {\n    *zoom: 1;\n}\n\n.nav-tabs:before,\n.nav-pills:before,\n.nav-tabs:after,\n.nav-pills:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.nav-tabs:after,\n.nav-pills:after {\n    clear: both;\n}\n\n.nav-tabs > li,\n.nav-pills > li {\n    float: left;\n}\n\n.nav-tabs > li > a,\n.nav-pills > li > a {\n    padding-right: 12px;\n    padding-left: 12px;\n    margin-right: 2px;\n    line-height: 14px;\n}\n\n.nav-tabs {\n    border-bottom: 1px solid #ddd;\n}\n\n.nav-tabs > li {\n    margin-bottom: -1px;\n}\n\n.nav-tabs > li > a {\n    padding-top: 8px;\n    padding-bottom: 8px;\n    line-height: 20px;\n    border: 1px solid transparent;\n    -webkit-border-radius: 4px 4px 0 0;\n    -moz-border-radius: 4px 4px 0 0;\n    border-radius: 4px 4px 0 0;\n}\n\n.nav-tabs > li > a:hover,\n.nav-tabs > li > a:focus {\n    border-color: #eeeeee #eeeeee #dddddd;\n}\n\n.nav-tabs > .active > a,\n.nav-tabs > .active > a:hover,\n.nav-tabs > .active > a:focus {\n    color: #555555;\n    cursor: default;\n    background-color: #ffffff;\n    border: 1px solid #ddd;\n    border-bottom-color: transparent;\n}\n\n.nav-pills > li > a {\n    padding-top: 8px;\n    padding-bottom: 8px;\n    margin-top: 2px;\n    margin-bottom: 2px;\n    -webkit-border-radius: 5px;\n    -moz-border-radius: 5px;\n    border-radius: 5px;\n}\n\n.nav-pills > .active > a,\n.nav-pills > .active > a:hover,\n.nav-pills > .active > a:focus {\n    color: #ffffff;\n    background-color: #0088cc;\n}\n\n.nav-stacked > li {\n    float: none;\n}\n\n.nav-stacked > li > a {\n    margin-right: 0;\n}\n\n.nav-tabs.nav-stacked {\n    border-bottom: 0;\n}\n\n.nav-tabs.nav-stacked > li > a {\n    border: 1px solid #ddd;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.nav-tabs.nav-stacked > li:first-child > a {\n    -webkit-border-top-right-radius: 4px;\n    border-top-right-radius: 4px;\n    -webkit-border-top-left-radius: 4px;\n    border-top-left-radius: 4px;\n    -moz-border-radius-topright: 4px;\n    -moz-border-radius-topleft: 4px;\n}\n\n.nav-tabs.nav-stacked > li:last-child > a {\n    -webkit-border-bottom-right-radius: 4px;\n    border-bottom-right-radius: 4px;\n    -webkit-border-bottom-left-radius: 4px;\n    border-bottom-left-radius: 4px;\n    -moz-border-radius-bottomright: 4px;\n    -moz-border-radius-bottomleft: 4px;\n}\n\n.nav-tabs.nav-stacked > li > a:hover,\n.nav-tabs.nav-stacked > li > a:focus {\n    z-index: 2;\n    border-color: #ddd;\n}\n\n.nav-pills.nav-stacked > li > a {\n    margin-bottom: 3px;\n}\n\n.nav-pills.nav-stacked > li:last-child > a {\n    margin-bottom: 1px;\n}\n\n.nav-tabs .dropdown-menu {\n    -webkit-border-radius: 0 0 6px 6px;\n    -moz-border-radius: 0 0 6px 6px;\n    border-radius: 0 0 6px 6px;\n}\n\n.nav-pills .dropdown-menu {\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n}\n\n.nav .dropdown-toggle .caret {\n    margin-top: 6px;\n    border-top-color: #0088cc;\n    border-bottom-color: #0088cc;\n}\n\n.nav .dropdown-toggle:hover .caret,\n.nav .dropdown-toggle:focus .caret {\n    border-top-color: #005580;\n    border-bottom-color: #005580;\n}\n\n/* move down carets for tabs */\n\n.nav-tabs .dropdown-toggle .caret {\n    margin-top: 8px;\n}\n\n.nav .active .dropdown-toggle .caret {\n    border-top-color: #fff;\n    border-bottom-color: #fff;\n}\n\n.nav-tabs .active .dropdown-toggle .caret {\n    border-top-color: #555555;\n    border-bottom-color: #555555;\n}\n\n.nav > .dropdown.active > a:hover,\n.nav > .dropdown.active > a:focus {\n    cursor: pointer;\n}\n\n.nav-tabs .open .dropdown-toggle,\n.nav-pills .open .dropdown-toggle,\n.nav > li.dropdown.open.active > a:hover,\n.nav > li.dropdown.open.active > a:focus {\n    color: #ffffff;\n    background-color: #999999;\n    border-color: #999999;\n}\n\n.nav li.dropdown.open .caret,\n.nav li.dropdown.open.active .caret,\n.nav li.dropdown.open a:hover .caret,\n.nav li.dropdown.open a:focus .caret {\n    border-top-color: #ffffff;\n    border-bottom-color: #ffffff;\n    opacity: 1;\n    filter: alpha(opacity=100);\n}\n\n.tabs-stacked .open > a:hover,\n.tabs-stacked .open > a:focus {\n    border-color: #999999;\n}\n\n.tabbable {\n    *zoom: 1;\n}\n\n.tabbable:before,\n.tabbable:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.tabbable:after {\n    clear: both;\n}\n\n.tab-content {\n    overflow: auto;\n}\n\n.tabs-below > .nav-tabs,\n.tabs-right > .nav-tabs,\n.tabs-left > .nav-tabs {\n    border-bottom: 0;\n}\n\n.tab-content > .tab-pane,\n.pill-content > .pill-pane {\n    display: none;\n}\n\n.tab-content > .active,\n.pill-content > .active {\n    display: block;\n}\n\n.tabs-below > .nav-tabs {\n    border-top: 1px solid #ddd;\n}\n\n.tabs-below > .nav-tabs > li {\n    margin-top: -1px;\n    margin-bottom: 0;\n}\n\n.tabs-below > .nav-tabs > li > a {\n    -webkit-border-radius: 0 0 4px 4px;\n    -moz-border-radius: 0 0 4px 4px;\n    border-radius: 0 0 4px 4px;\n}\n\n.tabs-below > .nav-tabs > li > a:hover,\n.tabs-below > .nav-tabs > li > a:focus {\n    border-top-color: #ddd;\n    border-bottom-color: transparent;\n}\n\n.tabs-below > .nav-tabs > .active > a,\n.tabs-below > .nav-tabs > .active > a:hover,\n.tabs-below > .nav-tabs > .active > a:focus {\n    border-color: transparent #ddd #ddd #ddd;\n}\n\n.tabs-left > .nav-tabs > li,\n.tabs-right > .nav-tabs > li {\n    float: none;\n}\n\n.tabs-left > .nav-tabs > li > a,\n.tabs-right > .nav-tabs > li > a {\n    min-width: 74px;\n    margin-right: 0;\n    margin-bottom: 3px;\n}\n\n.tabs-left > .nav-tabs {\n    float: left;\n    margin-right: 19px;\n    border-right: 1px solid #ddd;\n}\n\n.tabs-left > .nav-tabs > li > a {\n    margin-right: -1px;\n    -webkit-border-radius: 4px 0 0 4px;\n    -moz-border-radius: 4px 0 0 4px;\n    border-radius: 4px 0 0 4px;\n}\n\n.tabs-left > .nav-tabs > li > a:hover,\n.tabs-left > .nav-tabs > li > a:focus {\n    border-color: #eeeeee #dddddd #eeeeee #eeeeee;\n}\n\n.tabs-left > .nav-tabs .active > a,\n.tabs-left > .nav-tabs .active > a:hover,\n.tabs-left > .nav-tabs .active > a:focus {\n    border-color: #ddd transparent #ddd #ddd;\n    *border-right-color: #ffffff;\n}\n\n.tabs-right > .nav-tabs {\n    float: right;\n    margin-left: 19px;\n    border-left: 1px solid #ddd;\n}\n\n.tabs-right > .nav-tabs > li > a {\n    margin-left: -1px;\n    -webkit-border-radius: 0 4px 4px 0;\n    -moz-border-radius: 0 4px 4px 0;\n    border-radius: 0 4px 4px 0;\n}\n\n.tabs-right > .nav-tabs > li > a:hover,\n.tabs-right > .nav-tabs > li > a:focus {\n    border-color: #eeeeee #eeeeee #eeeeee #dddddd;\n}\n\n.tabs-right > .nav-tabs .active > a,\n.tabs-right > .nav-tabs .active > a:hover,\n.tabs-right > .nav-tabs .active > a:focus {\n    border-color: #ddd #ddd #ddd transparent;\n    *border-left-color: #ffffff;\n}\n\n.nav > .disabled > a {\n    color: #999999;\n}\n\n.nav > .disabled > a:hover,\n.nav > .disabled > a:focus {\n    text-decoration: none;\n    cursor: default;\n    background-color: transparent;\n}\n\n.navbar {\n    *position: relative;\n    *z-index: 2;\n    margin-bottom: 20px;\n    overflow: visible;\n}\n\n.navbar-inner {\n    min-height: 40px;\n    padding-right: 20px;\n    padding-left: 20px;\n    background-color: #fafafa;\n    background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));\n    background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);\n    background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);\n    background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);\n    background-repeat: repeat-x;\n    border: 1px solid #d4d4d4;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);\n    *zoom: 1;\n    -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\n    -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\n    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\n}\n\n.navbar-inner:before,\n.navbar-inner:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.navbar-inner:after {\n    clear: both;\n}\n\n.navbar .container {\n    width: auto;\n}\n\n.nav-collapse.collapse {\n    height: auto;\n    overflow: visible;\n}\n\n.navbar .brand {\n    display: block;\n    float: left;\n    padding: 10px 20px 10px;\n    margin-left: -20px;\n    font-size: 20px;\n    font-weight: 200;\n    color: #777777;\n    text-shadow: 0 1px 0 #ffffff;\n}\n\n.navbar .brand:hover,\n.navbar .brand:focus {\n    text-decoration: none;\n}\n\n.navbar-text {\n    margin-bottom: 0;\n    line-height: 40px;\n    color: #777777;\n}\n\n.navbar-link {\n    color: #777777;\n}\n\n.navbar-link:hover,\n.navbar-link:focus {\n    color: #333333;\n}\n\n.navbar .divider-vertical {\n    height: 40px;\n    margin: 0 9px;\n    border-right: 1px solid #ffffff;\n    border-left: 1px solid #f2f2f2;\n}\n\n.navbar .btn,\n.navbar .btn-group {\n    margin-top: 5px;\n}\n\n.navbar .btn-group .btn,\n.navbar .input-prepend .btn,\n.navbar .input-append .btn,\n.navbar .input-prepend .btn-group,\n.navbar .input-append .btn-group {\n    margin-top: 0;\n}\n\n.navbar-form {\n    margin-bottom: 0;\n    *zoom: 1;\n}\n\n.navbar-form:before,\n.navbar-form:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.navbar-form:after {\n    clear: both;\n}\n\n.navbar-form input,\n.navbar-form select,\n.navbar-form .radio,\n.navbar-form .checkbox {\n    margin-top: 5px;\n}\n\n.navbar-form input,\n.navbar-form select,\n.navbar-form .btn {\n    display: inline-block;\n    margin-bottom: 0;\n}\n\n.navbar-form input[type=\"image\"],\n.navbar-form input[type=\"checkbox\"],\n.navbar-form input[type=\"radio\"] {\n    margin-top: 3px;\n}\n\n.navbar-form .input-append,\n.navbar-form .input-prepend {\n    margin-top: 5px;\n    white-space: nowrap;\n}\n\n.navbar-form .input-append input,\n.navbar-form .input-prepend input {\n    margin-top: 0;\n}\n\n.navbar-search {\n    position: relative;\n    float: left;\n    margin-top: 5px;\n    margin-bottom: 0;\n}\n\n.navbar-search .search-query {\n    padding: 4px 14px;\n    margin-bottom: 0;\n    font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n    font-size: 13px;\n    font-weight: normal;\n    line-height: 1;\n    -webkit-border-radius: 15px;\n    -moz-border-radius: 15px;\n    border-radius: 15px;\n}\n\n.navbar-static-top {\n    position: static;\n    margin-bottom: 0;\n}\n\n.navbar-static-top .navbar-inner {\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n    position: fixed;\n    right: 0;\n    left: 0;\n    z-index: 1030;\n    margin-bottom: 0;\n}\n\n.navbar-fixed-top .navbar-inner,\n.navbar-static-top .navbar-inner {\n    border-width: 0 0 1px;\n}\n\n.navbar-fixed-bottom .navbar-inner {\n    border-width: 1px 0 0;\n}\n\n.navbar-fixed-top .navbar-inner,\n.navbar-fixed-bottom .navbar-inner {\n    padding-right: 0;\n    padding-left: 0;\n    -webkit-border-radius: 0;\n    -moz-border-radius: 0;\n    border-radius: 0;\n}\n\n.navbar-static-top .container,\n.navbar-fixed-top .container,\n.navbar-fixed-bottom .container {\n    width: 940px;\n}\n\n.navbar-fixed-top {\n    top: 0;\n}\n\n.navbar-fixed-top .navbar-inner,\n.navbar-static-top .navbar-inner {\n    -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);\n    box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);\n}\n\n.navbar-fixed-bottom {\n    bottom: 0;\n}\n\n.navbar-fixed-bottom .navbar-inner {\n    -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);\n    box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);\n}\n\n.navbar .nav {\n    position: relative;\n    left: 0;\n    display: block;\n    float: left;\n    margin: 0 10px 0 0;\n}\n\n.navbar .nav.pull-right {\n    float: right;\n    margin-right: 0;\n}\n\n.navbar .nav > li {\n    float: left;\n}\n\n.navbar .nav > li > a {\n    float: none;\n    padding: 10px 15px 10px;\n    color: #777777;\n    text-decoration: none;\n    text-shadow: 0 1px 0 #ffffff;\n}\n\n.navbar .nav .dropdown-toggle .caret {\n    margin-top: 8px;\n}\n\n.navbar .nav > li > a:focus,\n.navbar .nav > li > a:hover {\n    color: hsl(210, 53%, 51%);\n    text-decoration: none;\n    background-color: transparent;\n}\n\n.navbar .nav > .active > a,\n.navbar .nav > .active > a:hover,\n.navbar .nav > .active > a:focus {\n    color: #555555;\n    text-decoration: none;\n    background-color: #e5e5e5;\n    -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);\n    -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);\n    box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);\n}\n\n.navbar .btn-navbar {\n    display: none;\n    float: right;\n    padding: 7px 10px;\n    margin-right: 5px;\n    margin-left: 5px;\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #ededed;\n    *background-color: #e5e5e5;\n    background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));\n    background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);\n    background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);\n    background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);\n    background-repeat: repeat-x;\n    border-color: #e5e5e5 #e5e5e5 #bfbfbf;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);\n    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);\n    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);\n}\n\n.navbar .btn-navbar:hover,\n.navbar .btn-navbar:focus,\n.navbar .btn-navbar:active,\n.navbar .btn-navbar.active,\n.navbar .btn-navbar.disabled,\n.navbar .btn-navbar[disabled] {\n    color: #ffffff;\n    background-color: #e5e5e5;\n    *background-color: #d9d9d9;\n}\n\n.navbar .btn-navbar:active,\n.navbar .btn-navbar.active {\n    background-color: #cccccc \\9;\n}\n\n.navbar .btn-navbar .icon-bar {\n    display: block;\n    width: 18px;\n    height: 2px;\n    background-color: #f5f5f5;\n    -webkit-border-radius: 1px;\n    -moz-border-radius: 1px;\n    border-radius: 1px;\n    -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);\n    -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);\n    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);\n}\n\n.btn-navbar .icon-bar + .icon-bar {\n    margin-top: 3px;\n}\n\n.navbar .nav > li > .dropdown-menu:before {\n    position: absolute;\n    top: -7px;\n    left: 9px;\n    display: inline-block;\n    border-right: 7px solid transparent;\n    border-bottom: 7px solid #ccc;\n    border-left: 7px solid transparent;\n    border-bottom-color: rgba(0, 0, 0, 0.2);\n    content: '';\n}\n\n.navbar .nav > li > .dropdown-menu:after {\n    position: absolute;\n    top: -6px;\n    left: 10px;\n    display: inline-block;\n    border-right: 6px solid transparent;\n    border-bottom: 6px solid #ffffff;\n    border-left: 6px solid transparent;\n    content: '';\n}\n\n.navbar-fixed-bottom .nav > li > .dropdown-menu:before {\n    top: auto;\n    bottom: -7px;\n    border-top: 7px solid #ccc;\n    border-bottom: 0;\n    border-top-color: rgba(0, 0, 0, 0.2);\n}\n\n.navbar-fixed-bottom .nav > li > .dropdown-menu:after {\n    top: auto;\n    bottom: -6px;\n    border-top: 6px solid #ffffff;\n    border-bottom: 0;\n}\n\n.navbar .nav li.dropdown > a:hover .caret,\n.navbar .nav li.dropdown > a:focus .caret {\n    border-top-color: #333333;\n    border-bottom-color: #333333;\n}\n\n.navbar .nav li.dropdown.open > .dropdown-toggle,\n.navbar .nav li.dropdown.active > .dropdown-toggle,\n.navbar .nav li.dropdown.open.active > .dropdown-toggle {\n    color: #555555;\n    background-color: #e5e5e5;\n}\n\n.navbar .nav li.dropdown > .dropdown-toggle .caret {\n    border-top-color: #777777;\n    border-bottom-color: #777777;\n}\n\n.navbar .nav li.dropdown.open > .dropdown-toggle .caret,\n.navbar .nav li.dropdown.active > .dropdown-toggle .caret,\n.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {\n    border-top-color: #555555;\n    border-bottom-color: #555555;\n}\n\n.navbar .pull-right > li > .dropdown-menu,\n.navbar .nav > li > .dropdown-menu.pull-right {\n    right: 0;\n    left: auto;\n}\n\n.navbar .pull-right > li > .dropdown-menu:before,\n.navbar .nav > li > .dropdown-menu.pull-right:before {\n    right: 12px;\n    left: auto;\n}\n\n.navbar .pull-right > li > .dropdown-menu:after,\n.navbar .nav > li > .dropdown-menu.pull-right:after {\n    right: 13px;\n    left: auto;\n}\n\n.navbar .pull-right > li > .dropdown-menu .dropdown-menu,\n.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {\n    right: 100%;\n    left: auto;\n    margin-right: -1px;\n    margin-left: 0;\n    -webkit-border-radius: 6px 0 6px 6px;\n    -moz-border-radius: 6px 0 6px 6px;\n    border-radius: 6px 0 6px 6px;\n}\n\n.navbar-inverse .navbar-inner {\n    background-color: #1b1b1b;\n    background-image: -moz-linear-gradient(top, #222222, #111111);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));\n    background-image: -webkit-linear-gradient(top, #222222, #111111);\n    background-image: -o-linear-gradient(top, #222222, #111111);\n    background-image: linear-gradient(to bottom, #222222, #111111);\n    background-repeat: repeat-x;\n    border-color: #252525;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);\n}\n\n.navbar-inverse .brand,\n.navbar-inverse .nav > li > a {\n    color: #999999;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n\n.navbar-inverse .brand:hover,\n.navbar-inverse .nav > li > a:hover,\n.navbar-inverse .brand:focus,\n.navbar-inverse .nav > li > a:focus {\n    color: #ffffff;\n}\n\n.navbar-inverse .brand {\n    color: #999999;\n}\n\n.navbar-inverse .navbar-text {\n    color: #999999;\n}\n\n.navbar-inverse .nav > li > a:focus,\n.navbar-inverse .nav > li > a:hover {\n    color: #ffffff;\n    background-color: transparent;\n}\n\n.navbar-inverse .nav .active > a,\n.navbar-inverse .nav .active > a:hover,\n.navbar-inverse .nav .active > a:focus {\n    color: #ffffff;\n    background-color: #111111;\n}\n\n.navbar-inverse .navbar-link {\n    color: #999999;\n}\n\n.navbar-inverse .navbar-link:hover,\n.navbar-inverse .navbar-link:focus {\n    color: #ffffff;\n}\n\n.navbar-inverse .divider-vertical {\n    border-right-color: #222222;\n    border-left-color: #111111;\n}\n\n.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,\n.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,\n.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {\n    color: #ffffff;\n    background-color: #111111;\n}\n\n.navbar-inverse .nav li.dropdown > a:hover .caret,\n.navbar-inverse .nav li.dropdown > a:focus .caret {\n    border-top-color: #ffffff;\n    border-bottom-color: #ffffff;\n}\n\n.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {\n    border-top-color: #999999;\n    border-bottom-color: #999999;\n}\n\n.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,\n.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,\n.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {\n    border-top-color: #ffffff;\n    border-bottom-color: #ffffff;\n}\n\n.navbar-inverse .navbar-search .search-query {\n    color: #ffffff;\n    background-color: #515151;\n    border-color: #111111;\n    -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);\n    -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);\n    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);\n    -webkit-transition: none;\n    -moz-transition: none;\n    -o-transition: none;\n    transition: none;\n}\n\n.navbar-inverse .navbar-search .search-query:-moz-placeholder {\n    color: #cccccc;\n}\n\n.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {\n    color: #cccccc;\n}\n\n.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {\n    color: #cccccc;\n}\n\n.navbar-inverse .navbar-search .search-query:focus,\n.navbar-inverse .navbar-search .search-query.focused {\n    padding: 5px 15px;\n    color: #333333;\n    text-shadow: 0 1px 0 #ffffff;\n    background-color: #ffffff;\n    border: 0;\n    outline: 0;\n    -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);\n    -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);\n    box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);\n}\n\n.navbar-inverse .btn-navbar {\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #0e0e0e;\n    *background-color: #040404;\n    background-image: -moz-linear-gradient(top, #151515, #040404);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));\n    background-image: -webkit-linear-gradient(top, #151515, #040404);\n    background-image: -o-linear-gradient(top, #151515, #040404);\n    background-image: linear-gradient(to bottom, #151515, #040404);\n    background-repeat: repeat-x;\n    border-color: #040404 #040404 #000000;\n    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);\n    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);\n}\n\n.navbar-inverse .btn-navbar:hover,\n.navbar-inverse .btn-navbar:focus,\n.navbar-inverse .btn-navbar:active,\n.navbar-inverse .btn-navbar.active,\n.navbar-inverse .btn-navbar.disabled,\n.navbar-inverse .btn-navbar[disabled] {\n    color: #ffffff;\n    background-color: #040404;\n    *background-color: #000000;\n}\n\n.navbar-inverse .btn-navbar:active,\n.navbar-inverse .btn-navbar.active {\n    background-color: #000000 \\9;\n}\n\n.breadcrumb {\n    padding: 8px 15px;\n    margin: 0 0 20px;\n    list-style: none;\n    background-color: #f5f5f5;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.breadcrumb > li {\n    display: inline-block;\n    *display: inline;\n    text-shadow: 0 1px 0 #ffffff;\n    *zoom: 1;\n}\n\n.breadcrumb > li > .divider {\n    padding: 0 5px;\n    color: #ccc;\n}\n\n.breadcrumb > .active {\n    color: #999999;\n}\n\n.pagination {\n    margin: 20px 0;\n}\n\n.pagination ul {\n    display: inline-block;\n    *display: inline;\n    margin-bottom: 0;\n    margin-left: 0;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    *zoom: 1;\n    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.pagination ul > li {\n    display: inline;\n}\n\n.pagination ul > li > a,\n.pagination ul > li > span {\n    float: left;\n    padding: 4px 12px;\n    line-height: 20px;\n    text-decoration: none;\n    background-color: #ffffff;\n    border: 1px solid #dddddd;\n    border-left-width: 0;\n}\n\n.pagination ul > li > a:hover,\n.pagination ul > li > a:focus,\n.pagination ul > .active > a,\n.pagination ul > .active > span {\n    background-color: #f5f5f5;\n}\n\n.pagination ul > .active > a,\n.pagination ul > .active > span {\n    color: #999999;\n    cursor: default;\n}\n\n.pagination ul > .disabled > span,\n.pagination ul > .disabled > a,\n.pagination ul > .disabled > a:hover,\n.pagination ul > .disabled > a:focus {\n    color: #999999;\n    cursor: default;\n    background-color: transparent;\n}\n\n.pagination ul > li:first-child > a,\n.pagination ul > li:first-child > span {\n    border-left-width: 1px;\n    -webkit-border-bottom-left-radius: 4px;\n    border-bottom-left-radius: 4px;\n    -webkit-border-top-left-radius: 4px;\n    border-top-left-radius: 4px;\n    -moz-border-radius-bottomleft: 4px;\n    -moz-border-radius-topleft: 4px;\n}\n\n.pagination ul > li:last-child > a,\n.pagination ul > li:last-child > span {\n    -webkit-border-top-right-radius: 4px;\n    border-top-right-radius: 4px;\n    -webkit-border-bottom-right-radius: 4px;\n    border-bottom-right-radius: 4px;\n    -moz-border-radius-topright: 4px;\n    -moz-border-radius-bottomright: 4px;\n}\n\n.pagination-centered {\n    text-align: center;\n}\n\n.pagination-right {\n    text-align: right;\n}\n\n.pagination-large ul > li > a,\n.pagination-large ul > li > span {\n    padding: 11px 19px;\n    font-size: 17.5px;\n}\n\n.pagination-large ul > li:first-child > a,\n.pagination-large ul > li:first-child > span {\n    -webkit-border-bottom-left-radius: 6px;\n    border-bottom-left-radius: 6px;\n    -webkit-border-top-left-radius: 6px;\n    border-top-left-radius: 6px;\n    -moz-border-radius-bottomleft: 6px;\n    -moz-border-radius-topleft: 6px;\n}\n\n.pagination-large ul > li:last-child > a,\n.pagination-large ul > li:last-child > span {\n    -webkit-border-top-right-radius: 6px;\n    border-top-right-radius: 6px;\n    -webkit-border-bottom-right-radius: 6px;\n    border-bottom-right-radius: 6px;\n    -moz-border-radius-topright: 6px;\n    -moz-border-radius-bottomright: 6px;\n}\n\n.pagination-mini ul > li:first-child > a,\n.pagination-small ul > li:first-child > a,\n.pagination-mini ul > li:first-child > span,\n.pagination-small ul > li:first-child > span {\n    -webkit-border-bottom-left-radius: 3px;\n    border-bottom-left-radius: 3px;\n    -webkit-border-top-left-radius: 3px;\n    border-top-left-radius: 3px;\n    -moz-border-radius-bottomleft: 3px;\n    -moz-border-radius-topleft: 3px;\n}\n\n.pagination-mini ul > li:last-child > a,\n.pagination-small ul > li:last-child > a,\n.pagination-mini ul > li:last-child > span,\n.pagination-small ul > li:last-child > span {\n    -webkit-border-top-right-radius: 3px;\n    border-top-right-radius: 3px;\n    -webkit-border-bottom-right-radius: 3px;\n    border-bottom-right-radius: 3px;\n    -moz-border-radius-topright: 3px;\n    -moz-border-radius-bottomright: 3px;\n}\n\n.pagination-small ul > li > a,\n.pagination-small ul > li > span {\n    padding: 2px 10px;\n    font-size: 11.9px;\n}\n\n.pagination-mini ul > li > a,\n.pagination-mini ul > li > span {\n    padding: 0 6px;\n    font-size: 10.5px;\n}\n\n.pager {\n    margin: 20px 0;\n    text-align: center;\n    list-style: none;\n    *zoom: 1;\n}\n\n.pager:before,\n.pager:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.pager:after {\n    clear: both;\n}\n\n.pager li {\n    display: inline;\n}\n\n.pager li > a,\n.pager li > span {\n    display: inline-block;\n    padding: 5px 14px;\n    background-color: #fff;\n    border: 1px solid #ddd;\n    -webkit-border-radius: 15px;\n    -moz-border-radius: 15px;\n    border-radius: 15px;\n}\n\n.pager li > a:hover,\n.pager li > a:focus {\n    text-decoration: none;\n    background-color: #f5f5f5;\n}\n\n.pager .next > a,\n.pager .next > span {\n    float: right;\n}\n\n.pager .previous > a,\n.pager .previous > span {\n    float: left;\n}\n\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n    color: #999999;\n    cursor: default;\n    background-color: #fff;\n}\n\n.modal-backdrop {\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    z-index: 1040;\n    background-color: #000000;\n}\n\n.modal-backdrop.fade {\n    opacity: 0;\n}\n\n.modal-backdrop,\n.modal-backdrop.fade.in {\n    opacity: 0.8;\n    filter: alpha(opacity=80);\n}\n\n.modal {\n    position: fixed;\n    top: 10%;\n    left: 50%;\n    z-index: 1050;\n    width: 560px;\n    margin-left: -280px;\n    background-color: #ffffff;\n    border: 1px solid #999;\n    border: 1px solid rgba(0, 0, 0, 0.3);\n    *border: 1px solid #999;\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n    outline: none;\n    -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);\n    -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);\n    box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);\n    -webkit-background-clip: padding-box;\n    -moz-background-clip: padding-box;\n    background-clip: padding-box;\n}\n\n.modal.fade {\n    top: -25%;\n    -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;\n    -moz-transition: opacity 0.3s linear, top 0.3s ease-out;\n    -o-transition: opacity 0.3s linear, top 0.3s ease-out;\n    transition: opacity 0.3s linear, top 0.3s ease-out;\n}\n\n.modal.fade.in {\n    top: 10%;\n}\n\n.modal-header {\n    padding: 9px 15px;\n    border-bottom: 1px solid #eee;\n}\n\n.modal-header .close {\n    margin-top: 2px;\n}\n\n.modal-header h3 {\n    margin: 0;\n    line-height: 30px;\n}\n\n.modal-body {\n    position: relative;\n    max-height: 400px;\n    padding: 15px;\n    overflow-y: auto;\n}\n\n.modal-form {\n    margin-bottom: 0;\n}\n\n.modal-footer {\n    padding: 14px 15px 15px;\n    margin-bottom: 0;\n    text-align: right;\n    background-color: #f5f5f5;\n    border-top: 1px solid #ddd;\n    -webkit-border-radius: 0 0 6px 6px;\n    -moz-border-radius: 0 0 6px 6px;\n    border-radius: 0 0 6px 6px;\n    *zoom: 1;\n    -webkit-box-shadow: inset 0 1px 0 #ffffff;\n    -moz-box-shadow: inset 0 1px 0 #ffffff;\n    box-shadow: inset 0 1px 0 #ffffff;\n}\n\n.modal-footer:before,\n.modal-footer:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.modal-footer:after {\n    clear: both;\n}\n\n.modal-footer .btn + .btn {\n    margin-bottom: 0;\n    margin-left: 5px;\n}\n\n.modal-footer .btn-group .btn + .btn {\n    margin-left: -1px;\n}\n\n.modal-footer .btn-block + .btn-block {\n    margin-left: 0;\n}\n\n.tooltip {\n    position: absolute;\n    z-index: 1030;\n    display: block;\n    font-size: 11px;\n    line-height: 1.4;\n    opacity: 0;\n    filter: alpha(opacity=0);\n    visibility: visible;\n}\n\n.tooltip.in {\n    opacity: 0.8;\n    filter: alpha(opacity=80);\n}\n\n.tooltip.top {\n    padding: 5px 0;\n    margin-top: -3px;\n}\n\n.tooltip.right {\n    padding: 0 5px;\n    margin-left: 3px;\n}\n\n.tooltip.bottom {\n    padding: 5px 0;\n    margin-top: 3px;\n}\n\n.tooltip.left {\n    padding: 0 5px;\n    margin-left: -3px;\n}\n\n.tooltip-inner {\n    max-width: 200px;\n    padding: 8px;\n    color: #ffffff;\n    text-align: center;\n    text-decoration: none;\n    background-color: #000000;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.tooltip-arrow {\n    position: absolute;\n    width: 0;\n    height: 0;\n    border-color: transparent;\n    border-style: solid;\n}\n\n.tooltip.top .tooltip-arrow {\n    bottom: 0;\n    left: 50%;\n    margin-left: -5px;\n    border-top-color: #000000;\n    border-width: 5px 5px 0;\n}\n\n.tooltip.right .tooltip-arrow {\n    top: 50%;\n    left: 0;\n    margin-top: -5px;\n    border-right-color: #000000;\n    border-width: 5px 5px 5px 0;\n}\n\n.tooltip.left .tooltip-arrow {\n    top: 50%;\n    right: 0;\n    margin-top: -5px;\n    border-left-color: #000000;\n    border-width: 5px 0 5px 5px;\n}\n\n.tooltip.bottom .tooltip-arrow {\n    top: 0;\n    left: 50%;\n    margin-left: -5px;\n    border-bottom-color: #000000;\n    border-width: 0 5px 5px;\n}\n\n.popover {\n    position: absolute;\n    top: 0;\n    left: 0;\n    z-index: 1010;\n    display: none;\n    max-width: 276px;\n    padding: 1px;\n    text-align: left;\n    white-space: normal;\n    background-color: #ffffff;\n    border: 1px solid #ccc;\n    border: 1px solid rgba(0, 0, 0, 0.2);\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n    -webkit-background-clip: padding-box;\n    -moz-background-clip: padding;\n    background-clip: padding-box;\n}\n\n.popover.top {\n    margin-top: -10px;\n}\n\n.popover.right {\n    margin-left: 10px;\n}\n\n.popover.bottom {\n    margin-top: 10px;\n}\n\n.popover.left {\n    margin-left: -10px;\n}\n\n.popover-title {\n    padding: 8px 14px;\n    margin: 0;\n    font-size: 14px;\n    font-weight: normal;\n    line-height: 18px;\n    background-color: #f7f7f7;\n    border-bottom: 1px solid #ebebeb;\n    -webkit-border-radius: 5px 5px 0 0;\n    -moz-border-radius: 5px 5px 0 0;\n    border-radius: 5px 5px 0 0;\n}\n\n.popover-title:empty {\n    display: none;\n}\n\n.popover-content {\n    padding: 9px 14px;\n}\n\n.popover .arrow,\n.popover .arrow:after {\n    position: absolute;\n    display: block;\n    width: 0;\n    height: 0;\n    border-color: transparent;\n    border-style: solid;\n}\n\n.popover .arrow {\n    border-width: 11px;\n}\n\n.popover .arrow:after {\n    border-width: 10px;\n    content: \"\";\n}\n\n.popover.top .arrow {\n    bottom: -11px;\n    left: 50%;\n    margin-left: -11px;\n    border-top-color: #999;\n    border-top-color: rgba(0, 0, 0, 0.25);\n    border-bottom-width: 0;\n}\n\n.popover.top .arrow:after {\n    bottom: 1px;\n    margin-left: -10px;\n    border-top-color: #ffffff;\n    border-bottom-width: 0;\n}\n\n.popover.right .arrow {\n    top: 50%;\n    left: -11px;\n    margin-top: -11px;\n    border-right-color: #999;\n    border-right-color: rgba(0, 0, 0, 0.25);\n    border-left-width: 0;\n}\n\n.popover.right .arrow:after {\n    bottom: -10px;\n    left: 1px;\n    border-right-color: #ffffff;\n    border-left-width: 0;\n}\n\n.popover.bottom .arrow {\n    top: -11px;\n    left: 50%;\n    margin-left: -11px;\n    border-bottom-color: #999;\n    border-bottom-color: rgba(0, 0, 0, 0.25);\n    border-top-width: 0;\n}\n\n.popover.bottom .arrow:after {\n    top: 1px;\n    margin-left: -10px;\n    border-bottom-color: #ffffff;\n    border-top-width: 0;\n}\n\n.popover.left .arrow {\n    top: 50%;\n    right: -11px;\n    margin-top: -11px;\n    border-left-color: #999;\n    border-left-color: rgba(0, 0, 0, 0.25);\n    border-right-width: 0;\n}\n\n.popover.left .arrow:after {\n    right: 1px;\n    bottom: -10px;\n    border-left-color: #ffffff;\n    border-right-width: 0;\n}\n\n.thumbnails {\n    margin-left: -20px;\n    list-style: none;\n    *zoom: 1;\n}\n\n.thumbnails:before,\n.thumbnails:after {\n    display: table;\n    line-height: 0;\n    content: \"\";\n}\n\n.thumbnails:after {\n    clear: both;\n}\n\n.row-fluid .thumbnails {\n    margin-left: 0;\n}\n\n.thumbnails > li {\n    float: left;\n    margin-bottom: 20px;\n    margin-left: 20px;\n}\n\n.thumbnail {\n    display: block;\n    padding: 4px;\n    line-height: 20px;\n    border: 1px solid #ddd;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);\n    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);\n    -webkit-transition: all 0.2s ease-in-out;\n    -moz-transition: all 0.2s ease-in-out;\n    -o-transition: all 0.2s ease-in-out;\n    transition: all 0.2s ease-in-out;\n}\n\na.thumbnail:hover,\na.thumbnail:focus {\n    border-color: #0088cc;\n    -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);\n    -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);\n    box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);\n}\n\n.thumbnail > img {\n    display: block;\n    max-width: 100%;\n    margin-right: auto;\n    margin-left: auto;\n}\n\n.thumbnail .caption {\n    padding: 9px;\n    color: #555555;\n}\n\n.media,\n.media-body {\n    overflow: hidden;\n    *overflow: visible;\n    zoom: 1;\n}\n\n.media,\n.media .media {\n    margin-top: 15px;\n}\n\n.media:first-child {\n    margin-top: 0;\n}\n\n.media-object {\n    display: block;\n}\n\n.media-heading {\n    margin: 0 0 5px;\n}\n\n.media > .pull-left {\n    margin-right: 10px;\n}\n\n.media > .pull-right {\n    margin-left: 10px;\n}\n\n.media-list {\n    margin-left: 0;\n    list-style: none;\n}\n\n.label,\n.badge {\n    display: inline-block;\n    padding: 2px 4px;\n    font-size: 11.844px;\n    font-weight: bold;\n    line-height: 14px;\n    color: #ffffff;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    white-space: nowrap;\n    vertical-align: baseline;\n    background-color: #999999;\n}\n\n.label {\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    border-radius: 3px;\n}\n\n.badge {\n    padding-right: 9px;\n    padding-left: 9px;\n    -webkit-border-radius: 9px;\n    -moz-border-radius: 9px;\n    border-radius: 9px;\n}\n\n.label:empty,\n.badge:empty {\n    display: none;\n}\n\na.label:hover,\na.label:focus,\na.badge:hover,\na.badge:focus {\n    color: #ffffff;\n    text-decoration: none;\n    cursor: pointer;\n}\n\n.label-important,\n.badge-important {\n    background-color: #b94a48;\n}\n\n.label-important[href],\n.badge-important[href] {\n    background-color: #953b39;\n}\n\n.label-warning,\n.badge-warning {\n    background-color: #f89406;\n}\n\n.label-warning[href],\n.badge-warning[href] {\n    background-color: #c67605;\n}\n\n.label-success,\n.badge-success {\n    background-color: #468847;\n}\n\n.label-success[href],\n.badge-success[href] {\n    background-color: #356635;\n}\n\n.label-info,\n.badge-info {\n    background-color: #3a87ad;\n}\n\n.label-info[href],\n.badge-info[href] {\n    background-color: #2d6987;\n}\n\n.label-inverse,\n.badge-inverse {\n    background-color: #333333;\n}\n\n.label-inverse[href],\n.badge-inverse[href] {\n    background-color: #1a1a1a;\n}\n\n.btn .label,\n.btn .badge {\n    position: relative;\n    top: -1px;\n}\n\n.btn-mini .label,\n.btn-mini .badge {\n    top: 0;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n    from {\n        background-position: 40px 0;\n    }\n    to {\n        background-position: 0 0;\n    }\n}\n\n@-moz-keyframes progress-bar-stripes {\n    from {\n        background-position: 40px 0;\n    }\n    to {\n        background-position: 0 0;\n    }\n}\n\n@-ms-keyframes progress-bar-stripes {\n    from {\n        background-position: 40px 0;\n    }\n    to {\n        background-position: 0 0;\n    }\n}\n\n@-o-keyframes progress-bar-stripes {\n    from {\n        background-position: 0 0;\n    }\n    to {\n        background-position: 40px 0;\n    }\n}\n\n@keyframes progress-bar-stripes {\n    from {\n        background-position: 40px 0;\n    }\n    to {\n        background-position: 0 0;\n    }\n}\n\n.progress {\n    height: 20px;\n    margin-bottom: 20px;\n    overflow: hidden;\n    background-color: #f7f7f7;\n    background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));\n    background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);\n    background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);\n    background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);\n    background-repeat: repeat-x;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);\n    -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n.progress .bar {\n    float: left;\n    width: 0;\n    height: 100%;\n    font-size: 12px;\n    color: #ffffff;\n    text-align: center;\n    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n    background-color: #0e90d2;\n    background-image: -moz-linear-gradient(top, #149bdf, #0480be);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));\n    background-image: -webkit-linear-gradient(top, #149bdf, #0480be);\n    background-image: -o-linear-gradient(top, #149bdf, #0480be);\n    background-image: linear-gradient(to bottom, #149bdf, #0480be);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);\n    -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n    -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n    box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n    -webkit-transition: width 0.6s ease;\n    -moz-transition: width 0.6s ease;\n    -o-transition: width 0.6s ease;\n    transition: width 0.6s ease;\n}\n\n.progress .bar + .bar {\n    -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n    -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n    box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n}\n\n.progress-striped .bar {\n    background-color: #149bdf;\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    -webkit-background-size: 40px 40px;\n    -moz-background-size: 40px 40px;\n    -o-background-size: 40px 40px;\n    background-size: 40px 40px;\n}\n\n.progress.active .bar {\n    -webkit-animation: progress-bar-stripes 2s linear infinite;\n    -moz-animation: progress-bar-stripes 2s linear infinite;\n    -ms-animation: progress-bar-stripes 2s linear infinite;\n    -o-animation: progress-bar-stripes 2s linear infinite;\n    animation: progress-bar-stripes 2s linear infinite;\n}\n\n.progress-danger .bar,\n.progress .bar-danger {\n    background-color: #dd514c;\n    background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));\n    background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);\n    background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);\n    background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);\n}\n\n.progress-danger.progress-striped .bar,\n.progress-striped .bar-danger {\n    background-color: #ee5f5b;\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n\n.progress-success .bar,\n.progress .bar-success {\n    background-color: #5eb95e;\n    background-image: -moz-linear-gradient(top, #62c462, #57a957);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));\n    background-image: -webkit-linear-gradient(top, #62c462, #57a957);\n    background-image: -o-linear-gradient(top, #62c462, #57a957);\n    background-image: linear-gradient(to bottom, #62c462, #57a957);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);\n}\n\n.progress-success.progress-striped .bar,\n.progress-striped .bar-success {\n    background-color: #62c462;\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n\n.progress-info .bar,\n.progress .bar-info {\n    background-color: #4bb1cf;\n    background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));\n    background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);\n    background-image: -o-linear-gradient(top, #5bc0de, #339bb9);\n    background-image: linear-gradient(to bottom, #5bc0de, #339bb9);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);\n}\n\n.progress-info.progress-striped .bar,\n.progress-striped .bar-info {\n    background-color: #5bc0de;\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n\n.progress-warning .bar,\n.progress .bar-warning {\n    background-color: #faa732;\n    background-image: -moz-linear-gradient(top, #fbb450, #f89406);\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));\n    background-image: -webkit-linear-gradient(top, #fbb450, #f89406);\n    background-image: -o-linear-gradient(top, #fbb450, #f89406);\n    background-image: linear-gradient(to bottom, #fbb450, #f89406);\n    background-repeat: repeat-x;\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);\n}\n\n.progress-warning.progress-striped .bar,\n.progress-striped .bar-warning {\n    background-color: #fbb450;\n    background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));\n    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n\n.accordion {\n    margin-bottom: 20px;\n}\n\n.accordion-group {\n    margin-bottom: 2px;\n    border: 1px solid #e5e5e5;\n    -webkit-border-radius: 4px;\n    -moz-border-radius: 4px;\n    border-radius: 4px;\n}\n\n.accordion-heading {\n    border-bottom: 0;\n}\n\n.accordion-heading .accordion-toggle {\n    display: block;\n    padding: 8px 15px;\n}\n\n.accordion-toggle {\n    cursor: pointer;\n}\n\n.accordion-inner {\n    padding: 9px 15px;\n    border-top: 1px solid #e5e5e5;\n}\n\n.carousel {\n    position: relative;\n    margin-bottom: 20px;\n    line-height: 1;\n}\n\n.carousel-inner {\n    position: relative;\n    width: 100%;\n    overflow: hidden;\n}\n\n.carousel-inner > .item {\n    position: relative;\n    display: none;\n    -webkit-transition: 0.6s ease-in-out left;\n    -moz-transition: 0.6s ease-in-out left;\n    -o-transition: 0.6s ease-in-out left;\n    transition: 0.6s ease-in-out left;\n}\n\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n    display: block;\n    line-height: 1;\n}\n\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n    display: block;\n}\n\n.carousel-inner > .active {\n    left: 0;\n}\n\n.carousel-inner > .next,\n.carousel-inner > .prev {\n    position: absolute;\n    top: 0;\n    width: 100%;\n}\n\n.carousel-inner > .next {\n    left: 100%;\n}\n\n.carousel-inner > .prev {\n    left: -100%;\n}\n\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n    left: 0;\n}\n\n.carousel-inner > .active.left {\n    left: -100%;\n}\n\n.carousel-inner > .active.right {\n    left: 100%;\n}\n\n.carousel-control {\n    position: absolute;\n    top: 40%;\n    left: 15px;\n    width: 40px;\n    height: 40px;\n    margin-top: -20px;\n    font-size: 60px;\n    font-weight: 100;\n    line-height: 30px;\n    color: #ffffff;\n    text-align: center;\n    background: #222222;\n    border: 3px solid #ffffff;\n    -webkit-border-radius: 23px;\n    -moz-border-radius: 23px;\n    border-radius: 23px;\n    opacity: 0.5;\n    filter: alpha(opacity=50);\n}\n\n.carousel-control.right {\n    right: 15px;\n    left: auto;\n}\n\n.carousel-control:hover,\n.carousel-control:focus {\n    color: #ffffff;\n    text-decoration: none;\n    opacity: 0.9;\n    filter: alpha(opacity=90);\n}\n\n.carousel-indicators {\n    position: absolute;\n    top: 15px;\n    right: 15px;\n    z-index: 5;\n    margin: 0;\n    list-style: none;\n}\n\n.carousel-indicators li {\n    display: block;\n    float: left;\n    width: 10px;\n    height: 10px;\n    margin-left: 5px;\n    text-indent: -999px;\n    background-color: #ccc;\n    background-color: rgba(255, 255, 255, 0.25);\n    border-radius: 5px;\n}\n\n.carousel-indicators .active {\n    background-color: #fff;\n}\n\n.carousel-caption {\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    padding: 15px;\n    background: #333333;\n    background: rgba(0, 0, 0, 0.75);\n}\n\n.carousel-caption h4,\n.carousel-caption p {\n    line-height: 20px;\n    color: #ffffff;\n}\n\n.carousel-caption h4 {\n    margin: 0 0 5px;\n}\n\n.carousel-caption p {\n    margin-bottom: 0;\n}\n\n.hero-unit {\n    padding: 60px;\n    margin-bottom: 30px;\n    font-size: 18px;\n    font-weight: 200;\n    line-height: 30px;\n    color: inherit;\n    background-color: #eeeeee;\n    -webkit-border-radius: 6px;\n    -moz-border-radius: 6px;\n    border-radius: 6px;\n}\n\n.hero-unit h1 {\n    margin-bottom: 0;\n    font-size: 60px;\n    line-height: 1;\n    letter-spacing: -1px;\n    color: inherit;\n}\n\n.hero-unit li {\n    line-height: 30px;\n}\n\n.pull-right {\n    float: right;\n}\n\n.pull-left {\n    float: left;\n}\n\n.hide {\n    display: none;\n}\n\n.show {\n    display: block;\n}\n\n.invisible {\n    visibility: hidden;\n}\n\n.affix {\n    position: fixed;\n}\n"
  },
  {
    "path": "public/libs/bootstrap/js/bootstrap.js",
    "content": "/* ===================================================\n * bootstrap-transition.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#transitions\n * ===================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)\n   * ======================================================= */\n\n  $(function () {\n\n    $.support.transition = (function () {\n\n      var transitionEnd = (function () {\n\n        var el = document.createElement('bootstrap')\n          , transEndEventNames = {\n            'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd otransitionend', 'transition': 'transitionend'\n          }\n          , name\n\n        for (name in transEndEventNames) {\n          if (el.style[name] !== undefined) {\n            return transEndEventNames[name]\n          }\n        }\n\n      }())\n\n      return transitionEnd && {\n        end: transitionEnd\n      }\n\n    })()\n\n  })\n\n}(window.jQuery);\n/* ==========================================================\n * bootstrap-alert.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#alerts\n * ==========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* ALERT CLASS DEFINITION\n   * ====================== */\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n    , Alert = function (el) {\n      $(el).on('click', dismiss, this.close)\n    }\n\n  Alert.prototype.close = function (e) {\n    var $this = $(this)\n      , selector = $this.attr('data-target')\n      , $parent\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') //strip for ie7\n    }\n\n    $parent = $(selector)\n\n    e && e.preventDefault()\n\n    $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())\n\n    $parent.trigger(e = $.Event('close'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      $parent\n        .trigger('closed')\n        .remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent.on($.support.transition.end, removeElement) :\n      removeElement()\n  }\n\n\n  /* ALERT PLUGIN DEFINITION\n   * ======================= */\n\n  var old = $.fn.alert\n\n  $.fn.alert = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('alert')\n      if (!data) $this.data('alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  $.fn.alert.Constructor = Alert\n\n\n  /* ALERT NO CONFLICT\n   * ================= */\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  /* ALERT DATA-API\n   * ============== */\n\n  $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)\n\n}(window.jQuery);\n/* ============================================================\n * bootstrap-button.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#buttons\n * ============================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ============================================================ */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* BUTTON PUBLIC CLASS DEFINITION\n   * ============================== */\n\n  var Button = function (element, options) {\n    this.$element = $(element)\n    this.options = $.extend({}, $.fn.button.defaults, options)\n  }\n\n  Button.prototype.setState = function (state) {\n    var d = 'disabled'\n      , $el = this.$element\n      , data = $el.data()\n      , val = $el.is('input') ? 'val' : 'html'\n\n    state = state + 'Text'\n    data.resetText || $el.data('resetText', $el[val]())\n\n    $el[val](data[state] || this.options[state])\n\n    // push to event loop to allow forms to submit\n    setTimeout(function () {\n      state == 'loadingText' ?\n        $el.addClass(d).attr(d, d) :\n        $el.removeClass(d).removeAttr(d)\n    }, 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var $parent = this.$element.closest('[data-toggle=\"buttons-radio\"]')\n\n    $parent && $parent\n      .find('.active')\n      .removeClass('active')\n\n    this.$element.toggleClass('active')\n  }\n\n\n  /* BUTTON PLUGIN DEFINITION\n   * ======================== */\n\n  var old = $.fn.button\n\n  $.fn.button = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('button')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('button', (data = new Button(this, options)))\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  $.fn.button.defaults = {\n    loadingText: 'loading...'\n  }\n\n  $.fn.button.Constructor = Button\n\n\n  /* BUTTON NO CONFLICT\n   * ================== */\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  /* BUTTON DATA-API\n   * =============== */\n\n  $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {\n    var $btn = $(e.target)\n    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n    $btn.button('toggle')\n  })\n\n}(window.jQuery);\n/* ==========================================================\n * bootstrap-carousel.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#carousel\n * ==========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* CAROUSEL CLASS DEFINITION\n   * ========================= */\n\n  var Carousel = function (element, options) {\n    this.$element = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options = options\n    this.options.pause == 'hover' && this.$element\n      .on('mouseenter', $.proxy(this.pause, this))\n      .on('mouseleave', $.proxy(this.cycle, this))\n  }\n\n  Carousel.prototype = {\n\n    cycle: function (e) {\n      if (!e) this.paused = false\n      if (this.interval) clearInterval(this.interval);\n      this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n      return this\n    }, getActiveIndex: function () {\n      this.$active = this.$element.find('.item.active')\n      this.$items = this.$active.parent().children()\n      return this.$items.index(this.$active)\n    }, to: function (pos) {\n      var activeIndex = this.getActiveIndex()\n        , that = this\n\n      if (pos > (this.$items.length - 1) || pos < 0) return\n\n      if (this.sliding) {\n        return this.$element.one('slid', function () {\n          that.to(pos)\n        })\n      }\n\n      if (activeIndex == pos) {\n        return this.pause().cycle()\n      }\n\n      return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))\n    }, pause: function (e) {\n      if (!e) this.paused = true\n      if (this.$element.find('.next, .prev').length && $.support.transition.end) {\n        this.$element.trigger($.support.transition.end)\n        this.cycle(true)\n      }\n      clearInterval(this.interval)\n      this.interval = null\n      return this\n    }, next: function () {\n      if (this.sliding) return\n      return this.slide('next')\n    }, prev: function () {\n      if (this.sliding) return\n      return this.slide('prev')\n    }, slide: function (type, next) {\n      var $active = this.$element.find('.item.active')\n        , $next = next || $active[type]()\n        , isCycling = this.interval\n        , direction = type == 'next' ? 'left' : 'right'\n        , fallback = type == 'next' ? 'first' : 'last'\n        , that = this\n        , e\n\n      this.sliding = true\n\n      isCycling && this.pause()\n\n      $next = $next.length ? $next : this.$element.find('.item')[fallback]()\n\n      e = $.Event('slide', {\n        relatedTarget: $next[0], direction: direction\n      })\n\n      if ($next.hasClass('active')) return\n\n      if (this.$indicators.length) {\n        this.$indicators.find('.active').removeClass('active')\n        this.$element.one('slid', function () {\n          var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])\n          $nextIndicator && $nextIndicator.addClass('active')\n        })\n      }\n\n      if ($.support.transition && this.$element.hasClass('slide')) {\n        this.$element.trigger(e)\n        if (e.isDefaultPrevented()) return\n        $next.addClass(type)\n        $next[0].offsetWidth // force reflow\n        $active.addClass(direction)\n        $next.addClass(direction)\n        this.$element.one($.support.transition.end, function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger('slid')\n          }, 0)\n        })\n      } else {\n        this.$element.trigger(e)\n        if (e.isDefaultPrevented()) return\n        $active.removeClass('active')\n        $next.addClass('active')\n        this.sliding = false\n        this.$element.trigger('slid')\n      }\n\n      isCycling && this.cycle()\n\n      return this\n    }\n\n  }\n\n\n  /* CAROUSEL PLUGIN DEFINITION\n   * ========================== */\n\n  var old = $.fn.carousel\n\n  $.fn.carousel = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('carousel')\n        , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)\n        , action = typeof option == 'string' ? option : options.slide\n      if (!data) $this.data('carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  $.fn.carousel.defaults = {\n    interval: 5000, pause: 'hover'\n  }\n\n  $.fn.carousel.Constructor = Carousel\n\n\n  /* CAROUSEL NO CONFLICT\n   * ==================== */\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n  /* CAROUSEL DATA-API\n   * ================= */\n\n  $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {\n    var $this = $(this), href\n      , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) //strip for ie7\n      , options = $.extend({}, $target.data(), $this.data())\n      , slideIndex\n\n    $target.carousel(options)\n\n    if (slideIndex = $this.attr('data-slide-to')) {\n      $target.data('carousel').pause().to(slideIndex).cycle()\n    }\n\n    e.preventDefault()\n  })\n\n}(window.jQuery);\n/* =============================================================\n * bootstrap-collapse.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#collapse\n * =============================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ============================================================ */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* COLLAPSE PUBLIC CLASS DEFINITION\n   * ================================ */\n\n  var Collapse = function (element, options) {\n    this.$element = $(element)\n    this.options = $.extend({}, $.fn.collapse.defaults, options)\n\n    if (this.options.parent) {\n      this.$parent = $(this.options.parent)\n    }\n\n    this.options.toggle && this.toggle()\n  }\n\n  Collapse.prototype = {\n\n    constructor: Collapse, dimension: function () {\n      var hasWidth = this.$element.hasClass('width')\n      return hasWidth ? 'width' : 'height'\n    }, show: function () {\n      var dimension\n        , scroll\n        , actives\n        , hasData\n\n      if (this.transitioning || this.$element.hasClass('in')) return\n\n      dimension = this.dimension()\n      scroll = $.camelCase(['scroll', dimension].join('-'))\n      actives = this.$parent && this.$parent.find('> .accordion-group > .in')\n\n      if (actives && actives.length) {\n        hasData = actives.data('collapse')\n        if (hasData && hasData.transitioning) return\n        actives.collapse('hide')\n        hasData || actives.data('collapse', null)\n      }\n\n      this.$element[dimension](0)\n      this.transition('addClass', $.Event('show'), 'shown')\n      $.support.transition && this.$element[dimension](this.$element[0][scroll])\n    }, hide: function () {\n      var dimension\n      if (this.transitioning || !this.$element.hasClass('in')) return\n      dimension = this.dimension()\n      this.reset(this.$element[dimension]())\n      this.transition('removeClass', $.Event('hide'), 'hidden')\n      this.$element[dimension](0)\n    }, reset: function (size) {\n      var dimension = this.dimension()\n\n      this.$element\n        .removeClass('collapse')\n        [dimension](size || 'auto')\n        [0].offsetWidth\n\n      this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')\n\n      return this\n    }, transition: function (method, startEvent, completeEvent) {\n      var that = this\n        , complete = function () {\n          if (startEvent.type == 'show') that.reset()\n          that.transitioning = 0\n          that.$element.trigger(completeEvent)\n        }\n\n      this.$element.trigger(startEvent)\n\n      if (startEvent.isDefaultPrevented()) return\n\n      this.transitioning = 1\n\n      this.$element[method]('in')\n\n      $.support.transition && this.$element.hasClass('collapse') ?\n        this.$element.one($.support.transition.end, complete) :\n        complete()\n    }, toggle: function () {\n      this[this.$element.hasClass('in') ? 'hide' : 'show']()\n    }\n\n  }\n\n\n  /* COLLAPSE PLUGIN DEFINITION\n   * ========================== */\n\n  var old = $.fn.collapse\n\n  $.fn.collapse = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('collapse')\n        , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option)\n      if (!data) $this.data('collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.collapse.defaults = {\n    toggle: true\n  }\n\n  $.fn.collapse.Constructor = Collapse\n\n\n  /* COLLAPSE NO CONFLICT\n   * ==================== */\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  /* COLLAPSE DATA-API\n   * ================= */\n\n  $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {\n    var $this = $(this), href\n      , target = $this.attr('data-target')\n        || e.preventDefault()\n        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') //strip for ie7\n      , option = $(target).data('collapse') ? 'toggle' : $this.data()\n    $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')\n    $(target).collapse(option)\n  })\n\n}(window.jQuery);\n/* ============================================================\n * bootstrap-dropdown.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#dropdowns\n * ============================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ============================================================ */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* DROPDOWN CLASS DEFINITION\n   * ========================= */\n\n  var toggle = '[data-toggle=dropdown]'\n    , Dropdown = function (element) {\n      var $el = $(element).on('click.dropdown.data-api', this.toggle)\n      $('html').on('click.dropdown.data-api', function () {\n        $el.parent().removeClass('open')\n      })\n    }\n\n  Dropdown.prototype = {\n\n    constructor: Dropdown, toggle: function (e) {\n      var $this = $(this)\n        , $parent\n        , isActive\n\n      if ($this.is('.disabled, :disabled')) return\n\n      $parent = getParent($this)\n\n      isActive = $parent.hasClass('open')\n\n      clearMenus()\n\n      if (!isActive) {\n        $parent.toggleClass('open')\n      }\n\n      $this.focus()\n\n      return false\n    }, keydown: function (e) {\n      var $this\n        , $items\n        , $active\n        , $parent\n        , isActive\n        , index\n\n      if (!/(38|40|27)/.test(e.keyCode)) return\n\n      $this = $(this)\n\n      e.preventDefault()\n      e.stopPropagation()\n\n      if ($this.is('.disabled, :disabled')) return\n\n      $parent = getParent($this)\n\n      isActive = $parent.hasClass('open')\n\n      if (!isActive || (isActive && e.keyCode == 27)) {\n        if (e.which == 27) $parent.find(toggle).focus()\n        return $this.click()\n      }\n\n      $items = $('[role=menu] li:not(.divider):visible a', $parent)\n\n      if (!$items.length) return\n\n      index = $items.index($items.filter(':focus'))\n\n      if (e.keyCode == 38 && index > 0) index--                                        // up\n      if (e.keyCode == 40 && index < $items.length - 1) index++                        // down\n      if (!~index) index = 0\n\n      $items\n        .eq(index)\n        .focus()\n    }\n\n  }\n\n  function clearMenus() {\n    $(toggle).each(function () {\n      getParent($(this)).removeClass('open')\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n      , $parent\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') //strip for ie7\n    }\n\n    $parent = selector && $(selector)\n\n    if (!$parent || !$parent.length) $parent = $this.parent()\n\n    return $parent\n  }\n\n\n  /* DROPDOWN PLUGIN DEFINITION\n   * ========================== */\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('dropdown')\n      if (!data) $this.data('dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  /* DROPDOWN NO CONFLICT\n   * ==================== */\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  /* APPLY TO STANDARD DROPDOWN ELEMENTS\n   * =================================== */\n\n  $(document)\n    .on('click.dropdown.data-api', clearMenus)\n    .on('click.dropdown.data-api', '.dropdown form', function (e) {\n      e.stopPropagation()\n    })\n    .on('click.dropdown-menu', function (e) {\n      e.stopPropagation()\n    })\n    .on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.dropdown.data-api', toggle + ', [role=menu]', Dropdown.prototype.keydown)\n\n}(window.jQuery);\n/* =========================================================\n * bootstrap-modal.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#modals\n * =========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================= */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* MODAL CLASS DEFINITION\n   * ====================== */\n\n  var Modal = function (element, options) {\n    this.options = options\n    this.$element = $(element)\n      .delegate('[data-dismiss=\"modal\"]', 'click.dismiss.modal', $.proxy(this.hide, this))\n    this.options.remote && this.$element.find('.modal-body').load(this.options.remote)\n  }\n\n  Modal.prototype = {\n\n    constructor: Modal, toggle: function () {\n      return this[!this.isShown ? 'show' : 'hide']()\n    }, show: function () {\n      var that = this\n        , e = $.Event('show')\n\n      this.$element.trigger(e)\n\n      if (this.isShown || e.isDefaultPrevented()) return\n\n      this.isShown = true\n\n      this.escape()\n\n      this.backdrop(function () {\n        var transition = $.support.transition && that.$element.hasClass('fade')\n\n        if (!that.$element.parent().length) {\n          that.$element.appendTo(document.body) //don't move modals dom position\n        }\n\n        that.$element.show()\n\n        if (transition) {\n          that.$element[0].offsetWidth // force reflow\n        }\n\n        that.$element\n          .addClass('in')\n          .attr('aria-hidden', false)\n\n        that.enforceFocus()\n\n        transition ?\n          that.$element.one($.support.transition.end, function () {\n            that.$element.focus().trigger('shown')\n          }) :\n          that.$element.focus().trigger('shown')\n\n      })\n    }, hide: function (e) {\n      e && e.preventDefault()\n\n      var that = this\n\n      e = $.Event('hide')\n\n      this.$element.trigger(e)\n\n      if (!this.isShown || e.isDefaultPrevented()) return\n\n      this.isShown = false\n\n      this.escape()\n\n      $(document).off('focusin.modal')\n\n      this.$element\n        .removeClass('in')\n        .attr('aria-hidden', true)\n\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.hideWithTransition() :\n        this.hideModal()\n    }, enforceFocus: function () {\n      var that = this\n      $(document).on('focusin.modal', function (e) {\n        if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {\n          that.$element.focus()\n        }\n      })\n    }, escape: function () {\n      var that = this\n      if (this.isShown && this.options.keyboard) {\n        this.$element.on('keyup.dismiss.modal', function (e) {\n          e.which == 27 && that.hide()\n        })\n      } else if (!this.isShown) {\n        this.$element.off('keyup.dismiss.modal')\n      }\n    }, hideWithTransition: function () {\n      var that = this\n        , timeout = setTimeout(function () {\n          that.$element.off($.support.transition.end)\n          that.hideModal()\n        }, 500)\n\n      this.$element.one($.support.transition.end, function () {\n        clearTimeout(timeout)\n        that.hideModal()\n      })\n    }, hideModal: function () {\n      var that = this\n      this.$element.hide()\n      this.backdrop(function () {\n        that.removeBackdrop()\n        that.$element.trigger('hidden')\n      })\n    }, removeBackdrop: function () {\n      this.$backdrop && this.$backdrop.remove()\n      this.$backdrop = null\n    }, backdrop: function (callback) {\n      var that = this\n        , animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n      if (this.isShown && this.options.backdrop) {\n        var doAnimate = $.support.transition && animate\n\n        this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n          .appendTo(document.body)\n\n        this.$backdrop.click(\n            this.options.backdrop == 'static' ?\n            $.proxy(this.$element[0].focus, this.$element[0])\n            : $.proxy(this.hide, this)\n        )\n\n        if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n        this.$backdrop.addClass('in')\n\n        if (!callback) return\n\n        doAnimate ?\n          this.$backdrop.one($.support.transition.end, callback) :\n          callback()\n\n      } else if (!this.isShown && this.$backdrop) {\n        this.$backdrop.removeClass('in')\n\n        $.support.transition && this.$element.hasClass('fade') ?\n          this.$backdrop.one($.support.transition.end, callback) :\n          callback()\n\n      } else if (callback) {\n        callback()\n      }\n    }\n  }\n\n\n  /* MODAL PLUGIN DEFINITION\n   * ======================= */\n\n  var old = $.fn.modal\n\n  $.fn.modal = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('modal')\n        , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)\n      if (!data) $this.data('modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option]()\n      else if (options.show) data.show()\n    })\n  }\n\n  $.fn.modal.defaults = {\n    backdrop: true, keyboard: true, show: true\n  }\n\n  $.fn.modal.Constructor = Modal\n\n\n  /* MODAL NO CONFLICT\n   * ================= */\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  /* MODAL DATA-API\n   * ============== */\n\n  $(document).on('click.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this = $(this)\n      , href = $this.attr('href')\n      , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) //strip for ie7\n      , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    e.preventDefault()\n\n    $target\n      .modal(option)\n      .one('hide', function () {\n        $this.focus()\n      })\n  })\n\n}(window.jQuery);\n/* ===========================================================\n * bootstrap-tooltip.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#tooltips\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ===========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* TOOLTIP PUBLIC CLASS DEFINITION\n   * =============================== */\n\n  var Tooltip = function (element, options) {\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.prototype = {\n\n    constructor: Tooltip, init: function (type, element, options) {\n      var eventIn\n        , eventOut\n        , triggers\n        , trigger\n        , i\n\n      this.type = type\n      this.$element = $(element)\n      this.options = this.getOptions(options)\n      this.enabled = true\n\n      triggers = this.options.trigger.split(' ')\n\n      for (i = triggers.length; i--;) {\n        trigger = triggers[i]\n        if (trigger == 'click') {\n          this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n        } else if (trigger != 'manual') {\n          eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'\n          eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'\n          this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n          this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n        }\n      }\n\n      this.options.selector ?\n        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n        this.fixTitle()\n    }, getOptions: function (options) {\n      options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)\n\n      if (options.delay && typeof options.delay == 'number') {\n        options.delay = {\n          show: options.delay, hide: options.delay\n        }\n      }\n\n      return options\n    }, enter: function (e) {\n      var defaults = $.fn[this.type].defaults\n        , options = {}\n        , self\n\n      this._options && $.each(this._options, function (key, value) {\n        if (defaults[key] != value) options[key] = value\n      }, this)\n\n      self = $(e.currentTarget)[this.type](options).data(this.type)\n\n      if (!self.options.delay || !self.options.delay.show) return self.show()\n\n      clearTimeout(this.timeout)\n      self.hoverState = 'in'\n      this.timeout = setTimeout(function () {\n        if (self.hoverState == 'in') self.show()\n      }, self.options.delay.show)\n    }, leave: function (e) {\n      var self = $(e.currentTarget)[this.type](this._options).data(this.type)\n\n      if (this.timeout) clearTimeout(this.timeout)\n      if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n      self.hoverState = 'out'\n      this.timeout = setTimeout(function () {\n        if (self.hoverState == 'out') self.hide()\n      }, self.options.delay.hide)\n    }, show: function () {\n      var $tip\n        , pos\n        , actualWidth\n        , actualHeight\n        , placement\n        , tp\n        , e = $.Event('show')\n\n      if (this.hasContent() && this.enabled) {\n        this.$element.trigger(e)\n        if (e.isDefaultPrevented()) return\n        $tip = this.tip()\n        this.setContent()\n\n        if (this.options.animation) {\n          $tip.addClass('fade')\n        }\n\n        placement = typeof this.options.placement == 'function' ?\n          this.options.placement.call(this, $tip[0], this.$element[0]) :\n          this.options.placement\n\n        $tip\n          .detach()\n          .css({ top: 0, left: 0, display: 'block' })\n\n        this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n        pos = this.getPosition()\n\n        actualWidth = $tip[0].offsetWidth\n        actualHeight = $tip[0].offsetHeight\n\n        switch (placement) {\n          case 'bottom':\n            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}\n            break\n          case 'top':\n            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}\n            break\n          case 'left':\n            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}\n            break\n          case 'right':\n            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}\n            break\n        }\n\n        this.applyPlacement(tp, placement)\n        this.$element.trigger('shown')\n      }\n    }, applyPlacement: function (offset, placement) {\n      var $tip = this.tip()\n        , width = $tip[0].offsetWidth\n        , height = $tip[0].offsetHeight\n        , actualWidth\n        , actualHeight\n        , delta\n        , replace\n\n      $tip\n        .offset(offset)\n        .addClass(placement)\n        .addClass('in')\n\n      actualWidth = $tip[0].offsetWidth\n      actualHeight = $tip[0].offsetHeight\n\n      if (placement == 'top' && actualHeight != height) {\n        offset.top = offset.top + height - actualHeight\n        replace = true\n      }\n\n      if (placement == 'bottom' || placement == 'top') {\n        delta = 0\n\n        if (offset.left < 0) {\n          delta = offset.left * -2\n          offset.left = 0\n          $tip.offset(offset)\n          actualWidth = $tip[0].offsetWidth\n          actualHeight = $tip[0].offsetHeight\n        }\n\n        this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')\n      } else {\n        this.replaceArrow(actualHeight - height, actualHeight, 'top')\n      }\n\n      if (replace) $tip.offset(offset)\n    }, replaceArrow: function (delta, dimension, position) {\n      this\n        .arrow()\n        .css(position, delta ? (50 * (1 - delta / dimension) + \"%\") : '')\n    }, setContent: function () {\n      var $tip = this.tip()\n        , title = this.getTitle()\n\n      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n      $tip.removeClass('fade in top bottom left right')\n    }, hide: function () {\n      var that = this\n        , $tip = this.tip()\n        , e = $.Event('hide')\n\n      this.$element.trigger(e)\n      if (e.isDefaultPrevented()) return\n\n      $tip.removeClass('in')\n\n      function removeWithAnimation() {\n        var timeout = setTimeout(function () {\n          $tip.off($.support.transition.end).detach()\n        }, 500)\n\n        $tip.one($.support.transition.end, function () {\n          clearTimeout(timeout)\n          $tip.detach()\n        })\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        removeWithAnimation() :\n        $tip.detach()\n\n      this.$element.trigger('hidden')\n\n      return this\n    }, fixTitle: function () {\n      var $e = this.$element\n      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {\n        $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n      }\n    }, hasContent: function () {\n      return this.getTitle()\n    }, getPosition: function () {\n      var el = this.$element[0]\n      return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {\n        width: el.offsetWidth, height: el.offsetHeight\n      }, this.$element.offset())\n    }, getTitle: function () {\n      var title\n        , $e = this.$element\n        , o = this.options\n\n      title = $e.attr('data-original-title')\n        || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)\n\n      return title\n    }, tip: function () {\n      return this.$tip = this.$tip || $(this.options.template)\n    }, arrow: function () {\n      return this.$arrow = this.$arrow || this.tip().find(\".tooltip-arrow\")\n    }, validate: function () {\n      if (!this.$element[0].parentNode) {\n        this.hide()\n        this.$element = null\n        this.options = null\n      }\n    }, enable: function () {\n      this.enabled = true\n    }, disable: function () {\n      this.enabled = false\n    }, toggleEnabled: function () {\n      this.enabled = !this.enabled\n    }, toggle: function (e) {\n      var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this\n      self.tip().hasClass('in') ? self.hide() : self.show()\n    }, destroy: function () {\n      this.hide().$element.off('.' + this.type).removeData(this.type)\n    }\n\n  }\n\n\n  /* TOOLTIP PLUGIN DEFINITION\n   * ========================= */\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('tooltip')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.tooltip.Constructor = Tooltip\n\n  $.fn.tooltip.defaults = {\n    animation: true, placement: 'top', selector: false, template: '<div class=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>', trigger: 'hover focus', title: '', delay: 0, html: false, container: false\n  }\n\n\n  /* TOOLTIP NO CONFLICT\n   * =================== */\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(window.jQuery);\n/* ===========================================================\n * bootstrap-popover.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#popovers\n * ===========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* POPOVER PUBLIC CLASS DEFINITION\n   * =============================== */\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n\n  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js\n   ========================================== */\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {\n\n    constructor: Popover, setContent: function () {\n      var $tip = this.tip()\n        , title = this.getTitle()\n        , content = this.getContent()\n\n      $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n      $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)\n\n      $tip.removeClass('fade top bottom left right in')\n    }, hasContent: function () {\n      return this.getTitle() || this.getContent()\n    }, getContent: function () {\n      var content\n        , $e = this.$element\n        , o = this.options\n\n      content = (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)\n        || $e.attr('data-content')\n\n      return content\n    }, tip: function () {\n      if (!this.$tip) {\n        this.$tip = $(this.options.template)\n      }\n      return this.$tip\n    }, destroy: function () {\n      this.hide().$element.off('.' + this.type).removeData(this.type)\n    }\n\n  })\n\n\n  /* POPOVER PLUGIN DEFINITION\n   * ======================= */\n\n  var old = $.fn.popover\n\n  $.fn.popover = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('popover')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.popover.Constructor = Popover\n\n  $.fn.popover.defaults = $.extend({}, $.fn.tooltip.defaults, {\n    placement: 'right', trigger: 'click', content: '', template: '<div class=\"popover\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  /* POPOVER NO CONFLICT\n   * =================== */\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(window.jQuery);\n/* =============================================================\n * bootstrap-scrollspy.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#scrollspy\n * =============================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ============================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* SCROLLSPY CLASS DEFINITION\n   * ========================== */\n\n  function ScrollSpy(element, options) {\n    var process = $.proxy(this.process, this)\n      , $element = $(element).is('body') ? $(window) : $(element)\n      , href\n    this.options = $.extend({}, $.fn.scrollspy.defaults, options)\n    this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)\n    this.selector = (this.options.target\n      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) //strip for ie7\n      || '') + ' .nav li > a'\n    this.$body = $('body')\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.prototype = {\n\n    constructor: ScrollSpy, refresh: function () {\n      var self = this\n        , $targets\n\n      this.offsets = $([])\n      this.targets = $([])\n\n      $targets = this.$body\n        .find(this.selector)\n        .map(function () {\n          var $el = $(this)\n            , href = $el.data('target') || $el.attr('href')\n            , $href = /^#\\w/.test(href) && $(href)\n          return ( $href\n            && $href.length\n            && [\n              [ $href.position().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]\n            ] ) || null\n        })\n        .sort(function (a, b) {\n          return a[0] - b[0]\n        })\n        .each(function () {\n          self.offsets.push(this[0])\n          self.targets.push(this[1])\n        })\n    }, process: function () {\n      var scrollTop = this.$scrollElement.scrollTop() + this.options.offset\n        , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight\n        , maxScroll = scrollHeight - this.$scrollElement.height()\n        , offsets = this.offsets\n        , targets = this.targets\n        , activeTarget = this.activeTarget\n        , i\n\n      if (scrollTop >= maxScroll) {\n        return activeTarget != (i = targets.last()[0])\n          && this.activate(i)\n      }\n\n      for (i = offsets.length; i--;) {\n        activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])\n        && this.activate(targets[i])\n      }\n    }, activate: function (target) {\n      var active\n        , selector\n\n      this.activeTarget = target\n\n      $(this.selector)\n        .parent('.active')\n        .removeClass('active')\n\n      selector = this.selector\n        + '[data-target=\"' + target + '\"],'\n        + this.selector + '[href=\"' + target + '\"]'\n\n      active = $(selector)\n        .parent('li')\n        .addClass('active')\n\n      if (active.parent('.dropdown-menu').length) {\n        active = active.closest('li.dropdown').addClass('active')\n      }\n\n      active.trigger('activate')\n    }\n\n  }\n\n\n  /* SCROLLSPY PLUGIN DEFINITION\n   * =========================== */\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('scrollspy')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n  $.fn.scrollspy.defaults = {\n    offset: 10\n  }\n\n\n  /* SCROLLSPY NO CONFLICT\n   * ===================== */\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  /* SCROLLSPY DATA-API\n   * ================== */\n\n  $(window).on('load', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      $spy.scrollspy($spy.data())\n    })\n  })\n\n}(window.jQuery);\n/* ========================================================\n * bootstrap-tab.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#tabs\n * ========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ======================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* TAB CLASS DEFINITION\n   * ==================== */\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.prototype = {\n\n    constructor: Tab, show: function () {\n      var $this = this.element\n        , $ul = $this.closest('ul:not(.dropdown-menu)')\n        , selector = $this.attr('data-target')\n        , previous\n        , $target\n        , e\n\n      if (!selector) {\n        selector = $this.attr('href')\n        selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') //strip for ie7\n      }\n\n      if ($this.parent('li').hasClass('active')) return\n\n      previous = $ul.find('.active:last a')[0]\n\n      e = $.Event('show', {\n        relatedTarget: previous\n      })\n\n      $this.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      $target = $(selector)\n\n      this.activate($this.parent('li'), $ul)\n      this.activate($target, $target.parent(), function () {\n        $this.trigger({\n          type: 'shown', relatedTarget: previous\n        })\n      })\n    }, activate: function (element, container, callback) {\n      var $active = container.find('> .active')\n        , transition = callback\n          && $.support.transition\n          && $active.hasClass('fade')\n\n      function next() {\n        $active\n          .removeClass('active')\n          .find('> .dropdown-menu > .active')\n          .removeClass('active')\n\n        element.addClass('active')\n\n        if (transition) {\n          element[0].offsetWidth // reflow for transition\n          element.addClass('in')\n        } else {\n          element.removeClass('fade')\n        }\n\n        if (element.parent('.dropdown-menu')) {\n          element.closest('li.dropdown').addClass('active')\n        }\n\n        callback && callback()\n      }\n\n      transition ?\n        $active.one($.support.transition.end, next) :\n        next()\n\n      $active.removeClass('in')\n    }\n  }\n\n\n  /* TAB PLUGIN DEFINITION\n   * ===================== */\n\n  var old = $.fn.tab\n\n  $.fn.tab = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('tab')\n      if (!data) $this.data('tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.tab.Constructor = Tab\n\n\n  /* TAB NO CONFLICT\n   * =============== */\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  /* TAB DATA-API\n   * ============ */\n\n  $(document).on('click.tab.data-api', '[data-toggle=\"tab\"], [data-toggle=\"pill\"]', function (e) {\n    e.preventDefault()\n    $(this).tab('show')\n  })\n\n}(window.jQuery);\n/* =============================================================\n * bootstrap-typeahead.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#typeahead\n * =============================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ============================================================ */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* TYPEAHEAD PUBLIC CLASS DEFINITION\n   * ================================= */\n\n  var Typeahead = function (element, options) {\n    this.$element = $(element)\n    this.options = $.extend({}, $.fn.typeahead.defaults, options)\n    this.matcher = this.options.matcher || this.matcher\n    this.sorter = this.options.sorter || this.sorter\n    this.highlighter = this.options.highlighter || this.highlighter\n    this.updater = this.options.updater || this.updater\n    this.source = this.options.source\n    this.$menu = $(this.options.menu)\n    this.shown = false\n    this.listen()\n  }\n\n  Typeahead.prototype = {\n\n    constructor: Typeahead, select: function () {\n      var val = this.$menu.find('.active').attr('data-value')\n      this.$element\n        .val(this.updater(val))\n        .change()\n      return this.hide()\n    }, updater: function (item) {\n      return item\n    }, show: function () {\n      var pos = $.extend({}, this.$element.position(), {\n        height: this.$element[0].offsetHeight\n      })\n\n      this.$menu\n        .insertAfter(this.$element)\n        .css({\n          top: pos.top + pos.height, left: pos.left\n        })\n        .show()\n\n      this.shown = true\n      return this\n    }, hide: function () {\n      this.$menu.hide()\n      this.shown = false\n      return this\n    }, lookup: function (event) {\n      var items\n\n      this.query = this.$element.val()\n\n      if (!this.query || this.query.length < this.options.minLength) {\n        return this.shown ? this.hide() : this\n      }\n\n      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source\n\n      return items ? this.process(items) : this\n    }, process: function (items) {\n      var that = this\n\n      items = $.grep(items, function (item) {\n        return that.matcher(item)\n      })\n\n      items = this.sorter(items)\n\n      if (!items.length) {\n        return this.shown ? this.hide() : this\n      }\n\n      return this.render(items.slice(0, this.options.items)).show()\n    }, matcher: function (item) {\n      return ~item.toLowerCase().indexOf(this.query.toLowerCase())\n    }, sorter: function (items) {\n      var beginswith = []\n        , caseSensitive = []\n        , caseInsensitive = []\n        , item\n\n      while (item = items.shift()) {\n        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)\n        else if (~item.indexOf(this.query)) caseSensitive.push(item)\n        else caseInsensitive.push(item)\n      }\n\n      return beginswith.concat(caseSensitive, caseInsensitive)\n    }, highlighter: function (item) {\n      var query = this.query.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, '\\\\$&')\n      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {\n        return '<strong>' + match + '</strong>'\n      })\n    }, render: function (items) {\n      var that = this\n\n      items = $(items).map(function (i, item) {\n        i = $(that.options.item).attr('data-value', item)\n        i.find('a').html(that.highlighter(item))\n        return i[0]\n      })\n\n      items.first().addClass('active')\n      this.$menu.html(items)\n      return this\n    }, next: function (event) {\n      var active = this.$menu.find('.active').removeClass('active')\n        , next = active.next()\n\n      if (!next.length) {\n        next = $(this.$menu.find('li')[0])\n      }\n\n      next.addClass('active')\n    }, prev: function (event) {\n      var active = this.$menu.find('.active').removeClass('active')\n        , prev = active.prev()\n\n      if (!prev.length) {\n        prev = this.$menu.find('li').last()\n      }\n\n      prev.addClass('active')\n    }, listen: function () {\n      this.$element\n        .on('focus', $.proxy(this.focus, this))\n        .on('blur', $.proxy(this.blur, this))\n        .on('keypress', $.proxy(this.keypress, this))\n        .on('keyup', $.proxy(this.keyup, this))\n\n      if (this.eventSupported('keydown')) {\n        this.$element.on('keydown', $.proxy(this.keydown, this))\n      }\n\n      this.$menu\n        .on('click', $.proxy(this.click, this))\n        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))\n        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))\n    }, eventSupported: function (eventName) {\n      var isSupported = eventName in this.$element\n      if (!isSupported) {\n        this.$element.setAttribute(eventName, 'return;')\n        isSupported = typeof this.$element[eventName] === 'function'\n      }\n      return isSupported\n    }, move: function (e) {\n      if (!this.shown) return\n\n      switch (e.keyCode) {\n        case 9: // tab\n        case 13: // enter\n        case 27: // escape\n          e.preventDefault()\n          break\n\n        case 38: // up arrow\n          e.preventDefault()\n          this.prev()\n          break\n\n        case 40: // down arrow\n          e.preventDefault()\n          this.next()\n          break\n      }\n\n      e.stopPropagation()\n    }, keydown: function (e) {\n      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40, 38, 9, 13, 27])\n      this.move(e)\n    }, keypress: function (e) {\n      if (this.suppressKeyPressRepeat) return\n      this.move(e)\n    }, keyup: function (e) {\n      switch (e.keyCode) {\n        case 40: // down arrow\n        case 38: // up arrow\n        case 16: // shift\n        case 17: // ctrl\n        case 18: // alt\n          break\n\n        case 9: // tab\n        case 13: // enter\n          if (!this.shown) return\n          this.select()\n          break\n\n        case 27: // escape\n          if (!this.shown) return\n          this.hide()\n          break\n\n        default:\n          this.lookup()\n      }\n\n      e.stopPropagation()\n      e.preventDefault()\n    }, focus: function (e) {\n      this.focused = true\n    }, blur: function (e) {\n      this.focused = false\n      if (!this.mousedover && this.shown) this.hide()\n    }, click: function (e) {\n      e.stopPropagation()\n      e.preventDefault()\n      this.select()\n      this.$element.focus()\n    }, mouseenter: function (e) {\n      this.mousedover = true\n      this.$menu.find('.active').removeClass('active')\n      $(e.currentTarget).addClass('active')\n    }, mouseleave: function (e) {\n      this.mousedover = false\n      if (!this.focused && this.shown) this.hide()\n    }\n\n  }\n\n\n  /* TYPEAHEAD PLUGIN DEFINITION\n   * =========================== */\n\n  var old = $.fn.typeahead\n\n  $.fn.typeahead = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('typeahead')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.typeahead.defaults = {\n    source: [], items: 8, menu: '<ul class=\"typeahead dropdown-menu\"></ul>', item: '<li><a href=\"#\"></a></li>', minLength: 1\n  }\n\n  $.fn.typeahead.Constructor = Typeahead\n\n\n  /* TYPEAHEAD NO CONFLICT\n   * =================== */\n\n  $.fn.typeahead.noConflict = function () {\n    $.fn.typeahead = old\n    return this\n  }\n\n\n  /* TYPEAHEAD DATA-API\n   * ================== */\n\n  $(document).on('focus.typeahead.data-api', '[data-provide=\"typeahead\"]', function (e) {\n    var $this = $(this)\n    if ($this.data('typeahead')) return\n    $this.typeahead($this.data())\n  })\n\n}(window.jQuery);\n/* ==========================================================\n * bootstrap-affix.js v2.3.1\n * http://twitter.github.com/bootstrap/javascript.html#affix\n * ==========================================================\n * Copyright 2012 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================== */\n\n\n!function ($) {\n\n  \"use strict\"; // jshint ;_;\n\n\n  /* AFFIX CLASS DEFINITION\n   * ====================== */\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, $.fn.affix.defaults, options)\n    this.$window = $(window)\n      .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.affix.data-api', $.proxy(function () {\n        setTimeout($.proxy(this.checkPosition, this), 1)\n      }, this))\n    this.$element = $(element)\n    this.checkPosition()\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var scrollHeight = $(document).height()\n      , scrollTop = this.$window.scrollTop()\n      , position = this.$element.offset()\n      , offset = this.options.offset\n      , offsetBottom = offset.bottom\n      , offsetTop = offset.top\n      , reset = 'affix affix-top affix-bottom'\n      , affix\n\n    if (typeof offset != 'object') offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function') offsetTop = offset.top()\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()\n\n    affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?\n      false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?\n      'bottom' : offsetTop != null && scrollTop <= offsetTop ?\n      'top' : false\n\n    if (this.affixed === affix) return\n\n    this.affixed = affix\n    this.unpin = affix == 'bottom' ? position.top - scrollTop : null\n\n    this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))\n  }\n\n\n  /* AFFIX PLUGIN DEFINITION\n   * ======================= */\n\n  var old = $.fn.affix\n\n  $.fn.affix = function (option) {\n    return this.each(function () {\n      var $this = $(this)\n        , data = $this.data('affix')\n        , options = typeof option == 'object' && option\n      if (!data) $this.data('affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  $.fn.affix.Constructor = Affix\n\n  $.fn.affix.defaults = {\n    offset: 0\n  }\n\n\n  /* AFFIX NO CONFLICT\n   * ================= */\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  /* AFFIX DATA-API\n   * ============== */\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n        , data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      data.offsetBottom && (data.offset.bottom = data.offsetBottom)\n      data.offsetTop && (data.offset.top = data.offsetTop)\n\n      $spy.affix(data)\n    })\n  })\n\n\n}(window.jQuery);"
  },
  {
    "path": "public/libs/code-prettify/lang-apollo.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"com\", /^#[^\\n\\r]*/, null, \"#\"],\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"str\", /^\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)/, null, '\"']\n], [\n  [\"kwd\", /^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\\s/,\n    null],\n  [\"typ\", /^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[ES]?BANK=?|BLOCK|BNKSUM|E?CADR|COUNT\\*?|2?DEC\\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\\s/, null],\n  [\"lit\", /^'(?:-*(?:\\w|\\\\[!-~])(?:[\\w-]*|\\\\[!-~])[!=?]?)?/],\n  [\"pln\", /^-*(?:[!-z]|\\\\[!-~])(?:[\\w-]*|\\\\[!-~])[!=?]?/],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'-);\\\\\\xa0]+/]\n]), [\"apollo\", \"agc\", \"aea\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-clj.js",
    "content": "/*\n Copyright (C) 2011 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n */\nvar a = null;\nPR.registerLangHandler(PR.createSimpleLexer([\n  [\"opn\", /^[([{]+/, a, \"([{\"],\n  [\"clo\", /^[)\\]}]+/, a, \")]}\"],\n  [\"com\", /^;[^\\n\\r]*/, a, \";\"],\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, a, \"\\t\\n\\r \\xa0\"],\n  [\"str\", /^\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)/, a, '\"']\n], [\n  [\"kwd\", /^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\\b/, a],\n  [\"typ\", /^:[\\dA-Za-z-]+/]\n]), [\"clj\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-css.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\f\\r ]+/, null, \" \\t\\r\\n\f\"]\n], [\n  [\"str\", /^\"(?:[^\\n\\f\\r\"\\\\]|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\S\\s])*\"/, null],\n  [\"str\", /^'(?:[^\\n\\f\\r'\\\\]|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\S\\s])*'/, null],\n  [\"lang-css-str\", /^url\\(([^\"')]*)\\)/i],\n  [\"kwd\", /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\\w-]|$)/i, null],\n  [\"lang-css-kw\", /^(-?(?:[_a-z]|\\\\[\\da-f]+ ?)(?:[\\w-]|\\\\\\\\[\\da-f]+ ?)*)\\s*:/i],\n  [\"com\", /^\\/\\*[^*]*\\*+(?:[^*/][^*]*\\*+)*\\//],\n  [\"com\",\n    /^(?:<\\!--|--\\>)/],\n  [\"lit\", /^(?:\\d+|\\d*\\.\\d+)(?:%|[a-z]+)?/i],\n  [\"lit\", /^#[\\da-f]{3,6}/i],\n  [\"pln\", /^-?(?:[_a-z]|\\\\[\\da-f]+ ?)(?:[\\w-]|\\\\\\\\[\\da-f]+ ?)*/i],\n  [\"pun\", /^[^\\s\\w\"']+/]\n]), [\"css\"]);\nPR.registerLangHandler(PR.createSimpleLexer([], [\n  [\"kwd\", /^-?(?:[_a-z]|\\\\[\\da-f]+ ?)(?:[\\w-]|\\\\\\\\[\\da-f]+ ?)*/i]\n]), [\"css-kw\"]);\nPR.registerLangHandler(PR.createSimpleLexer([], [\n  [\"str\", /^[^\"')]+/]\n]), [\"css-str\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-go.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"pln\", /^(?:\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)|'(?:[^'\\\\]|\\\\[\\S\\s])+(?:'|$)|`[^`]*(?:`|$))/, null, \"\\\"'\"]\n], [\n  [\"com\", /^(?:\\/\\/[^\\n\\r]*|\\/\\*[\\S\\s]*?\\*\\/)/],\n  [\"pln\", /^(?:[^\"'/`]|\\/(?![*/]))+/]\n]), [\"go\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-hs.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t-\\r ]+/, null, \"\\t\\n\u000b\f\\r \"],\n  [\"str\", /^\"(?:[^\\n\\f\\r\"\\\\]|\\\\[\\S\\s])*(?:\"|$)/, null, '\"'],\n  [\"str\", /^'(?:[^\\n\\f\\r'\\\\]|\\\\[^&])'?/, null, \"'\"],\n  [\"lit\", /^(?:0o[0-7]+|0x[\\da-f]+|\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?)/i, null, \"0123456789\"]\n], [\n  [\"com\", /^(?:--+[^\\n\\f\\r]*|{-(?:[^-]|-+[^}-])*-})/],\n  [\"kwd\", /^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\\d'A-Za-z]|$)/,\n    null],\n  [\"pln\", /^(?:[A-Z][\\w']*\\.)*[A-Za-z][\\w']*/],\n  [\"pun\", /^[^\\d\\t-\\r \"'A-Za-z]+/]\n]), [\"hs\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-lisp.js",
    "content": "var a = null;\nPR.registerLangHandler(PR.createSimpleLexer([\n  [\"opn\", /^\\(+/, a, \"(\"],\n  [\"clo\", /^\\)+/, a, \")\"],\n  [\"com\", /^;[^\\n\\r]*/, a, \";\"],\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, a, \"\\t\\n\\r \\xa0\"],\n  [\"str\", /^\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)/, a, '\"']\n], [\n  [\"kwd\", /^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\\b/, a],\n  [\"lit\", /^[+-]?(?:[#0]x[\\da-f]+|\\d+\\/\\d+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:[de][+-]?\\d+)?)/i],\n  [\"lit\", /^'(?:-*(?:\\w|\\\\[!-~])(?:[\\w-]*|\\\\[!-~])[!=?]?)?/],\n  [\"pln\", /^-*(?:[_a-z]|\\\\[!-~])(?:[\\w-]*|\\\\[!-~])[!=?]?/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'-);\\\\\\xa0]+/]\n]), [\"cl\", \"el\", \"lisp\", \"scm\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-lua.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"str\", /^(?:\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)|'(?:[^'\\\\]|\\\\[\\S\\s])*(?:'|$))/, null, \"\\\"'\"]\n], [\n  [\"com\", /^--(?:\\[(=*)\\[[\\S\\s]*?(?:]\\1]|$)|[^\\n\\r]*)/],\n  [\"str\", /^\\[(=*)\\[[\\S\\s]*?(?:]\\1]|$)/],\n  [\"kwd\", /^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\\b/, null],\n  [\"lit\", /^[+-]?(?:0x[\\da-f]+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+-]?\\d+)?)/i],\n  [\"pln\", /^[_a-z]\\w*/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \\xa0][^\\w\\t\\n\\r \"'+=\\xa0-]*/]\n]), [\"lua\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-ml.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"com\", /^#(?:if[\\t\\n\\r \\xa0]+(?:[$_a-z][\\w']*|``[^\\t\\n\\r`]*(?:``|$))|else|endif|light)/i, null, \"#\"],\n  [\"str\", /^(?:\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)|'(?:[^'\\\\]|\\\\[\\S\\s])(?:'|$))/, null, \"\\\"'\"]\n], [\n  [\"com\", /^(?:\\/\\/[^\\n\\r]*|\\(\\*[\\S\\s]*?\\*\\))/],\n  [\"kwd\", /^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\\b/],\n  [\"lit\", /^[+-]?(?:0x[\\da-f]+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+-]?\\d+)?)/i],\n  [\"pln\", /^(?:[_a-z][\\w']*[!#?]?|``[^\\t\\n\\r`]*(?:``|$))/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'\\xa0]+/]\n]), [\"fs\", \"ml\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-n.js",
    "content": "var a = null;\nPR.registerLangHandler(PR.createSimpleLexer([\n  [\"str\", /^(?:'(?:[^\\n\\r'\\\\]|\\\\.)*'|\"(?:[^\\n\\r\"\\\\]|\\\\.)*(?:\"|$))/, a, '\"'],\n  [\"com\", /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\\b|[^\\n\\r]*)/, a, \"#\"],\n  [\"pln\", /^\\s+/, a, \" \\r\\n\\t\\xa0\"]\n], [\n  [\"str\", /^@\"(?:[^\"]|\"\")*(?:\"|$)/, a],\n  [\"str\", /^<#[^#>]*(?:#>|$)/, a],\n  [\"str\", /^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h|[a-z]\\w*)>/, a],\n  [\"com\", /^\\/\\/[^\\n\\r]*/, a],\n  [\"com\", /^\\/\\*[\\S\\s]*?(?:\\*\\/|$)/,\n    a],\n  [\"kwd\", /^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\\b/,\n    a],\n  [\"typ\", /^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\\b/, a],\n  [\"lit\", /^@[$_a-z][\\w$@]*/i, a],\n  [\"typ\", /^@[A-Z]+[a-z][\\w$@]*/, a],\n  [\"pln\", /^'?[$_a-z][\\w$@]*/i, a],\n  [\"lit\", /^(?:0x[\\da-f]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+-]?\\d+)?)[a-z]*/i, a, \"0123456789\"],\n  [\"pun\", /^.[^\\s\\w\"-$'./@`]*/, a]\n]), [\"n\", \"nemerle\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-proto.js",
    "content": "PR.registerLangHandler(PR.sourceDecorator({keywords: \"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true\", types: /^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\\b/, cStyleComments: !0}), [\"proto\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-scala.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"str\", /^\"(?:\"\"(?:\"\"?(?!\")|[^\"\\\\]|\\\\.)*\"{0,3}|(?:[^\\n\\r\"\\\\]|\\\\.)*\"?)/, null, '\"'],\n  [\"lit\", /^`(?:[^\\n\\r\\\\`]|\\\\.)*`?/, null, \"`\"],\n  [\"pun\", /^[!#%&(--:-@[-^{-~]+/, null, \"!#%&()*+,-:;<=>?@[\\\\]^{|}~\"]\n], [\n  [\"str\", /^'(?:[^\\n\\r'\\\\]|\\\\(?:'|[^\\n\\r']+))'/],\n  [\"lit\", /^'[$A-Z_a-z][\\w$]*(?![\\w$'])/],\n  [\"kwd\", /^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\\b/],\n  [\"lit\", /^(?:true|false|null|this)\\b/],\n  [\"lit\", /^(?:0(?:[0-7]+|x[\\da-f]+)l?|(?:0|[1-9]\\d*)(?:(?:\\.\\d+)?(?:e[+-]?\\d+)?f?|l?)|\\\\.\\d+(?:e[+-]?\\d+)?f?)/i],\n  [\"typ\", /^[$_]*[A-Z][\\d$A-Z_]*[a-z][\\w$]*/],\n  [\"pln\", /^[$A-Z_a-z][\\w$]*/],\n  [\"com\", /^\\/(?:\\/.*|\\*(?:\\/|\\**[^*/])*(?:\\*+\\/?)?)/],\n  [\"pun\", /^(?:\\.+|\\/)/]\n]), [\"scala\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-sql.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"str\", /^(?:\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')/, null, \"\\\"'\"]\n], [\n  [\"com\", /^(?:--[^\\n\\r]*|\\/\\*[\\S\\s]*?(?:\\*\\/|$))/],\n  [\"kwd\", /^(?:add|all|alter|and|any|as|asc|authorization|backup|begin|between|break|browse|bulk|by|cascade|case|check|checkpoint|close|clustered|coalesce|collate|column|commit|compute|constraint|contains|containstable|continue|convert|create|cross|current|current_date|current_time|current_timestamp|current_user|cursor|database|dbcc|deallocate|declare|default|delete|deny|desc|disk|distinct|distributed|double|drop|dummy|dump|else|end|errlvl|escape|except|exec|execute|exists|exit|fetch|file|fillfactor|for|foreign|freetext|freetexttable|from|full|function|goto|grant|group|having|holdlock|identity|identitycol|identity_insert|if|in|index|inner|insert|intersect|into|is|join|key|kill|left|like|lineno|load|match|merge|national|nocheck|nonclustered|not|null|nullif|of|off|offsets|on|open|opendatasource|openquery|openrowset|openxml|option|or|order|outer|over|percent|plan|precision|primary|print|proc|procedure|public|raiserror|read|readtext|reconfigure|references|replication|restore|restrict|return|revoke|right|rollback|rowcount|rowguidcol|rule|save|schema|select|session_user|set|setuser|shutdown|some|statistics|system_user|table|textsize|then|to|top|tran|transaction|trigger|truncate|tsequal|union|unique|update|updatetext|use|user|using|values|varying|view|waitfor|when|where|while|with|writetext)(?=[^\\w-]|$)/i,\n    null],\n  [\"lit\", /^[+-]?(?:0x[\\da-f]+|(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+-]?\\d+)?)/i],\n  [\"pln\", /^[_a-z][\\w-]*/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'\\xa0][^\\w\\t\\n\\r \"'+\\xa0-]*/]\n]), [\"sql\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-tex.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"],\n  [\"com\", /^%[^\\n\\r]*/, null, \"%\"]\n], [\n  [\"kwd\", /^\\\\[@-Za-z]+/],\n  [\"kwd\", /^\\\\./],\n  [\"typ\", /^[$&]/],\n  [\"lit\", /[+-]?(?:\\.\\d+|\\d+(?:\\.\\d*)?)(cm|em|ex|in|pc|pt|bp|mm)/i],\n  [\"pun\", /^[()=[\\]{}]+/]\n]), [\"latex\", \"tex\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-vb.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0\\u2028\\u2029]+/, null, \"\\t\\n\\r �\\xa0  \"],\n  [\"str\", /^(?:[\"\\u201c\\u201d](?:[^\"\\u201c\\u201d]|[\"\\u201c\\u201d]{2})(?:[\"\\u201c\\u201d]c|$)|[\"\\u201c\\u201d](?:[^\"\\u201c\\u201d]|[\"\\u201c\\u201d]{2})*(?:[\"\\u201c\\u201d]|$))/i, null, '\"“”'],\n  [\"com\", /^['\\u2018\\u2019].*/, null, \"'‘’\"]\n], [\n  [\"kwd\", /^(?:addhandler|addressof|alias|and|andalso|ansi|as|assembly|auto|boolean|byref|byte|byval|call|case|catch|cbool|cbyte|cchar|cdate|cdbl|cdec|char|cint|class|clng|cobj|const|cshort|csng|cstr|ctype|date|decimal|declare|default|delegate|dim|directcast|do|double|each|else|elseif|end|endif|enum|erase|error|event|exit|finally|for|friend|function|get|gettype|gosub|goto|handles|if|implements|imports|in|inherits|integer|interface|is|let|lib|like|long|loop|me|mod|module|mustinherit|mustoverride|mybase|myclass|namespace|new|next|not|notinheritable|notoverridable|object|on|option|optional|or|orelse|overloads|overridable|overrides|paramarray|preserve|private|property|protected|public|raiseevent|readonly|redim|removehandler|resume|return|select|set|shadows|shared|short|single|static|step|stop|string|structure|sub|synclock|then|throw|to|try|typeof|unicode|until|variant|wend|when|while|with|withevents|writeonly|xor|endif|gosub|let|variant|wend)\\b/i,\n    null],\n  [\"com\", /^rem.*/i],\n  [\"lit\", /^(?:true\\b|false\\b|nothing\\b|\\d+(?:e[+-]?\\d+[dfr]?|[dfilrs])?|(?:&h[\\da-f]+|&o[0-7]+)[ils]?|\\d*\\.\\d+(?:e[+-]?\\d+)?[dfr]?|#\\s+(?:\\d+[/-]\\d+[/-]\\d+(?:\\s+\\d+:\\d+(?::\\d+)?(\\s*(?:am|pm))?)?|\\d+:\\d+(?::\\d+)?(\\s*(?:am|pm))?)\\s+#)/i],\n  [\"pln\", /^(?:(?:[a-z]|_\\w)\\w*|\\[(?:[a-z]|_\\w)\\w*])/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'[\\]\\xa0\\u2018\\u2019\\u201c\\u201d\\u2028\\u2029]+/],\n  [\"pun\", /^(?:\\[|])/]\n]), [\"vb\", \"vbs\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-vhdl.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/, null, \"\\t\\n\\r �\\xa0\"]\n], [\n  [\"str\", /^(?:[box]?\"(?:[^\"]|\"\")*\"|'.')/i],\n  [\"com\", /^--[^\\n\\r]*/],\n  [\"kwd\", /^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\\w-]|$)/i,\n    null],\n  [\"typ\", /^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\\w-]|$)/i, null],\n  [\"typ\", /^'(?:active|ascending|base|delayed|driving|driving_value|event|high|image|instance_name|last_active|last_event|last_value|left|leftof|length|low|path_name|pos|pred|quiet|range|reverse_range|right|rightof|simple_name|stable|succ|transaction|val|value)(?=[^\\w-]|$)/i, null],\n  [\"lit\", /^\\d+(?:_\\d+)*(?:#[\\w.\\\\]+#(?:[+-]?\\d+(?:_\\d+)*)?|(?:\\.\\d+(?:_\\d+)*)?(?:e[+-]?\\d+(?:_\\d+)*)?)/i],\n  [\"pln\", /^(?:[a-z]\\w*|\\\\[^\\\\]*\\\\)/i],\n  [\"pun\", /^[^\\w\\t\\n\\r \"'\\xa0][^\\w\\t\\n\\r \"'\\xa0-]*/]\n]), [\"vhdl\", \"vhd\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-wiki.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"pln\", /^[\\d\\t a-gi-z\\xa0]+/, null, \"\\t �\\xa0abcdefgijklmnopqrstuvwxyz0123456789\"],\n  [\"pun\", /^[*=[\\]^~]+/, null, \"=*~^[]\"]\n], [\n  [\"lang-wiki.meta\", /(?:^^|\\r\\n?|\\n)(#[a-z]+)\\b/],\n  [\"lit\", /^[A-Z][a-z][\\da-z]+[A-Z][a-z][^\\W_]+\\b/],\n  [\"lang-\", /^{{{([\\S\\s]+?)}}}/],\n  [\"lang-\", /^`([^\\n\\r`]+)`/],\n  [\"str\", /^https?:\\/\\/[^\\s#/?]*(?:\\/[^\\s#?]*)?(?:\\?[^\\s#]*)?(?:#\\S*)?/i],\n  [\"pln\", /^(?:\\r\\n|[\\S\\s])[^\\n\\r#*=A-[^`h{~]*/]\n]), [\"wiki\"]);\nPR.registerLangHandler(PR.createSimpleLexer([\n  [\"kwd\", /^#[a-z]+/i, null, \"#\"]\n], []), [\"wiki.meta\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-xq.js",
    "content": "PR.registerLangHandler(PR.createSimpleLexer([\n  [\"var pln\", /^\\$[\\w-]+/, null, \"$\"]\n], [\n  [\"pln\", /^[\\s=][<>][\\s=]/],\n  [\"lit\", /^@[\\w-]+/],\n  [\"tag\", /^<\\/?[a-z](?:[\\w-.:]*\\w)?|\\/?>$/i],\n  [\"com\", /^\\(:[\\S\\s]*?:\\)/],\n  [\"pln\", /^[(),/;[\\]{}]$/],\n  [\"str\", /^(?:\"(?:[^\"\\\\{]|\\\\[\\S\\s])*(?:\"|$)|'(?:[^'\\\\{]|\\\\[\\S\\s])*(?:'|$))/, null, \"\\\"'\"],\n  [\"kwd\", /^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\\b/],\n  [\"typ\", /^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\\b/, null],\n  [\"fun pln\", /^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\\b/],\n  [\"pln\", /^[\\w:-]+/],\n  [\"pln\", /^[\\t\\n\\r \\xa0]+/]\n]), [\"xq\", \"xquery\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/lang-yaml.js",
    "content": "var a = null;\nPR.registerLangHandler(PR.createSimpleLexer([\n  [\"pun\", /^[:>?|]+/, a, \":|>?\"],\n  [\"dec\", /^%(?:YAML|TAG)[^\\n\\r#]+/, a, \"%\"],\n  [\"typ\", /^&\\S+/, a, \"&\"],\n  [\"typ\", /^!\\S*/, a, \"!\"],\n  [\"str\", /^\"(?:[^\"\\\\]|\\\\.)*(?:\"|$)/, a, '\"'],\n  [\"str\", /^'(?:[^']|'')*(?:'|$)/, a, \"'\"],\n  [\"com\", /^#[^\\n\\r]*/, a, \"#\"],\n  [\"pln\", /^\\s+/, a, \" \\t\\r\\n\"]\n], [\n  [\"dec\", /^(?:---|\\.\\.\\.)(?:[\\n\\r]|$)/],\n  [\"pun\", /^-/],\n  [\"kwd\", /^\\w+:[\\n\\r ]/],\n  [\"pln\", /^\\w+/]\n]), [\"yaml\", \"yml\"]);\n"
  },
  {
    "path": "public/libs/code-prettify/prettify.css",
    "content": "/* Pretty printing styles. Used with prettify.js. */\n\n/* SPAN elements with the classes below are added by prettyprint. */\n.pln {\n    color: #000\n}\n\n/* plain text */\n\n@media screen {\n    .str {\n        color: #080\n    }\n\n    /* string content */\n    .kwd {\n        color: #008\n    }\n\n    /* a keyword */\n    .com {\n        color: #800\n    }\n\n    /* a comment */\n    .typ {\n        color: #606\n    }\n\n    /* a type name */\n    .lit {\n        color: #066\n    }\n\n    /* a literal value */\n    /* punctuation, lisp open bracket, lisp close bracket */\n    .pun, .opn, .clo {\n        color: #660\n    }\n\n    .tag {\n        color: #008\n    }\n\n    /* a markup tag name */\n    .atn {\n        color: #606\n    }\n\n    /* a markup attribute name */\n    .atv {\n        color: #080\n    }\n\n    /* a markup attribute value */\n    .dec, .var {\n        color: #606\n    }\n\n    /* a declaration; a variable name */\n    .fun {\n        color: red\n    }\n\n    /* a function name */\n}\n\n/* Use higher contrast and text-weight for printable form. */\n@media print, projection {\n    .str {\n        color: #060\n    }\n\n    .kwd {\n        color: #006;\n        font-weight: bold\n    }\n\n    .com {\n        color: #600;\n        font-style: italic\n    }\n\n    .typ {\n        color: #404;\n        font-weight: bold\n    }\n\n    .lit {\n        color: #044\n    }\n\n    .pun, .opn, .clo {\n        color: #440\n    }\n\n    .tag {\n        color: #006;\n        font-weight: bold\n    }\n\n    .atn {\n        color: #404\n    }\n\n    .atv {\n        color: #060\n    }\n}\n\n/* Put a border around prettyprinted code snippets. */\npre.prettyprint {\n    padding: 2px;\n    border: 1px solid #888\n}\n\n/* Specify class=linenums on a pre to get line numbering */\nol.linenums {\n    margin-top: 0;\n    margin-bottom: 0\n}\n\n/* IE indents via margin-left */\nli.L0,\nli.L1,\nli.L2,\nli.L3,\nli.L5,\nli.L6,\nli.L7,\nli.L8 {\n    list-style-type: none\n}\n\n/* Alternate shading for lines */\nli.L1,\nli.L3,\nli.L5,\nli.L7,\nli.L9 {\n    background: #eee\n}"
  },
  {
    "path": "public/libs/code-prettify/prettify.js",
    "content": "var q = null;\nwindow.PR_SHOULD_USE_CONTINUATION = !0;\n(function () {\n  function L(a) {\n    function m(a) {\n      var f = a.charCodeAt(0);\n      if (f !== 92)return f;\n      var b = a.charAt(1);\n      return(f = r[b]) ? f : \"0\" <= b && b <= \"7\" ? parseInt(a.substring(1), 8) : b === \"u\" || b === \"x\" ? parseInt(a.substring(2), 16) : a.charCodeAt(1)\n    }\n\n    function e(a) {\n      if (a < 32)return(a < 16 ? \"\\\\x0\" : \"\\\\x\") + a.toString(16);\n      a = String.fromCharCode(a);\n      if (a === \"\\\\\" || a === \"-\" || a === \"[\" || a === \"]\")a = \"\\\\\" + a;\n      return a\n    }\n\n    function h(a) {\n      for (var f = a.substring(1, a.length - 1).match(/\\\\u[\\dA-Fa-f]{4}|\\\\x[\\dA-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\S\\s]|[^\\\\]/g), a =\n        [], b = [], o = f[0] === \"^\", c = o ? 1 : 0, i = f.length; c < i; ++c) {\n        var j = f[c];\n        if (/\\\\[bdsw]/i.test(j))a.push(j); else {\n          var j = m(j), d;\n          c + 2 < i && \"-\" === f[c + 1] ? (d = m(f[c + 2]), c += 2) : d = j;\n          b.push([j, d]);\n          d < 65 || j > 122 || (d < 65 || j > 90 || b.push([Math.max(65, j) | 32, Math.min(d, 90) | 32]), d < 97 || j > 122 || b.push([Math.max(97, j) & -33, Math.min(d, 122) & -33]))\n        }\n      }\n      b.sort(function (a, f) {\n        return a[0] - f[0] || f[1] - a[1]\n      });\n      f = [];\n      j = [NaN, NaN];\n      for (c = 0; c < b.length; ++c)i = b[c], i[0] <= j[1] + 1 ? j[1] = Math.max(j[1], i[1]) : f.push(j = i);\n      b = [\"[\"];\n      o && b.push(\"^\");\n      b.push.apply(b, a);\n      for (c = 0; c <\n        f.length; ++c)i = f[c], b.push(e(i[0])), i[1] > i[0] && (i[1] + 1 > i[0] && b.push(\"-\"), b.push(e(i[1])));\n      b.push(\"]\");\n      return b.join(\"\")\n    }\n\n    function y(a) {\n      for (var f = a.source.match(/\\[(?:[^\\\\\\]]|\\\\[\\S\\s])*]|\\\\u[\\dA-Fa-f]{4}|\\\\x[\\dA-Fa-f]{2}|\\\\\\d+|\\\\[^\\dux]|\\(\\?[!:=]|[()^]|[^()[\\\\^]+/g), b = f.length, d = [], c = 0, i = 0; c < b; ++c) {\n        var j = f[c];\n        j === \"(\" ? ++i : \"\\\\\" === j.charAt(0) && (j = +j.substring(1)) && j <= i && (d[j] = -1)\n      }\n      for (c = 1; c < d.length; ++c)-1 === d[c] && (d[c] = ++t);\n      for (i = c = 0; c < b; ++c)j = f[c], j === \"(\" ? (++i, d[i] === void 0 && (f[c] = \"(?:\")) : \"\\\\\" === j.charAt(0) &&\n        (j = +j.substring(1)) && j <= i && (f[c] = \"\\\\\" + d[i]);\n      for (i = c = 0; c < b; ++c)\"^\" === f[c] && \"^\" !== f[c + 1] && (f[c] = \"\");\n      if (a.ignoreCase && s)for (c = 0; c < b; ++c)j = f[c], a = j.charAt(0), j.length >= 2 && a === \"[\" ? f[c] = h(j) : a !== \"\\\\\" && (f[c] = j.replace(/[A-Za-z]/g, function (a) {\n        a = a.charCodeAt(0);\n        return\"[\" + String.fromCharCode(a & -33, a | 32) + \"]\"\n      }));\n      return f.join(\"\")\n    }\n\n    for (var t = 0, s = !1, l = !1, p = 0, d = a.length; p < d; ++p) {\n      var g = a[p];\n      if (g.ignoreCase)l = !0; else if (/[a-z]/i.test(g.source.replace(/\\\\u[\\da-f]{4}|\\\\x[\\da-f]{2}|\\\\[^UXux]/gi, \"\"))) {\n        s = !0;\n        l = !1;\n        break\n      }\n    }\n    for (var r =\n    {b: 8, t: 9, n: 10, v: 11, f: 12, r: 13}, n = [], p = 0, d = a.length; p < d; ++p) {\n      g = a[p];\n      if (g.global || g.multiline)throw Error(\"\" + g);\n      n.push(\"(?:\" + y(g) + \")\")\n    }\n    return RegExp(n.join(\"|\"), l ? \"gi\" : \"g\")\n  }\n\n  function M(a) {\n    function m(a) {\n      switch (a.nodeType) {\n        case 1:\n          if (e.test(a.className))break;\n          for (var g = a.firstChild; g; g = g.nextSibling)m(g);\n          g = a.nodeName;\n          if (\"BR\" === g || \"LI\" === g)h[s] = \"\\n\", t[s << 1] = y++, t[s++ << 1 | 1] = a;\n          break;\n        case 3:\n        case 4:\n          g = a.nodeValue, g.length && (g = p ? g.replace(/\\r\\n?/g, \"\\n\") : g.replace(/[\\t\\n\\r ]+/g, \" \"), h[s] = g, t[s << 1] = y, y += g.length,\n            t[s++ << 1 | 1] = a)\n      }\n    }\n\n    var e = /(?:^|\\s)nocode(?:\\s|$)/, h = [], y = 0, t = [], s = 0, l;\n    a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = document.defaultView.getComputedStyle(a, q).getPropertyValue(\"white-space\"));\n    var p = l && \"pre\" === l.substring(0, 3);\n    m(a);\n    return{a: h.join(\"\").replace(/\\n$/, \"\"), c: t}\n  }\n\n  function B(a, m, e, h) {\n    m && (a = {a: m, d: a}, e(a), h.push.apply(h, a.e))\n  }\n\n  function x(a, m) {\n    function e(a) {\n      for (var l = a.d, p = [l, \"pln\"], d = 0, g = a.a.match(y) || [], r = {}, n = 0, z = g.length; n < z; ++n) {\n        var f = g[n], b = r[f], o = void 0, c;\n        if (typeof b ===\n          \"string\")c = !1; else {\n          var i = h[f.charAt(0)];\n          if (i)o = f.match(i[1]), b = i[0]; else {\n            for (c = 0; c < t; ++c)if (i = m[c], o = f.match(i[1])) {\n              b = i[0];\n              break\n            }\n            o || (b = \"pln\")\n          }\n          if ((c = b.length >= 5 && \"lang-\" === b.substring(0, 5)) && !(o && typeof o[1] === \"string\"))c = !1, b = \"src\";\n          c || (r[f] = b)\n        }\n        i = d;\n        d += f.length;\n        if (c) {\n          c = o[1];\n          var j = f.indexOf(c), k = j + c.length;\n          o[2] && (k = f.length - o[2].length, j = k - c.length);\n          b = b.substring(5);\n          B(l + i, f.substring(0, j), e, p);\n          B(l + i + j, c, C(b, c), p);\n          B(l + i + k, f.substring(k), e, p)\n        } else p.push(l + i, b)\n      }\n      a.e = p\n    }\n\n    var h = {}, y;\n    (function () {\n      for (var e = a.concat(m),\n             l = [], p = {}, d = 0, g = e.length; d < g; ++d) {\n        var r = e[d], n = r[3];\n        if (n)for (var k = n.length; --k >= 0;)h[n.charAt(k)] = r;\n        r = r[1];\n        n = \"\" + r;\n        p.hasOwnProperty(n) || (l.push(r), p[n] = q)\n      }\n      l.push(/[\\S\\s]/);\n      y = L(l)\n    })();\n    var t = m.length;\n    return e\n  }\n\n  function u(a) {\n    var m = [], e = [];\n    a.tripleQuotedStrings ? m.push([\"str\", /^(?:'''(?:[^'\\\\]|\\\\[\\S\\s]|''?(?=[^']))*(?:'''|$)|\"\"\"(?:[^\"\\\\]|\\\\[\\S\\s]|\"\"?(?=[^\"]))*(?:\"\"\"|$)|'(?:[^'\\\\]|\\\\[\\S\\s])*(?:'|$)|\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$))/, q, \"'\\\"\"]) : a.multiLineStrings ? m.push([\"str\", /^(?:'(?:[^'\\\\]|\\\\[\\S\\s])*(?:'|$)|\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)|`(?:[^\\\\`]|\\\\[\\S\\s])*(?:`|$))/,\n      q, \"'\\\"`\"]) : m.push([\"str\", /^(?:'(?:[^\\n\\r'\\\\]|\\\\.)*(?:'|$)|\"(?:[^\\n\\r\"\\\\]|\\\\.)*(?:\"|$))/, q, \"\\\"'\"]);\n    a.verbatimStrings && e.push([\"str\", /^@\"(?:[^\"]|\"\")*(?:\"|$)/, q]);\n    var h = a.hashComments;\n    h && (a.cStyleComments ? (h > 1 ? m.push([\"com\", /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, q, \"#\"]) : m.push([\"com\", /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\\b|[^\\n\\r]*)/, q, \"#\"]), e.push([\"str\", /^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h|[a-z]\\w*)>/, q])) : m.push([\"com\", /^#[^\\n\\r]*/,\n      q, \"#\"]));\n    a.cStyleComments && (e.push([\"com\", /^\\/\\/[^\\n\\r]*/, q]), e.push([\"com\", /^\\/\\*[\\S\\s]*?(?:\\*\\/|$)/, q]));\n    a.regexLiterals && e.push([\"lang-regex\", /^(?:^^\\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|,|-=|->|\\/|\\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\\^=|\\^\\^|\\^\\^=|{|\\||\\|=|\\|\\||\\|\\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*(\\/(?=[^*/])(?:[^/[\\\\]|\\\\[\\S\\s]|\\[(?:[^\\\\\\]]|\\\\[\\S\\s])*(?:]|$))+\\/)/]);\n    (h = a.types) && e.push([\"typ\", h]);\n    a = (\"\" + a.keywords).replace(/^ | $/g,\n      \"\");\n    a.length && e.push([\"kwd\", RegExp(\"^(?:\" + a.replace(/[\\s,]+/g, \"|\") + \")\\\\b\"), q]);\n    m.push([\"pln\", /^\\s+/, q, \" \\r\\n\\t\\xa0\"]);\n    e.push([\"lit\", /^@[$_a-z][\\w$@]*/i, q], [\"typ\", /^(?:[@_]?[A-Z]+[a-z][\\w$@]*|\\w+_t\\b)/, q], [\"pln\", /^[$_a-z][\\w$@]*/i, q], [\"lit\", /^(?:0x[\\da-f]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+-]?\\d+)?)[a-z]*/i, q, \"0123456789\"], [\"pln\", /^\\\\[\\S\\s]?/, q], [\"pun\", /^.[^\\s\\w\"-$'./@\\\\`]*/, q]);\n    return x(m, e)\n  }\n\n  function D(a, m) {\n    function e(a) {\n      switch (a.nodeType) {\n        case 1:\n          if (k.test(a.className))break;\n          if (\"BR\" === a.nodeName)h(a),\n            a.parentNode && a.parentNode.removeChild(a); else for (a = a.firstChild; a; a = a.nextSibling)e(a);\n          break;\n        case 3:\n        case 4:\n          if (p) {\n            var b = a.nodeValue, d = b.match(t);\n            if (d) {\n              var c = b.substring(0, d.index);\n              a.nodeValue = c;\n              (b = b.substring(d.index + d[0].length)) && a.parentNode.insertBefore(s.createTextNode(b), a.nextSibling);\n              h(a);\n              c || a.parentNode.removeChild(a)\n            }\n          }\n      }\n    }\n\n    function h(a) {\n      function b(a, d) {\n        var e = d ? a.cloneNode(!1) : a, f = a.parentNode;\n        if (f) {\n          var f = b(f, 1), g = a.nextSibling;\n          f.appendChild(e);\n          for (var h = g; h; h = g)g = h.nextSibling, f.appendChild(h)\n        }\n        return e\n      }\n\n      for (; !a.nextSibling;)if (a = a.parentNode, !a)return;\n      for (var a = b(a.nextSibling, 0), e; (e = a.parentNode) && e.nodeType === 1;)a = e;\n      d.push(a)\n    }\n\n    var k = /(?:^|\\s)nocode(?:\\s|$)/, t = /\\r\\n?|\\n/, s = a.ownerDocument, l;\n    a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = s.defaultView.getComputedStyle(a, q).getPropertyValue(\"white-space\"));\n    var p = l && \"pre\" === l.substring(0, 3);\n    for (l = s.createElement(\"LI\"); a.firstChild;)l.appendChild(a.firstChild);\n    for (var d = [l], g = 0; g < d.length; ++g)e(d[g]);\n    m === (m | 0) && d[0].setAttribute(\"value\",\n      m);\n    var r = s.createElement(\"OL\");\n    r.className = \"linenums\";\n    for (var n = Math.max(0, m - 1 | 0) || 0, g = 0, z = d.length; g < z; ++g)l = d[g], l.className = \"L\" + (g + n) % 10, l.firstChild || l.appendChild(s.createTextNode(\"\\xa0\")), r.appendChild(l);\n    a.appendChild(r)\n  }\n\n  function k(a, m) {\n    for (var e = m.length; --e >= 0;) {\n      var h = m[e];\n      A.hasOwnProperty(h) ? window.console && console.warn(\"cannot override language handler %s\", h) : A[h] = a\n    }\n  }\n\n  function C(a, m) {\n    if (!a || !A.hasOwnProperty(a))a = /^\\s*</.test(m) ? \"default-markup\" : \"default-code\";\n    return A[a]\n  }\n\n  function E(a) {\n    var m =\n      a.g;\n    try {\n      var e = M(a.h), h = e.a;\n      a.a = h;\n      a.c = e.c;\n      a.d = 0;\n      C(m, h)(a);\n      var k = /\\bMSIE\\b/.test(navigator.userAgent), m = /\\n/g, t = a.a, s = t.length, e = 0, l = a.c, p = l.length, h = 0, d = a.e, g = d.length, a = 0;\n      d[g] = s;\n      var r, n;\n      for (n = r = 0; n < g;)d[n] !== d[n + 2] ? (d[r++] = d[n++], d[r++] = d[n++]) : n += 2;\n      g = r;\n      for (n = r = 0; n < g;) {\n        for (var z = d[n], f = d[n + 1], b = n + 2; b + 2 <= g && d[b + 1] === f;)b += 2;\n        d[r++] = z;\n        d[r++] = f;\n        n = b\n      }\n      for (d.length = r; h < p;) {\n        var o = l[h + 2] || s, c = d[a + 2] || s, b = Math.min(o, c), i = l[h + 1], j;\n        if (i.nodeType !== 1 && (j = t.substring(e, b))) {\n          k && (j = j.replace(m, \"\\r\"));\n          i.nodeValue =\n            j;\n          var u = i.ownerDocument, v = u.createElement(\"SPAN\");\n          v.className = d[a + 1];\n          var x = i.parentNode;\n          x.replaceChild(v, i);\n          v.appendChild(i);\n          e < o && (l[h + 1] = i = u.createTextNode(t.substring(b, o)), x.insertBefore(i, v.nextSibling))\n        }\n        e = b;\n        e >= o && (h += 2);\n        e >= c && (a += 2)\n      }\n    } catch (w) {\n      \"console\"in window && console.log(w && w.stack ? w.stack : w)\n    }\n  }\n\n  var v = [\"break,continue,do,else,for,if,return,while\"], w = [\n      [v, \"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"],\n      \"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof\"\n    ], F = [w, \"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where\"], G = [w, \"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient\"],\n    H = [G, \"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var\"], w = [w, \"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN\"], I = [v, \"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None\"],\n    J = [v, \"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END\"], v = [v, \"case,done,elif,esac,eval,fi,function,in,local,set,then,until\"], K = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)/, N = /\\S/, O = u({keywords: [F, H, w, \"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\" +\n      I, J, v], hashComments: !0, cStyleComments: !0, multiLineStrings: !0, regexLiterals: !0}), A = {};\n  k(O, [\"default-code\"]);\n  k(x([], [\n      [\"pln\", /^[^<?]+/],\n      [\"dec\", /^<!\\w[^>]*(?:>|$)/],\n      [\"com\", /^<\\!--[\\S\\s]*?(?:--\\>|$)/],\n      [\"lang-\", /^<\\?([\\S\\s]+?)(?:\\?>|$)/],\n      [\"lang-\", /^<%([\\S\\s]+?)(?:%>|$)/],\n      [\"pun\", /^(?:<[%?]|[%?]>)/],\n      [\"lang-\", /^<xmp\\b[^>]*>([\\S\\s]+?)<\\/xmp\\b[^>]*>/i],\n      [\"lang-js\", /^<script\\b[^>]*>([\\S\\s]*?)(<\\/script\\b[^>]*>)/i],\n      [\"lang-css\", /^<style\\b[^>]*>([\\S\\s]*?)(<\\/style\\b[^>]*>)/i],\n      [\"lang-in.tag\", /^(<\\/?[a-z][^<>]*>)/i]\n    ]),\n    [\"default-markup\", \"htm\", \"html\", \"mxml\", \"xhtml\", \"xml\", \"xsl\"]);\n  k(x([\n    [\"pln\", /^\\s+/, q, \" \\t\\r\\n\"],\n    [\"atv\", /^(?:\"[^\"]*\"?|'[^']*'?)/, q, \"\\\"'\"]\n  ], [\n    [\"tag\", /^^<\\/?[a-z](?:[\\w-.:]*\\w)?|\\/?>$/i],\n    [\"atn\", /^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],\n    [\"lang-uq.val\", /^=\\s*([^\\s\"'>]*(?:[^\\s\"'/>]|\\/(?=\\s)))/],\n    [\"pun\", /^[/<->]+/],\n    [\"lang-js\", /^on\\w+\\s*=\\s*\"([^\"]+)\"/i],\n    [\"lang-js\", /^on\\w+\\s*=\\s*'([^']+)'/i],\n    [\"lang-js\", /^on\\w+\\s*=\\s*([^\\s\"'>]+)/i],\n    [\"lang-css\", /^style\\s*=\\s*\"([^\"]+)\"/i],\n    [\"lang-css\", /^style\\s*=\\s*'([^']+)'/i],\n    [\"lang-css\",\n      /^style\\s*=\\s*([^\\s\"'>]+)/i]\n  ]), [\"in.tag\"]);\n  k(x([], [\n    [\"atv\", /^[\\S\\s]+/]\n  ]), [\"uq.val\"]);\n  k(u({keywords: F, hashComments: !0, cStyleComments: !0, types: K}), [\"c\", \"cc\", \"cpp\", \"cxx\", \"cyc\", \"m\"]);\n  k(u({keywords: \"null,true,false\"}), [\"json\"]);\n  k(u({keywords: H, hashComments: !0, cStyleComments: !0, verbatimStrings: !0, types: K}), [\"cs\"]);\n  k(u({keywords: G, cStyleComments: !0}), [\"java\"]);\n  k(u({keywords: v, hashComments: !0, multiLineStrings: !0}), [\"bsh\", \"csh\", \"sh\"]);\n  k(u({keywords: I, hashComments: !0, multiLineStrings: !0, tripleQuotedStrings: !0}),\n    [\"cv\", \"py\"]);\n  k(u({keywords: \"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\", hashComments: !0, multiLineStrings: !0, regexLiterals: !0}), [\"perl\", \"pl\", \"pm\"]);\n  k(u({keywords: J, hashComments: !0, multiLineStrings: !0, regexLiterals: !0}), [\"rb\"]);\n  k(u({keywords: w, cStyleComments: !0, regexLiterals: !0}), [\"js\"]);\n  k(u({keywords: \"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes\",\n    hashComments: 3, cStyleComments: !0, multilineStrings: !0, tripleQuotedStrings: !0, regexLiterals: !0}), [\"coffee\"]);\n  k(x([], [\n    [\"str\", /^[\\S\\s]+/]\n  ]), [\"regex\"]);\n  window.prettyPrintOne = function (a, m, e) {\n    var h = document.createElement(\"PRE\");\n    h.innerHTML = a;\n    e && D(h, e);\n    E({g: m, i: e, h: h});\n    return h.innerHTML\n  };\n  window.prettyPrint = function (a) {\n    function m() {\n      for (var e = window.PR_SHOULD_USE_CONTINUATION ? l.now() + 250 : Infinity; p < h.length && l.now() < e; p++) {\n        var n = h[p], k = n.className;\n        if (k.indexOf(\"prettyprint\") >= 0) {\n          var k = k.match(g), f, b;\n          if (b = !k) {\n            b = n;\n            for (var o = void 0, c = b.firstChild; c; c = c.nextSibling)var i = c.nodeType, o = i === 1 ? o ? b : c : i === 3 ? N.test(c.nodeValue) ? b : o : o;\n            b = (f = o === b ? void 0 : o) && \"CODE\" === f.tagName\n          }\n          b && (k = f.className.match(g));\n          k && (k = k[1]);\n          b = !1;\n          for (o = n.parentNode; o; o = o.parentNode)if ((o.tagName === \"pre\" || o.tagName === \"code\" || o.tagName === \"xmp\") && o.className && o.className.indexOf(\"prettyprint\") >= 0) {\n            b = !0;\n            break\n          }\n          b || ((b = (b = n.className.match(/\\blinenums\\b(?::(\\d+))?/)) ? b[1] && b[1].length ? +b[1] : !0 : !1) && D(n, b), d = {g: k, h: n, i: b}, E(d))\n        }\n      }\n      p < h.length ? setTimeout(m,\n        250) : a && a()\n    }\n\n    for (var e = [document.getElementsByTagName(\"pre\"), document.getElementsByTagName(\"code\"), document.getElementsByTagName(\"xmp\")], h = [], k = 0; k < e.length; ++k)for (var t = 0, s = e[k].length; t < s; ++t)h.push(e[k][t]);\n    var e = q, l = Date;\n    l.now || (l = {now: function () {\n      return+new Date\n    }});\n    var p = 0, d, g = /\\blang(?:uage)?-([\\w.]+)(?!\\S)/;\n    m()\n  };\n  window.PR = {createSimpleLexer: x, registerLangHandler: k, sourceDecorator: u, PR_ATTRIB_NAME: \"atn\", PR_ATTRIB_VALUE: \"atv\", PR_COMMENT: \"com\", PR_DECLARATION: \"dec\", PR_KEYWORD: \"kwd\", PR_LITERAL: \"lit\",\n    PR_NOCODE: \"nocode\", PR_PLAIN: \"pln\", PR_PUNCTUATION: \"pun\", PR_SOURCE: \"src\", PR_STRING: \"str\", PR_TAG: \"tag\", PR_TYPE: \"typ\"}\n})();\n"
  },
  {
    "path": "public/libs/editor/editor.css",
    "content": "@font-face {\n\tfont-family: 'icomoon';\n\tsrc:url('/public/libs/editor/fonts/icomoon.eot');\n\tsrc:url('/public/libs/editor/fonts/icomoon.eot?#iefix') format('embedded-opentype'),\n\t\turl('/public/libs/editor/fonts/icomoon.woff') format('woff'),\n\t\turl('/public/libs/editor/fonts/icomoon.ttf') format('truetype'),\n\t\turl('/public/libs/editor/fonts/icomoon.svg#icomoon') format('svg');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n/* Use the following CSS code if you want to use data attributes for inserting your icons */\n[data-icon]:before {\n\tfont-family: 'icomoon';\n\tcontent: attr(data-icon);\n\tspeak: none;\n\tfont-weight: normal;\n\tfont-variant: normal;\n\ttext-transform: none;\n\tline-height: 1;\n\t-webkit-font-smoothing: antialiased;\n}\n\n/* Use the following CSS code if you want to have a class per icon */\n/*\nInstead of a list of all class selectors,\nyou can use the generic selector below, but it's slower:\n[class*=\"icon-\"] {\n*/\n.eicon-bold, .eicon-italic, .eicon-quote, .eicon-unordered-list, .eicon-ordered-list, .eicon-link, .eicon-image, .eicon-play, .eicon-music, .eicon-contract, .eicon-fullscreen, .eicon-question, .eicon-info, .eicon-undo, .eicon-redo, .eicon-code, .eicon-preview {\n\tfont-family: 'icomoon';\n\tspeak: none;\n\tfont-style: normal;\n\tfont-weight: normal;\n\tfont-variant: normal;\n\ttext-transform: none;\n\tline-height: 1;\n\t-webkit-font-smoothing: antialiased;\n}\n.eicon-bold:before {\n\tcontent: \"\\e000\";\n}\n.eicon-italic:before {\n\tcontent: \"\\e001\";\n}\n.eicon-quote:before {\n\tcontent: \"\\e003\";\n}\n.eicon-unordered-list:before {\n\tcontent: \"\\e004\";\n}\n.eicon-ordered-list:before {\n\tcontent: \"\\e005\";\n}\n.eicon-link:before {\n\tcontent: \"\\e006\";\n}\n.eicon-image:before {\n\tcontent: \"\\e007\";\n}\n.eicon-play:before {\n\tcontent: \"\\e008\";\n}\n.eicon-music:before {\n\tcontent: \"\\e009\";\n}\n.eicon-contract:before {\n\tcontent: \"\\e00a\";\n}\n.eicon-fullscreen:before {\n\tcontent: \"\\e00b\";\n}\n.eicon-question:before {\n\tcontent: \"\\e00c\";\n}\n.eicon-info:before {\n\tcontent: \"\\e00d\";\n}\n.eicon-undo:before {\n\tcontent: \"\\e00e\";\n}\n.eicon-redo:before {\n\tcontent: \"\\e00f\";\n}\n.eicon-code:before {\n\tcontent: \"\\e011\";\n}\n.eicon-preview:before {\n\tcontent: \"\\e002\";\n}\n/* BASICS */\n\n.CodeMirror {\n  height: 300px;\n}\n.CodeMirror-scroll {\n  /* Set scrolling behaviour here */\n  overflow: auto;\n}\n\n/* PADDING */\n\n.CodeMirror-lines {\n  padding: 4px 0; /* Vertical padding around content */\n}\n.CodeMirror pre {\n  padding: 0 4px; /* Horizontal padding of content */\n}\n\n.CodeMirror-scrollbar-filler {\n  background-color: white; /* The little square between H and V scrollbars */\n}\n\n/* CURSOR */\n.CodeMirror div.CodeMirror-cursor {\n  border-left: 1px solid black;\n  z-index: 3;\n}\n/* Shown when moving in bi-directional text */\n.CodeMirror div.CodeMirror-secondarycursor {\n  border-left: 1px solid silver;\n}\n.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {\n  width: auto;\n  border: 0;\n  background: #7e7;\n  z-index: 1;\n}\n/* Can style cursor different in overwrite (non-insert) mode */\n.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}\n\n/* DEFAULT THEME */\n\n.cm-s-paper .cm-keyword {color: #555;}\n.cm-s-paper .cm-atom {color: #7f8c8d;}\n.cm-s-paper .cm-number {color: #7f8c8d;}\n.cm-s-paper .cm-def {color: #00f;}\n.cm-s-paper .cm-variable {color: black;}\n.cm-s-paper .cm-variable-2 {color: #555;}\n.cm-s-paper .cm-variable-3 {color: #085;}\n.cm-s-paper .cm-property {color: black;}\n.cm-s-paper .cm-operator {color: black;}\n.cm-s-paper .cm-comment {color: #959595;}\n.cm-s-paper .cm-string {color: #7f8c8d;}\n.cm-s-paper .cm-string-2 {color: #f50;}\n.cm-s-paper .cm-meta {color: #555;}\n.cm-s-paper .cm-error {color: #f00;}\n.cm-s-paper .cm-qualifier {color: #555;}\n.cm-s-paper .cm-builtin {color: #555;}\n.cm-s-paper .cm-bracket {color: #997;}\n.cm-s-paper .cm-tag {color: #7f8c8d;}\n.cm-s-paper .cm-attribute {color: #7f8c8d;}\n.cm-s-paper .cm-header {color: #000;}\n.cm-s-paper .cm-quote {color: #888;}\n.cm-s-paper .cm-hr {color: #999;}\n.cm-s-paper .cm-link {color: #7f8c8d;}\n\n.cm-negative {color: #d44;}\n.cm-positive {color: #292;}\n.cm-header, .cm-strong {font-weight: bold;}\n.cm-em {font-style: italic;}\n.cm-link {text-decoration: underline;}\n\n.cm-invalidchar {color: #f00;}\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}\n\n\n/* STOP */\n\n/* The rest of this file contains styles related to the mechanics of\n   the editor. You probably shouldn't touch them. */\n\n.CodeMirror {\n  position: relative;\n  overflow: hidden;\n}\n\n.CodeMirror-scroll {\n  /* 30px is the magic margin used to hide the element's real scrollbars */\n  /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */\n  margin-bottom: -30px; margin-right: -30px;\n  padding-bottom: 30px; padding-right: 30px;\n  height: 100%;\n  outline: none; /* Prevent dragging from highlighting the element */\n  position: relative;\n}\n.CodeMirror-sizer {\n  position: relative;\n  min-width: 100px !important;/* set min width */\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\n   before actuall scrolling happens, thus preventing shaking and\n   flickering artifacts. */\n.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {\n  position: absolute;\n  z-index: 6;\n  display: none;\n}\n.CodeMirror-vscrollbar {\n  right: 0; top: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n}\n.CodeMirror-hscrollbar {\n  bottom: 0; left: 0;\n  overflow-y: hidden;\n  overflow-x: scroll;\n}\n.CodeMirror-scrollbar-filler {\n  right: 0; bottom: 0;\n  z-index: 6;\n}\n\n.CodeMirror-lines {\n  cursor: text;\n}\n.CodeMirror pre {\n  /* Reset some styles that the rest of the page might have set */\n  -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;\n  border-width: 0;\n  background: transparent;\n  font-family: inherit;\n  font-size: inherit;\n  margin: 0;\n  white-space: pre-wrap;\n  word-wrap: normal;\n  line-height: inherit;\n  color: inherit;\n  z-index: 2;\n  position: relative;\n  overflow: visible;\n}\n.CodeMirror-wrap pre {\n  word-wrap: break-word;\n  white-space: pre-wrap;\n  word-break: normal;\n}\n.CodeMirror-linebackground {\n  position: absolute;\n  left: 0; right: 0; top: 0; bottom: 0;\n  z-index: 0;\n}\n\n.CodeMirror-linewidget {\n  position: relative;\n  z-index: 2;\n  overflow: auto;\n}\n\n.CodeMirror-widget {\n  display: inline-block;\n}\n\n.CodeMirror-wrap .CodeMirror-scroll {\n  overflow-x: hidden;\n}\n\n.CodeMirror-measure {\n  position: absolute;\n  width: 100%; height: 0px;\n  overflow: hidden;\n  visibility: hidden;\n}\n.CodeMirror-measure pre { position: static; }\n\n.CodeMirror div.CodeMirror-cursor {\n  position: absolute;\n  visibility: hidden;\n  border-right: none;\n  width: 0;\n}\n.CodeMirror-focused div.CodeMirror-cursor {\n  visibility: visible;\n}\n\n.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused .CodeMirror-selected { background: #BDC3C7; }\n\n.cm-searching {\n  background: #ffa;\n  background: rgba(255, 255, 0, .4);\n}\n\n/* IE7 hack to prevent it from returning funny offsetTops on the spans */\n.CodeMirror span { *vertical-align: text-bottom; }\n\n@media print {\n  /* Hide the cursor when printing */\n  .CodeMirror div.CodeMirror-cursor {\n    visibility: hidden;\n  }\n}\n.CodeMirror {\n  height: 450px;\n}\n:-webkit-full-screen {\n  background: #f9f9f5;\n  padding: 0.5em 1em;\n  width: 100%;\n  height: 100%;\n}\n:-moz-full-screen {\n  padding: 0.5em 1em;\n  background: #f9f9f5;\n  width: 100%;\n  height: 100%;\n}\n.editor-wrapper {\n  font: 16px/1.62 \"Helvetica Neue\", \"Xin Gothic\", \"Hiragino Sans GB\", \"WenQuanYi Micro Hei\", \"Microsoft YaHei\", sans-serif;\n  color: #2c3e50;\n}\n/* this is the title */\n.editor-wrapper input.title {\n  font: 18px \"Helvetica Neue\", \"Xin Gothic\", \"Hiragino Sans GB\", \"WenQuanYi Micro Hei\", \"Microsoft YaHei\", sans-serif;\n  background: transparent;\n  padding: 4px;\n  width: 100%;\n  border: none;\n  outline: none;\n  opacity: 0.6;\n}\n.editor-toolbar {\n  position: relative;\n  opacity: 0.6;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  -o-user-select: none;\n  user-select: none;\n}\n.editor-toolbar:before, .editor-toolbar:after {\n  display: block;\n  content: ' ';\n  height: 1px;\n  background-color: #bdc3c7;\n  background: -moz-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);\n  background: -webkit-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);\n  background: -ms-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);\n  background: linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);\n}\n.editor-toolbar:before {\n  margin-bottom: 8px;\n}\n.editor-toolbar:after {\n  margin-top: 8px;\n}\n.editor-wrapper input.title:hover, .editor-wrapper input.title:focus, .editor-toolbar:hover {\n  opacity: 0.8;\n}\n.editor-toolbar a {\n  display: inline-block;\n  text-align: center;\n  text-decoration: none !important;\n  color: #2c3e50 !important;\n  width: 24px;\n  height: 24px;\n  margin: 2px;\n  border: 1px solid transparent;\n  border-radius: 3px;\n  cursor: pointer;\n}\n.editor-toolbar a:hover, .editor-toolbar a.active {\n  background: #fcfcfc;\n  border-color: #95a5a6;\n}\n.editor-toolbar a:before {\n  line-height: 24px;\n}\n.editor-toolbar i.separator {\n  display: inline-block;\n  width: 0;\n  border-left: 1px solid #d9d9d9;\n  border-right: 1px solid white;\n  color: transparent;\n  text-indent: -10px;\n  margin: 0 6px;\n}\n.editor-toolbar a.eicon-fullscreen {\n  position: absolute;\n  right: 0;\n}\n.editor-statusbar {\n  border-top: 1px solid #ece9e9;\n  padding: 8px 10px;\n  font-size: 12px;\n  color: #959694;\n  text-align: right;\n}\n.editor-statusbar span {\n  display: inline-block;\n  min-width: 4em;\n  margin-left: 1em;\n}\n.editor-statusbar .lines:before {\n  content: 'lines: ';\n}\n.editor-statusbar .words:before {\n  content: 'words: ';\n}\n.editor-preview {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  top: 0;\n  left: 100%;\n  background: #f9f9f5;\n  z-index: 9999;\n  overflow: auto;\n  -webkit-transition: left 0.2s ease;\n  -moz-transition: left 0.2s ease;\n  -ms-transition: left 0.2s ease;\n  transition: left 0.2s ease;\n}\n.editor-preview-active {\n  left: 0;\n}\n.editor-preview > p {\n  margin-top: 0;\n}\n"
  },
  {
    "path": "public/libs/editor/editor.js",
    "content": "(function(global) {\n// CodeMirror version 3.15\n//\n// CodeMirror is the only global var we claim\nvar CodeMirror = (function() {\n  \"use strict\";\n\n  // BROWSER SNIFFING\n\n  // Crude, but necessary to handle a number of hard-to-feature-detect\n  // bugs and behavior differences.\n  var gecko = /gecko\\/\\d/i.test(navigator.userAgent);\n  var ie = /MSIE \\d/.test(navigator.userAgent);\n  var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);\n  var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);\n  var webkit = /WebKit\\//.test(navigator.userAgent);\n  var qtwebkit = webkit && /Qt\\/\\d+\\.\\d+/.test(navigator.userAgent);\n  var chrome = /Chrome\\//.test(navigator.userAgent);\n  var opera = /Opera\\//.test(navigator.userAgent);\n  var safari = /Apple Computer/.test(navigator.vendor);\n  var khtml = /KHTML\\//.test(navigator.userAgent);\n  var mac_geLion = /Mac OS X 1\\d\\D([7-9]|\\d\\d)\\D/.test(navigator.userAgent);\n  var mac_geMountainLion = /Mac OS X 1\\d\\D([8-9]|\\d\\d)\\D/.test(navigator.userAgent);\n  var phantom = /PhantomJS/.test(navigator.userAgent);\n\n  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\\/\\w+/.test(navigator.userAgent);\n  // This is woefully incomplete. Suggestions for alternative methods welcome.\n  var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);\n  var mac = ios || /Mac/.test(navigator.platform);\n  var windows = /windows/i.test(navigator.platform);\n\n  var opera_version = opera && navigator.userAgent.match(/Version\\/(\\d*\\.\\d*)/);\n  if (opera_version) opera_version = Number(opera_version[1]);\n  if (opera_version && opera_version >= 15) { opera = false; webkit = true; }\n  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X\n  var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));\n  var captureMiddleClick = gecko || (ie && !ie_lt9);\n\n  // Optimize some code when these features are not used\n  var sawReadOnlySpans = false, sawCollapsedSpans = false;\n\n  // CONSTRUCTOR\n\n  function CodeMirror(place, options) {\n    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);\n\n    this.options = options = options || {};\n    // Determine effective options based on given values and defaults.\n    for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))\n      options[opt] = defaults[opt];\n    setGuttersForLineNumbers(options);\n\n    var docStart = typeof options.value == \"string\" ? 0 : options.value.first;\n    var display = this.display = makeDisplay(place, docStart);\n    display.wrapper.CodeMirror = this;\n    updateGutters(this);\n    if (options.autofocus && !mobile) focusInput(this);\n\n    this.state = {keyMaps: [],\n                  overlays: [],\n                  modeGen: 0,\n                  overwrite: false, focused: false,\n                  suppressEdits: false, pasteIncoming: false,\n                  draggingText: false,\n                  highlight: new Delayed()};\n\n    themeChanged(this);\n    if (options.lineWrapping)\n      this.display.wrapper.className += \" CodeMirror-wrap\";\n\n    var doc = options.value;\n    if (typeof doc == \"string\") doc = new Doc(options.value, options.mode);\n    operation(this, attachDoc)(this, doc);\n\n    // Override magic textarea content restore that IE sometimes does\n    // on our hidden textarea on reload\n    if (ie) setTimeout(bind(resetInput, this, true), 20);\n\n    registerEventHandlers(this);\n    // IE throws unspecified error in certain cases, when\n    // trying to access activeElement before onload\n    var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }\n    if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);\n    else onBlur(this);\n\n    operation(this, function() {\n      for (var opt in optionHandlers)\n        if (optionHandlers.propertyIsEnumerable(opt))\n          optionHandlers[opt](this, options[opt], Init);\n      for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);\n    })();\n  }\n\n  // DISPLAY CONSTRUCTOR\n\n  function makeDisplay(place, docStart) {\n    var d = {};\n\n    var input = d.input = elt(\"textarea\", null, null, \"position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;\");\n    if (webkit) input.style.width = \"1000px\";\n    else input.setAttribute(\"wrap\", \"off\");\n    // if border: 0; -- iOS fails to open keyboard (issue #1287)\n    if (ios) input.style.border = \"1px solid black\";\n    input.setAttribute(\"autocorrect\", \"off\"); input.setAttribute(\"autocapitalize\", \"off\"); input.setAttribute(\"spellcheck\", \"false\");\n\n    // Wraps and hides input textarea\n    d.inputDiv = elt(\"div\", [input], null, \"overflow: hidden; position: relative; width: 3px; height: 0px;\");\n    // The actual fake scrollbars.\n    d.scrollbarH = elt(\"div\", [elt(\"div\", null, null, \"height: 1px\")], \"CodeMirror-hscrollbar\");\n    d.scrollbarV = elt(\"div\", [elt(\"div\", null, null, \"width: 1px\")], \"CodeMirror-vscrollbar\");\n    d.scrollbarFiller = elt(\"div\", null, \"CodeMirror-scrollbar-filler\");\n    d.gutterFiller = elt(\"div\", null, \"CodeMirror-gutter-filler\");\n    // DIVs containing the selection and the actual code\n    d.lineDiv = elt(\"div\", null, \"CodeMirror-code\");\n    d.selectionDiv = elt(\"div\", null, null, \"position: relative; z-index: 1\");\n    // Blinky cursor, and element used to ensure cursor fits at the end of a line\n    d.cursor = elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor\");\n    // Secondary cursor, shown when on a 'jump' in bi-directional text\n    d.otherCursor = elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor CodeMirror-secondarycursor\");\n    // Used to measure text size\n    d.measure = elt(\"div\", null, \"CodeMirror-measure\");\n    // Wraps everything that needs to exist inside the vertically-padded coordinate system\n    d.lineSpace = elt(\"div\", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],\n                         null, \"position: relative; outline: none\");\n    // Moved around its parent to cover visible view\n    d.mover = elt(\"div\", [elt(\"div\", [d.lineSpace], \"CodeMirror-lines\")], null, \"position: relative\");\n    // Set to the height of the text, causes scrolling\n    d.sizer = elt(\"div\", [d.mover], \"CodeMirror-sizer\");\n    // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers\n    d.heightForcer = elt(\"div\", null, null, \"position: absolute; height: \" + scrollerCutOff + \"px; width: 1px;\");\n    // Will contain the gutters, if any\n    d.gutters = elt(\"div\", null, \"CodeMirror-gutters\");\n    d.lineGutter = null;\n    // Provides scrolling\n    d.scroller = elt(\"div\", [d.sizer, d.heightForcer, d.gutters], \"CodeMirror-scroll\");\n    d.scroller.setAttribute(\"tabIndex\", \"-1\");\n    // The element in which the editor lives.\n    d.wrapper = elt(\"div\", [d.inputDiv, d.scrollbarH, d.scrollbarV,\n                            d.scrollbarFiller, d.gutterFiller, d.scroller], \"CodeMirror\");\n    // Work around IE7 z-index bug\n    if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }\n    if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);\n\n    // Needed to hide big blue blinking cursor on Mobile Safari\n    if (ios) input.style.width = \"0px\";\n    if (!webkit) d.scroller.draggable = true;\n    // Needed to handle Tab key in KHTML\n    if (khtml) { d.inputDiv.style.height = \"1px\"; d.inputDiv.style.position = \"absolute\"; }\n    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).\n    else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = \"18px\";\n\n    // Current visible range (may be bigger than the view window).\n    d.viewOffset = d.lastSizeC = 0;\n    d.showingFrom = d.showingTo = docStart;\n\n    // Used to only resize the line number gutter when necessary (when\n    // the amount of lines crosses a boundary that makes its width change)\n    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;\n    // See readInput and resetInput\n    d.prevInput = \"\";\n    // Set to true when a non-horizontal-scrolling widget is added. As\n    // an optimization, widget aligning is skipped when d is false.\n    d.alignWidgets = false;\n    // Flag that indicates whether we currently expect input to appear\n    // (after some event like 'keypress' or 'input') and are polling\n    // intensively.\n    d.pollingFast = false;\n    // Self-resetting timeout for the poller\n    d.poll = new Delayed();\n\n    d.cachedCharWidth = d.cachedTextHeight = null;\n    d.measureLineCache = [];\n    d.measureLineCachePos = 0;\n\n    // Tracks when resetInput has punted to just putting a short\n    // string instead of the (large) selection.\n    d.inaccurateSelection = false;\n\n    // Tracks the maximum line length so that the horizontal scrollbar\n    // can be kept static when scrolling.\n    d.maxLine = null;\n    d.maxLineLength = 0;\n    d.maxLineChanged = false;\n\n    // Used for measuring wheel scrolling granularity\n    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;\n\n    return d;\n  }\n\n  // STATE UPDATES\n\n  // Used to get the editor into a consistent state again when options change.\n\n  function loadMode(cm) {\n    cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);\n    cm.doc.iter(function(line) {\n      if (line.stateAfter) line.stateAfter = null;\n      if (line.styles) line.styles = null;\n    });\n    cm.doc.frontier = cm.doc.first;\n    startWorker(cm, 100);\n    cm.state.modeGen++;\n    if (cm.curOp) regChange(cm);\n  }\n\n  function wrappingChanged(cm) {\n    if (cm.options.lineWrapping) {\n      cm.display.wrapper.className += \" CodeMirror-wrap\";\n      cm.display.sizer.style.minWidth = \"\";\n    } else {\n      cm.display.wrapper.className = cm.display.wrapper.className.replace(\" CodeMirror-wrap\", \"\");\n      computeMaxLength(cm);\n    }\n    estimateLineHeights(cm);\n    regChange(cm);\n    clearCaches(cm);\n    setTimeout(function(){updateScrollbars(cm);}, 100);\n  }\n\n  function estimateHeight(cm) {\n    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;\n    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);\n    return function(line) {\n      if (lineIsHidden(cm.doc, line))\n        return 0;\n      else if (wrapping)\n        return (Math.ceil(line.text.length / perLine) || 1) * th;\n      else\n        return th;\n    };\n  }\n\n  function estimateLineHeights(cm) {\n    var doc = cm.doc, est = estimateHeight(cm);\n    doc.iter(function(line) {\n      var estHeight = est(line);\n      if (estHeight != line.height) updateLineHeight(line, estHeight);\n    });\n  }\n\n  function keyMapChanged(cm) {\n    var map = keyMap[cm.options.keyMap], style = map.style;\n    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\\s*cm-keymap-\\S+/g, \"\") +\n      (style ? \" cm-keymap-\" + style : \"\");\n    cm.state.disableInput = map.disableInput;\n  }\n\n  function themeChanged(cm) {\n    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\\s*cm-s-\\S+/g, \"\") +\n      cm.options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\n    clearCaches(cm);\n  }\n\n  function guttersChanged(cm) {\n    updateGutters(cm);\n    regChange(cm);\n    setTimeout(function(){alignHorizontally(cm);}, 20);\n  }\n\n  function updateGutters(cm) {\n    var gutters = cm.display.gutters, specs = cm.options.gutters;\n    removeChildren(gutters);\n    for (var i = 0; i < specs.length; ++i) {\n      var gutterClass = specs[i];\n      var gElt = gutters.appendChild(elt(\"div\", null, \"CodeMirror-gutter \" + gutterClass));\n      if (gutterClass == \"CodeMirror-linenumbers\") {\n        cm.display.lineGutter = gElt;\n        gElt.style.width = (cm.display.lineNumWidth || 1) + \"px\";\n      }\n    }\n    gutters.style.display = i ? \"\" : \"none\";\n  }\n\n  function lineLength(doc, line) {\n    if (line.height == 0) return 0;\n    var len = line.text.length, merged, cur = line;\n    while (merged = collapsedSpanAtStart(cur)) {\n      var found = merged.find();\n      cur = getLine(doc, found.from.line);\n      len += found.from.ch - found.to.ch;\n    }\n    cur = line;\n    while (merged = collapsedSpanAtEnd(cur)) {\n      var found = merged.find();\n      len -= cur.text.length - found.from.ch;\n      cur = getLine(doc, found.to.line);\n      len += cur.text.length - found.to.ch;\n    }\n    return len;\n  }\n\n  function computeMaxLength(cm) {\n    var d = cm.display, doc = cm.doc;\n    d.maxLine = getLine(doc, doc.first);\n    d.maxLineLength = lineLength(doc, d.maxLine);\n    d.maxLineChanged = true;\n    doc.iter(function(line) {\n      var len = lineLength(doc, line);\n      if (len > d.maxLineLength) {\n        d.maxLineLength = len;\n        d.maxLine = line;\n      }\n    });\n  }\n\n  // Make sure the gutters options contains the element\n  // \"CodeMirror-linenumbers\" when the lineNumbers option is true.\n  function setGuttersForLineNumbers(options) {\n    var found = false;\n    for (var i = 0; i < options.gutters.length; ++i) {\n      if (options.gutters[i] == \"CodeMirror-linenumbers\") {\n        if (options.lineNumbers) found = true;\n        else options.gutters.splice(i--, 1);\n      }\n    }\n    if (!found && options.lineNumbers)\n      options.gutters.push(\"CodeMirror-linenumbers\");\n  }\n\n  // SCROLLBARS\n\n  // Re-synchronize the fake scrollbars with the actual size of the\n  // content. Optionally force a scrollTop.\n  function updateScrollbars(cm) {\n    var d = cm.display, docHeight = cm.doc.height;\n    var totalHeight = docHeight + paddingVert(d);\n    d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + \"px\";\n    d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + \"px\";\n    var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);\n    var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);\n    var needsV = scrollHeight > (d.scroller.clientHeight + 1);\n    if (needsV) {\n      d.scrollbarV.style.display = \"block\";\n      d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + \"px\" : \"0\";\n      d.scrollbarV.firstChild.style.height =\n        (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + \"px\";\n    } else d.scrollbarV.style.display = \"\";\n    if (needsH) {\n      d.scrollbarH.style.display = \"block\";\n      d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + \"px\" : \"0\";\n      d.scrollbarH.firstChild.style.width =\n        (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + \"px\";\n    } else d.scrollbarH.style.display = \"\";\n    if (needsH && needsV) {\n      d.scrollbarFiller.style.display = \"block\";\n      d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + \"px\";\n    } else d.scrollbarFiller.style.display = \"\";\n    if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {\n      d.gutterFiller.style.display = \"block\";\n      d.gutterFiller.style.height = scrollbarWidth(d.measure) + \"px\";\n      d.gutterFiller.style.width = d.gutters.offsetWidth + \"px\";\n    } else d.gutterFiller.style.display = \"\";\n\n    if (mac_geLion && scrollbarWidth(d.measure) === 0)\n      d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? \"18px\" : \"12px\";\n  }\n\n  function visibleLines(display, doc, viewPort) {\n    var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;\n    if (typeof viewPort == \"number\") top = viewPort;\n    else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}\n    top = Math.floor(top - paddingTop(display));\n    var bottom = Math.ceil(top + height);\n    return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};\n  }\n\n  // LINE NUMBERS\n\n  function alignHorizontally(cm) {\n    var display = cm.display;\n    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;\n    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;\n    var gutterW = display.gutters.offsetWidth, l = comp + \"px\";\n    for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {\n      for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;\n    }\n    if (cm.options.fixedGutter)\n      display.gutters.style.left = (comp + gutterW) + \"px\";\n  }\n\n  function maybeUpdateLineNumberWidth(cm) {\n    if (!cm.options.lineNumbers) return false;\n    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;\n    if (last.length != display.lineNumChars) {\n      var test = display.measure.appendChild(elt(\"div\", [elt(\"div\", last)],\n                                                 \"CodeMirror-linenumber CodeMirror-gutter-elt\"));\n      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;\n      display.lineGutter.style.width = \"\";\n      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);\n      display.lineNumWidth = display.lineNumInnerWidth + padding;\n      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;\n      display.lineGutter.style.width = display.lineNumWidth + \"px\";\n      return true;\n    }\n    return false;\n  }\n\n  function lineNumberFor(options, i) {\n    return String(options.lineNumberFormatter(i + options.firstLineNumber));\n  }\n  function compensateForHScroll(display) {\n    return getRect(display.scroller).left - getRect(display.sizer).left;\n  }\n\n  // DISPLAY DRAWING\n\n  function updateDisplay(cm, changes, viewPort, forced) {\n    var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;\n    var visible = visibleLines(cm.display, cm.doc, viewPort);\n    for (;;) {\n      if (!updateDisplayInner(cm, changes, visible, forced)) break;\n      forced = false;\n      updated = true;\n      updateSelection(cm);\n      updateScrollbars(cm);\n\n      // Clip forced viewport to actual scrollable area\n      if (viewPort)\n        viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,\n                            typeof viewPort == \"number\" ? viewPort : viewPort.top);\n      visible = visibleLines(cm.display, cm.doc, viewPort);\n      if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)\n        break;\n      changes = [];\n    }\n\n    if (updated) {\n      signalLater(cm, \"update\", cm);\n      if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)\n        signalLater(cm, \"viewportChange\", cm, cm.display.showingFrom, cm.display.showingTo);\n    }\n    return updated;\n  }\n\n  // Uses a set of changes plus the current scroll position to\n  // determine which DOM updates have to be made, and makes the\n  // updates.\n  function updateDisplayInner(cm, changes, visible, forced) {\n    var display = cm.display, doc = cm.doc;\n    if (!display.wrapper.clientWidth) {\n      display.showingFrom = display.showingTo = doc.first;\n      display.viewOffset = 0;\n      return;\n    }\n\n    // Bail out if the visible area is already rendered and nothing changed.\n    if (!forced && changes.length == 0 &&\n        visible.from > display.showingFrom && visible.to < display.showingTo)\n      return;\n\n    if (maybeUpdateLineNumberWidth(cm))\n      changes = [{from: doc.first, to: doc.first + doc.size}];\n    var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + \"px\";\n    display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : \"0\";\n\n    // Used to determine which lines need their line numbers updated\n    var positionsChangedFrom = Infinity;\n    if (cm.options.lineNumbers)\n      for (var i = 0; i < changes.length; ++i)\n        if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }\n\n    var end = doc.first + doc.size;\n    var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);\n    var to = Math.min(end, visible.to + cm.options.viewportMargin);\n    if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);\n    if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);\n    if (sawCollapsedSpans) {\n      from = lineNo(visualLine(doc, getLine(doc, from)));\n      while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;\n    }\n\n    // Create a range of theoretically intact lines, and punch holes\n    // in that using the change info.\n    var intact = [{from: Math.max(display.showingFrom, doc.first),\n                   to: Math.min(display.showingTo, end)}];\n    if (intact[0].from >= intact[0].to) intact = [];\n    else intact = computeIntact(intact, changes);\n    // When merged lines are present, we might have to reduce the\n    // intact ranges because changes in continued fragments of the\n    // intact lines do require the lines to be redrawn.\n    if (sawCollapsedSpans)\n      for (var i = 0; i < intact.length; ++i) {\n        var range = intact[i], merged;\n        while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {\n          var newTo = merged.find().from.line;\n          if (newTo > range.from) range.to = newTo;\n          else { intact.splice(i--, 1); break; }\n        }\n      }\n\n    // Clip off the parts that won't be visible\n    var intactLines = 0;\n    for (var i = 0; i < intact.length; ++i) {\n      var range = intact[i];\n      if (range.from < from) range.from = from;\n      if (range.to > to) range.to = to;\n      if (range.from >= range.to) intact.splice(i--, 1);\n      else intactLines += range.to - range.from;\n    }\n    if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {\n      updateViewOffset(cm);\n      return;\n    }\n    intact.sort(function(a, b) {return a.from - b.from;});\n\n    // Avoid crashing on IE's \"unspecified error\" when in iframes\n    try {\n      var focused = document.activeElement;\n    } catch(e) {}\n    if (intactLines < (to - from) * .7) display.lineDiv.style.display = \"none\";\n    patchDisplay(cm, from, to, intact, positionsChangedFrom);\n    display.lineDiv.style.display = \"\";\n    if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();\n\n    var different = from != display.showingFrom || to != display.showingTo ||\n      display.lastSizeC != display.wrapper.clientHeight;\n    // This is just a bogus formula that detects when the editor is\n    // resized or the font size changes.\n    if (different) {\n      display.lastSizeC = display.wrapper.clientHeight;\n      startWorker(cm, 400);\n    }\n    display.showingFrom = from; display.showingTo = to;\n\n    updateHeightsInViewport(cm);\n    updateViewOffset(cm);\n\n    return true;\n  }\n\n  function updateHeightsInViewport(cm) {\n    var display = cm.display;\n    var prevBottom = display.lineDiv.offsetTop;\n    for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {\n      if (ie_lt8) {\n        var bot = node.offsetTop + node.offsetHeight;\n        height = bot - prevBottom;\n        prevBottom = bot;\n      } else {\n        var box = getRect(node);\n        height = box.bottom - box.top;\n      }\n      var diff = node.lineObj.height - height;\n      if (height < 2) height = textHeight(display);\n      if (diff > .001 || diff < -.001) {\n        updateLineHeight(node.lineObj, height);\n        var widgets = node.lineObj.widgets;\n        if (widgets) for (var i = 0; i < widgets.length; ++i)\n          widgets[i].height = widgets[i].node.offsetHeight;\n      }\n    }\n  }\n\n  function updateViewOffset(cm) {\n    var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));\n    // Position the mover div to align with the current virtual scroll position\n    cm.display.mover.style.top = off + \"px\";\n  }\n\n  function computeIntact(intact, changes) {\n    for (var i = 0, l = changes.length || 0; i < l; ++i) {\n      var change = changes[i], intact2 = [], diff = change.diff || 0;\n      for (var j = 0, l2 = intact.length; j < l2; ++j) {\n        var range = intact[j];\n        if (change.to <= range.from && change.diff) {\n          intact2.push({from: range.from + diff, to: range.to + diff});\n        } else if (change.to <= range.from || change.from >= range.to) {\n          intact2.push(range);\n        } else {\n          if (change.from > range.from)\n            intact2.push({from: range.from, to: change.from});\n          if (change.to < range.to)\n            intact2.push({from: change.to + diff, to: range.to + diff});\n        }\n      }\n      intact = intact2;\n    }\n    return intact;\n  }\n\n  function getDimensions(cm) {\n    var d = cm.display, left = {}, width = {};\n    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {\n      left[cm.options.gutters[i]] = n.offsetLeft;\n      width[cm.options.gutters[i]] = n.offsetWidth;\n    }\n    return {fixedPos: compensateForHScroll(d),\n            gutterTotalWidth: d.gutters.offsetWidth,\n            gutterLeft: left,\n            gutterWidth: width,\n            wrapperWidth: d.wrapper.clientWidth};\n  }\n\n  function patchDisplay(cm, from, to, intact, updateNumbersFrom) {\n    var dims = getDimensions(cm);\n    var display = cm.display, lineNumbers = cm.options.lineNumbers;\n    if (!intact.length && (!webkit || !cm.display.currentWheelTarget))\n      removeChildren(display.lineDiv);\n    var container = display.lineDiv, cur = container.firstChild;\n\n    function rm(node) {\n      var next = node.nextSibling;\n      if (webkit && mac && cm.display.currentWheelTarget == node) {\n        node.style.display = \"none\";\n        node.lineObj = null;\n      } else {\n        node.parentNode.removeChild(node);\n      }\n      return next;\n    }\n\n    var nextIntact = intact.shift(), lineN = from;\n    cm.doc.iter(from, to, function(line) {\n      if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();\n      if (lineIsHidden(cm.doc, line)) {\n        if (line.height != 0) updateLineHeight(line, 0);\n        if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {\n          var w = line.widgets[i];\n          if (w.showIfHidden) {\n            var prev = cur.previousSibling;\n            if (/pre/i.test(prev.nodeName)) {\n              var wrap = elt(\"div\", null, null, \"position: relative\");\n              prev.parentNode.replaceChild(wrap, prev);\n              wrap.appendChild(prev);\n              prev = wrap;\n            }\n            var wnode = prev.appendChild(elt(\"div\", [w.node], \"CodeMirror-linewidget\"));\n            if (!w.handleMouseEvents) wnode.ignoreEvents = true;\n            positionLineWidget(w, wnode, prev, dims);\n          }\n        }\n      } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {\n        // This line is intact. Skip to the actual node. Update its\n        // line number if needed.\n        while (cur.lineObj != line) cur = rm(cur);\n        if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)\n          setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));\n        cur = cur.nextSibling;\n      } else {\n        // For lines with widgets, make an attempt to find and reuse\n        // the existing element, so that widgets aren't needlessly\n        // removed and re-inserted into the dom\n        if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)\n          if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }\n        // This line needs to be generated.\n        var lineNode = buildLineElement(cm, line, lineN, dims, reuse);\n        if (lineNode != reuse) {\n          container.insertBefore(lineNode, cur);\n        } else {\n          while (cur != reuse) cur = rm(cur);\n          cur = cur.nextSibling;\n        }\n\n        lineNode.lineObj = line;\n      }\n      ++lineN;\n    });\n    while (cur) cur = rm(cur);\n  }\n\n  function buildLineElement(cm, line, lineNo, dims, reuse) {\n    var lineElement = lineContent(cm, line);\n    var markers = line.gutterMarkers, display = cm.display, wrap;\n\n    if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)\n      return lineElement;\n\n    // Lines with gutter elements, widgets or a background class need\n    // to be wrapped again, and have the extra elements added to the\n    // wrapper div\n\n    if (reuse) {\n      reuse.alignable = null;\n      var isOk = true, widgetsSeen = 0, insertBefore = null;\n      for (var n = reuse.firstChild, next; n; n = next) {\n        next = n.nextSibling;\n        if (!/\\bCodeMirror-linewidget\\b/.test(n.className)) {\n          reuse.removeChild(n);\n        } else {\n          for (var i = 0; i < line.widgets.length; ++i) {\n            var widget = line.widgets[i];\n            if (widget.node == n.firstChild) {\n              if (!widget.above && !insertBefore) insertBefore = n;\n              positionLineWidget(widget, n, reuse, dims);\n              ++widgetsSeen;\n              break;\n            }\n          }\n          if (i == line.widgets.length) { isOk = false; break; }\n        }\n      }\n      reuse.insertBefore(lineElement, insertBefore);\n      if (isOk && widgetsSeen == line.widgets.length) {\n        wrap = reuse;\n        reuse.className = line.wrapClass || \"\";\n      }\n    }\n    if (!wrap) {\n      wrap = elt(\"div\", null, line.wrapClass, \"position: relative\");\n      wrap.appendChild(lineElement);\n    }\n    // Kludge to make sure the styled element lies behind the selection (by z-index)\n    if (line.bgClass)\n      wrap.insertBefore(elt(\"div\", null, line.bgClass + \" CodeMirror-linebackground\"), wrap.firstChild);\n    if (cm.options.lineNumbers || markers) {\n      var gutterWrap = wrap.insertBefore(elt(\"div\", null, null, \"position: absolute; left: \" +\n                                             (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px\"),\n                                         wrap.firstChild);\n      if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);\n      if (cm.options.lineNumbers && (!markers || !markers[\"CodeMirror-linenumbers\"]))\n        wrap.lineNumber = gutterWrap.appendChild(\n          elt(\"div\", lineNumberFor(cm.options, lineNo),\n              \"CodeMirror-linenumber CodeMirror-gutter-elt\",\n              \"left: \" + dims.gutterLeft[\"CodeMirror-linenumbers\"] + \"px; width: \"\n              + display.lineNumInnerWidth + \"px\"));\n      if (markers)\n        for (var k = 0; k < cm.options.gutters.length; ++k) {\n          var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];\n          if (found)\n            gutterWrap.appendChild(elt(\"div\", [found], \"CodeMirror-gutter-elt\", \"left: \" +\n                                       dims.gutterLeft[id] + \"px; width: \" + dims.gutterWidth[id] + \"px\"));\n        }\n    }\n    if (ie_lt8) wrap.style.zIndex = 2;\n    if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {\n      var widget = ws[i], node = elt(\"div\", [widget.node], \"CodeMirror-linewidget\");\n      if (!widget.handleMouseEvents) node.ignoreEvents = true;\n      positionLineWidget(widget, node, wrap, dims);\n      if (widget.above)\n        wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);\n      else\n        wrap.appendChild(node);\n      signalLater(widget, \"redraw\");\n    }\n    return wrap;\n  }\n\n  function positionLineWidget(widget, node, wrap, dims) {\n    if (widget.noHScroll) {\n      (wrap.alignable || (wrap.alignable = [])).push(node);\n      var width = dims.wrapperWidth;\n      node.style.left = dims.fixedPos + \"px\";\n      if (!widget.coverGutter) {\n        width -= dims.gutterTotalWidth;\n        node.style.paddingLeft = dims.gutterTotalWidth + \"px\";\n      }\n      node.style.width = width + \"px\";\n    }\n    if (widget.coverGutter) {\n      node.style.zIndex = 5;\n      node.style.position = \"relative\";\n      if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + \"px\";\n    }\n  }\n\n  // SELECTION / CURSOR\n\n  function updateSelection(cm) {\n    var display = cm.display;\n    var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);\n    if (collapsed || cm.options.showCursorWhenSelecting)\n      updateSelectionCursor(cm);\n    else\n      display.cursor.style.display = display.otherCursor.style.display = \"none\";\n    if (!collapsed)\n      updateSelectionRange(cm);\n    else\n      display.selectionDiv.style.display = \"none\";\n\n    // Move the hidden textarea near the cursor to prevent scrolling artifacts\n    if (cm.options.moveInputWithCursor) {\n      var headPos = cursorCoords(cm, cm.doc.sel.head, \"div\");\n      var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);\n      display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,\n                                                        headPos.top + lineOff.top - wrapOff.top)) + \"px\";\n      display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,\n                                                         headPos.left + lineOff.left - wrapOff.left)) + \"px\";\n    }\n  }\n\n  // No selection, plain cursor\n  function updateSelectionCursor(cm) {\n    var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, \"div\");\n    display.cursor.style.left = pos.left + \"px\";\n    display.cursor.style.top = pos.top + \"px\";\n    display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + \"px\";\n    display.cursor.style.display = \"\";\n\n    if (pos.other) {\n      display.otherCursor.style.display = \"\";\n      display.otherCursor.style.left = pos.other.left + \"px\";\n      display.otherCursor.style.top = pos.other.top + \"px\";\n      display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + \"px\";\n    } else { display.otherCursor.style.display = \"none\"; }\n  }\n\n  // Highlight selection\n  function updateSelectionRange(cm) {\n    var display = cm.display, doc = cm.doc, sel = cm.doc.sel;\n    var fragment = document.createDocumentFragment();\n    var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);\n\n    function add(left, top, width, bottom) {\n      if (top < 0) top = 0;\n      fragment.appendChild(elt(\"div\", null, \"CodeMirror-selected\", \"position: absolute; left: \" + left +\n                               \"px; top: \" + top + \"px; width: \" + (width == null ? clientWidth - left : width) +\n                               \"px; height: \" + (bottom - top) + \"px\"));\n    }\n\n    function drawForLine(line, fromArg, toArg) {\n      var lineObj = getLine(doc, line);\n      var lineLen = lineObj.text.length;\n      var start, end;\n      function coords(ch, bias) {\n        return charCoords(cm, Pos(line, ch), \"div\", lineObj, bias);\n      }\n\n      iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {\n        var leftPos = coords(from, \"left\"), rightPos, left, right;\n        if (from == to) {\n          rightPos = leftPos;\n          left = right = leftPos.left;\n        } else {\n          rightPos = coords(to - 1, \"right\");\n          if (dir == \"rtl\") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }\n          left = leftPos.left;\n          right = rightPos.right;\n        }\n        if (fromArg == null && from == 0) left = pl;\n        if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part\n          add(left, leftPos.top, null, leftPos.bottom);\n          left = pl;\n          if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);\n        }\n        if (toArg == null && to == lineLen) right = clientWidth;\n        if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)\n          start = leftPos;\n        if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)\n          end = rightPos;\n        if (left < pl + 1) left = pl;\n        add(left, rightPos.top, right - left, rightPos.bottom);\n      });\n      return {start: start, end: end};\n    }\n\n    if (sel.from.line == sel.to.line) {\n      drawForLine(sel.from.line, sel.from.ch, sel.to.ch);\n    } else {\n      var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);\n      var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);\n      var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;\n      var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;\n      if (singleVLine) {\n        if (leftEnd.top < rightStart.top - 2) {\n          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);\n          add(pl, rightStart.top, rightStart.left, rightStart.bottom);\n        } else {\n          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);\n        }\n      }\n      if (leftEnd.bottom < rightStart.top)\n        add(pl, leftEnd.bottom, null, rightStart.top);\n    }\n\n    removeChildrenAndAdd(display.selectionDiv, fragment);\n    display.selectionDiv.style.display = \"\";\n  }\n\n  // Cursor-blinking\n  function restartBlink(cm) {\n    if (!cm.state.focused) return;\n    var display = cm.display;\n    clearInterval(display.blinker);\n    var on = true;\n    display.cursor.style.visibility = display.otherCursor.style.visibility = \"\";\n    display.blinker = setInterval(function() {\n      display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? \"\" : \"hidden\";\n    }, cm.options.cursorBlinkRate);\n  }\n\n  // HIGHLIGHT WORKER\n\n  function startWorker(cm, time) {\n    if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)\n      cm.state.highlight.set(time, bind(highlightWorker, cm));\n  }\n\n  function highlightWorker(cm) {\n    var doc = cm.doc;\n    if (doc.frontier < doc.first) doc.frontier = doc.first;\n    if (doc.frontier >= cm.display.showingTo) return;\n    var end = +new Date + cm.options.workTime;\n    var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));\n    var changed = [], prevChange;\n    doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {\n      if (doc.frontier >= cm.display.showingFrom) { // Visible\n        var oldStyles = line.styles;\n        line.styles = highlightLine(cm, line, state);\n        var ischange = !oldStyles || oldStyles.length != line.styles.length;\n        for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];\n        if (ischange) {\n          if (prevChange && prevChange.end == doc.frontier) prevChange.end++;\n          else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});\n        }\n        line.stateAfter = copyState(doc.mode, state);\n      } else {\n        processLine(cm, line, state);\n        line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;\n      }\n      ++doc.frontier;\n      if (+new Date > end) {\n        startWorker(cm, cm.options.workDelay);\n        return true;\n      }\n    });\n    if (changed.length)\n      operation(cm, function() {\n        for (var i = 0; i < changed.length; ++i)\n          regChange(this, changed[i].start, changed[i].end);\n      })();\n  }\n\n  // Finds the line to start with when starting a parse. Tries to\n  // find a line with a stateAfter, so that it can start with a\n  // valid state. If that fails, it returns the line with the\n  // smallest indentation, which tends to need the least context to\n  // parse correctly.\n  function findStartLine(cm, n, precise) {\n    var minindent, minline, doc = cm.doc;\n    for (var search = n, lim = n - 100; search > lim; --search) {\n      if (search <= doc.first) return doc.first;\n      var line = getLine(doc, search - 1);\n      if (line.stateAfter && (!precise || search <= doc.frontier)) return search;\n      var indented = countColumn(line.text, null, cm.options.tabSize);\n      if (minline == null || minindent > indented) {\n        minline = search - 1;\n        minindent = indented;\n      }\n    }\n    return minline;\n  }\n\n  function getStateBefore(cm, n, precise) {\n    var doc = cm.doc, display = cm.display;\n      if (!doc.mode.startState) return true;\n    var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;\n    if (!state) state = startState(doc.mode);\n    else state = copyState(doc.mode, state);\n    doc.iter(pos, n, function(line) {\n      processLine(cm, line, state);\n      var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;\n      line.stateAfter = save ? copyState(doc.mode, state) : null;\n      ++pos;\n    });\n    return state;\n  }\n\n  // POSITION MEASUREMENT\n\n  function paddingTop(display) {return display.lineSpace.offsetTop;}\n  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}\n  function paddingLeft(display) {\n    var e = removeChildrenAndAdd(display.measure, elt(\"pre\", null, null, \"text-align: left\")).appendChild(elt(\"span\", \"x\"));\n    return e.offsetLeft;\n  }\n\n  function measureChar(cm, line, ch, data, bias) {\n    var dir = -1;\n    data = data || measureLine(cm, line);\n\n    for (var pos = ch;; pos += dir) {\n      var r = data[pos];\n      if (r) break;\n      if (dir < 0 && pos == 0) dir = 1;\n    }\n    bias = pos > ch ? \"left\" : pos < ch ? \"right\" : bias;\n    if (bias == \"left\" && r.leftSide) r = r.leftSide;\n    else if (bias == \"right\" && r.rightSide) r = r.rightSide;\n    return {left: pos < ch ? r.right : r.left,\n            right: pos > ch ? r.left : r.right,\n            top: r.top,\n            bottom: r.bottom};\n  }\n\n  function findCachedMeasurement(cm, line) {\n    var cache = cm.display.measureLineCache;\n    for (var i = 0; i < cache.length; ++i) {\n      var memo = cache[i];\n      if (memo.text == line.text && memo.markedSpans == line.markedSpans &&\n          cm.display.scroller.clientWidth == memo.width &&\n          memo.classes == line.textClass + \"|\" + line.bgClass + \"|\" + line.wrapClass)\n        return memo;\n    }\n  }\n\n  function clearCachedMeasurement(cm, line) {\n    var exists = findCachedMeasurement(cm, line);\n    if (exists) exists.text = exists.measure = exists.markedSpans = null;\n  }\n\n  function measureLine(cm, line) {\n    // First look in the cache\n    var cached = findCachedMeasurement(cm, line);\n    if (cached) return cached.measure;\n\n    // Failing that, recompute and store result in cache\n    var measure = measureLineInner(cm, line);\n    var cache = cm.display.measureLineCache;\n    var memo = {text: line.text, width: cm.display.scroller.clientWidth,\n                markedSpans: line.markedSpans, measure: measure,\n                classes: line.textClass + \"|\" + line.bgClass + \"|\" + line.wrapClass};\n    if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;\n    else cache.push(memo);\n    return measure;\n  }\n\n  function measureLineInner(cm, line) {\n    var display = cm.display, measure = emptyArray(line.text.length);\n    var pre = lineContent(cm, line, measure, true);\n\n    // IE does not cache element positions of inline elements between\n    // calls to getBoundingClientRect. This makes the loop below,\n    // which gathers the positions of all the characters on the line,\n    // do an amount of layout work quadratic to the number of\n    // characters. When line wrapping is off, we try to improve things\n    // by first subdividing the line into a bunch of inline blocks, so\n    // that IE can reuse most of the layout information from caches\n    // for those blocks. This does interfere with line wrapping, so it\n    // doesn't work when wrapping is on, but in that case the\n    // situation is slightly better, since IE does cache line-wrapping\n    // information and only recomputes per-line.\n    if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {\n      var fragment = document.createDocumentFragment();\n      var chunk = 10, n = pre.childNodes.length;\n      for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {\n        var wrap = elt(\"div\", null, null, \"display: inline-block\");\n        for (var j = 0; j < chunk && n; ++j) {\n          wrap.appendChild(pre.firstChild);\n          --n;\n        }\n        fragment.appendChild(wrap);\n      }\n      pre.appendChild(fragment);\n    }\n\n    removeChildrenAndAdd(display.measure, pre);\n\n    var outer = getRect(display.lineDiv);\n    var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;\n    // Work around an IE7/8 bug where it will sometimes have randomly\n    // replaced our pre with a clone at this point.\n    if (ie_lt9 && display.measure.first != pre)\n      removeChildrenAndAdd(display.measure, pre);\n\n    function measureRect(rect) {\n      var top = rect.top - outer.top, bot = rect.bottom - outer.top;\n      if (bot > maxBot) bot = maxBot;\n      if (top < 0) top = 0;\n      for (var i = vranges.length - 2; i >= 0; i -= 2) {\n        var rtop = vranges[i], rbot = vranges[i+1];\n        if (rtop > bot || rbot < top) continue;\n        if (rtop <= top && rbot >= bot ||\n            top <= rtop && bot >= rbot ||\n            Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {\n          vranges[i] = Math.min(top, rtop);\n          vranges[i+1] = Math.max(bot, rbot);\n          break;\n        }\n      }\n      if (i < 0) { i = vranges.length; vranges.push(top, bot); }\n      return {left: rect.left - outer.left,\n              right: rect.right - outer.left,\n              top: i, bottom: null};\n    }\n    function finishRect(rect) {\n      rect.bottom = vranges[rect.top+1];\n      rect.top = vranges[rect.top];\n    }\n\n    for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {\n      var node = cur, rect = null;\n      // A widget might wrap, needs special care\n      if (/\\bCodeMirror-widget\\b/.test(cur.className) && cur.getClientRects) {\n        if (cur.firstChild.nodeType == 1) node = cur.firstChild;\n        var rects = node.getClientRects();\n        if (rects.length > 1) {\n          rect = data[i] = measureRect(rects[0]);\n          rect.rightSide = measureRect(rects[rects.length - 1]);\n        }\n      }\n      if (!rect) rect = data[i] = measureRect(getRect(node));\n      if (cur.measureRight) rect.right = getRect(cur.measureRight).left;\n      if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));\n    }\n    for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {\n      finishRect(cur);\n      if (cur.leftSide) finishRect(cur.leftSide);\n      if (cur.rightSide) finishRect(cur.rightSide);\n    }\n    return data;\n  }\n\n  function measureLineWidth(cm, line) {\n    var hasBadSpan = false;\n    if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {\n      var sp = line.markedSpans[i];\n      if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;\n    }\n    var cached = !hasBadSpan && findCachedMeasurement(cm, line);\n    if (cached) return measureChar(cm, line, line.text.length, cached.measure, \"right\").right;\n\n    var pre = lineContent(cm, line, null, true);\n    var end = pre.appendChild(zeroWidthElement(cm.display.measure));\n    removeChildrenAndAdd(cm.display.measure, pre);\n    return getRect(end).right - getRect(cm.display.lineDiv).left;\n  }\n\n  function clearCaches(cm) {\n    cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;\n    cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;\n    if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;\n    cm.display.lineNumChars = null;\n  }\n\n  function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }\n  function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }\n\n  // Context is one of \"line\", \"div\" (display.lineDiv), \"local\"/null (editor), or \"page\"\n  function intoCoordSystem(cm, lineObj, rect, context) {\n    if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {\n      var size = widgetHeight(lineObj.widgets[i]);\n      rect.top += size; rect.bottom += size;\n    }\n    if (context == \"line\") return rect;\n    if (!context) context = \"local\";\n    var yOff = heightAtLine(cm, lineObj);\n    if (context == \"local\") yOff += paddingTop(cm.display);\n    else yOff -= cm.display.viewOffset;\n    if (context == \"page\" || context == \"window\") {\n      var lOff = getRect(cm.display.lineSpace);\n      yOff += lOff.top + (context == \"window\" ? 0 : pageScrollY());\n      var xOff = lOff.left + (context == \"window\" ? 0 : pageScrollX());\n      rect.left += xOff; rect.right += xOff;\n    }\n    rect.top += yOff; rect.bottom += yOff;\n    return rect;\n  }\n\n  // Context may be \"window\", \"page\", \"div\", or \"local\"/null\n  // Result is in \"div\" coords\n  function fromCoordSystem(cm, coords, context) {\n    if (context == \"div\") return coords;\n    var left = coords.left, top = coords.top;\n    // First move into \"page\" coordinate system\n    if (context == \"page\") {\n      left -= pageScrollX();\n      top -= pageScrollY();\n    } else if (context == \"local\" || !context) {\n      var localBox = getRect(cm.display.sizer);\n      left += localBox.left;\n      top += localBox.top;\n    }\n\n    var lineSpaceBox = getRect(cm.display.lineSpace);\n    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};\n  }\n\n  function charCoords(cm, pos, context, lineObj, bias) {\n    if (!lineObj) lineObj = getLine(cm.doc, pos.line);\n    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);\n  }\n\n  function cursorCoords(cm, pos, context, lineObj, measurement) {\n    lineObj = lineObj || getLine(cm.doc, pos.line);\n    if (!measurement) measurement = measureLine(cm, lineObj);\n    function get(ch, right) {\n      var m = measureChar(cm, lineObj, ch, measurement, right ? \"right\" : \"left\");\n      if (right) m.left = m.right; else m.right = m.left;\n      return intoCoordSystem(cm, lineObj, m, context);\n    }\n    function getBidi(ch, partPos) {\n      var part = order[partPos], right = part.level % 2;\n      if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {\n        part = order[--partPos];\n        ch = bidiRight(part) - (part.level % 2 ? 0 : 1);\n        right = true;\n      } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {\n        part = order[++partPos];\n        ch = bidiLeft(part) - part.level % 2;\n        right = false;\n      }\n      if (right && ch == part.to && ch > part.from) return get(ch - 1);\n      return get(ch, right);\n    }\n    var order = getOrder(lineObj), ch = pos.ch;\n    if (!order) return get(ch);\n    var partPos = getBidiPartAt(order, ch);\n    var val = getBidi(ch, partPos);\n    if (bidiOther != null) val.other = getBidi(ch, bidiOther);\n    return val;\n  }\n\n  function PosWithInfo(line, ch, outside, xRel) {\n    var pos = new Pos(line, ch);\n    pos.xRel = xRel;\n    if (outside) pos.outside = true;\n    return pos;\n  }\n\n  // Coords must be lineSpace-local\n  function coordsChar(cm, x, y) {\n    var doc = cm.doc;\n    y += cm.display.viewOffset;\n    if (y < 0) return PosWithInfo(doc.first, 0, true, -1);\n    var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;\n    if (lineNo > last)\n      return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);\n    if (x < 0) x = 0;\n\n    for (;;) {\n      var lineObj = getLine(doc, lineNo);\n      var found = coordsCharInner(cm, lineObj, lineNo, x, y);\n      var merged = collapsedSpanAtEnd(lineObj);\n      var mergedPos = merged && merged.find();\n      if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))\n        lineNo = mergedPos.to.line;\n      else\n        return found;\n    }\n  }\n\n  function coordsCharInner(cm, lineObj, lineNo, x, y) {\n    var innerOff = y - heightAtLine(cm, lineObj);\n    var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;\n    var measurement = measureLine(cm, lineObj);\n\n    function getX(ch) {\n      var sp = cursorCoords(cm, Pos(lineNo, ch), \"line\",\n                            lineObj, measurement);\n      wrongLine = true;\n      if (innerOff > sp.bottom) return sp.left - adjust;\n      else if (innerOff < sp.top) return sp.left + adjust;\n      else wrongLine = false;\n      return sp.left;\n    }\n\n    var bidi = getOrder(lineObj), dist = lineObj.text.length;\n    var from = lineLeft(lineObj), to = lineRight(lineObj);\n    var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;\n\n    if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);\n    // Do a binary search between these bounds.\n    for (;;) {\n      if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {\n        var ch = x < fromX || x - fromX <= toX - x ? from : to;\n        var xDiff = x - (ch == from ? fromX : toX);\n        while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;\n        var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,\n                              xDiff < 0 ? -1 : xDiff ? 1 : 0);\n        return pos;\n      }\n      var step = Math.ceil(dist / 2), middle = from + step;\n      if (bidi) {\n        middle = from;\n        for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);\n      }\n      var middleX = getX(middle);\n      if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}\n      else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}\n    }\n  }\n\n  var measureText;\n  function textHeight(display) {\n    if (display.cachedTextHeight != null) return display.cachedTextHeight;\n    if (measureText == null) {\n      measureText = elt(\"pre\");\n      // Measure a bunch of lines, for browsers that compute\n      // fractional heights.\n      for (var i = 0; i < 49; ++i) {\n        measureText.appendChild(document.createTextNode(\"x\"));\n        measureText.appendChild(elt(\"br\"));\n      }\n      measureText.appendChild(document.createTextNode(\"x\"));\n    }\n    removeChildrenAndAdd(display.measure, measureText);\n    var height = measureText.offsetHeight / 50;\n    if (height > 3) display.cachedTextHeight = height;\n    removeChildren(display.measure);\n    return height || 1;\n  }\n\n  function charWidth(display) {\n    if (display.cachedCharWidth != null) return display.cachedCharWidth;\n    var anchor = elt(\"span\", \"x\");\n    var pre = elt(\"pre\", [anchor]);\n    removeChildrenAndAdd(display.measure, pre);\n    var width = anchor.offsetWidth;\n    if (width > 2) display.cachedCharWidth = width;\n    return width || 10;\n  }\n\n  // OPERATIONS\n\n  // Operations are used to wrap changes in such a way that each\n  // change won't have to update the cursor and display (which would\n  // be awkward, slow, and error-prone), but instead updates are\n  // batched and then all combined and executed at once.\n\n  var nextOpId = 0;\n  function startOperation(cm) {\n    cm.curOp = {\n      // An array of ranges of lines that have to be updated. See\n      // updateDisplay.\n      changes: [],\n      forceUpdate: false,\n      updateInput: null,\n      userSelChange: null,\n      textChanged: null,\n      selectionChanged: false,\n      cursorActivity: false,\n      updateMaxLine: false,\n      updateScrollPos: false,\n      id: ++nextOpId\n    };\n    if (!delayedCallbackDepth++) delayedCallbacks = [];\n  }\n\n  function endOperation(cm) {\n    var op = cm.curOp, doc = cm.doc, display = cm.display;\n    cm.curOp = null;\n\n    if (op.updateMaxLine) computeMaxLength(cm);\n    if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {\n      var width = measureLineWidth(cm, display.maxLine);\n      display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + \"px\";\n      display.maxLineChanged = false;\n      var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);\n      if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)\n        setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);\n    }\n    var newScrollPos, updated;\n    if (op.updateScrollPos) {\n      newScrollPos = op.updateScrollPos;\n    } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible\n      var coords = cursorCoords(cm, doc.sel.head);\n      newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);\n    }\n    if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {\n      updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);\n      if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;\n    }\n    if (!updated && op.selectionChanged) updateSelection(cm);\n    if (op.updateScrollPos) {\n      display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;\n      display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;\n      alignHorizontally(cm);\n      if (op.scrollToPos)\n        scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);\n    } else if (newScrollPos) {\n      scrollCursorIntoView(cm);\n    }\n    if (op.selectionChanged) restartBlink(cm);\n\n    if (cm.state.focused && op.updateInput)\n      resetInput(cm, op.userSelChange);\n\n    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;\n    if (hidden) for (var i = 0; i < hidden.length; ++i)\n      if (!hidden[i].lines.length) signal(hidden[i], \"hide\");\n    if (unhidden) for (var i = 0; i < unhidden.length; ++i)\n      if (unhidden[i].lines.length) signal(unhidden[i], \"unhide\");\n\n    var delayed;\n    if (!--delayedCallbackDepth) {\n      delayed = delayedCallbacks;\n      delayedCallbacks = null;\n    }\n    if (op.textChanged)\n      signal(cm, \"change\", cm, op.textChanged);\n    if (op.cursorActivity) signal(cm, \"cursorActivity\", cm);\n    if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();\n  }\n\n  // Wraps a function in an operation. Returns the wrapped function.\n  function operation(cm1, f) {\n    return function() {\n      var cm = cm1 || this, withOp = !cm.curOp;\n      if (withOp) startOperation(cm);\n      try { var result = f.apply(cm, arguments); }\n      finally { if (withOp) endOperation(cm); }\n      return result;\n    };\n  }\n  function docOperation(f) {\n    return function() {\n      var withOp = this.cm && !this.cm.curOp, result;\n      if (withOp) startOperation(this.cm);\n      try { result = f.apply(this, arguments); }\n      finally { if (withOp) endOperation(this.cm); }\n      return result;\n    };\n  }\n  function runInOp(cm, f) {\n    var withOp = !cm.curOp, result;\n    if (withOp) startOperation(cm);\n    try { result = f(); }\n    finally { if (withOp) endOperation(cm); }\n    return result;\n  }\n\n  function regChange(cm, from, to, lendiff) {\n    if (from == null) from = cm.doc.first;\n    if (to == null) to = cm.doc.first + cm.doc.size;\n    cm.curOp.changes.push({from: from, to: to, diff: lendiff});\n  }\n\n  // INPUT HANDLING\n\n  function slowPoll(cm) {\n    if (cm.display.pollingFast) return;\n    cm.display.poll.set(cm.options.pollInterval, function() {\n      readInput(cm);\n      if (cm.state.focused) slowPoll(cm);\n    });\n  }\n\n  function fastPoll(cm) {\n    var missed = false;\n    cm.display.pollingFast = true;\n    function p() {\n      var changed = readInput(cm);\n      if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}\n      else {cm.display.pollingFast = false; slowPoll(cm);}\n    }\n    cm.display.poll.set(20, p);\n  }\n\n  // prevInput is a hack to work with IME. If we reset the textarea\n  // on every change, that breaks IME. So we look for changes\n  // compared to the previous content instead. (Modern browsers have\n  // events that indicate IME taking place, but these are not widely\n  // supported or compatible enough yet to rely on.)\n  function readInput(cm) {\n    var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;\n    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;\n    var text = input.value;\n    if (text == prevInput && posEq(sel.from, sel.to)) return false;\n    if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {\n      resetInput(cm, true);\n      return false;\n    }\n\n    var withOp = !cm.curOp;\n    if (withOp) startOperation(cm);\n    sel.shift = false;\n    var same = 0, l = Math.min(prevInput.length, text.length);\n    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;\n    var from = sel.from, to = sel.to;\n    if (same < prevInput.length)\n      from = Pos(from.line, from.ch - (prevInput.length - same));\n    else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)\n      to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));\n\n    var updateInput = cm.curOp.updateInput;\n    var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),\n                       origin: cm.state.pasteIncoming ? \"paste\" : \"+input\"};\n    makeChange(cm.doc, changeEvent, \"end\");\n    cm.curOp.updateInput = updateInput;\n    signalLater(cm, \"inputRead\", cm, changeEvent);\n\n    if (text.length > 1000 || text.indexOf(\"\\n\") > -1) input.value = cm.display.prevInput = \"\";\n    else cm.display.prevInput = text;\n    if (withOp) endOperation(cm);\n    cm.state.pasteIncoming = false;\n    return true;\n  }\n\n  function resetInput(cm, user) {\n    var minimal, selected, doc = cm.doc;\n    if (!posEq(doc.sel.from, doc.sel.to)) {\n      cm.display.prevInput = \"\";\n      minimal = hasCopyEvent &&\n        (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);\n      var content = minimal ? \"-\" : selected || cm.getSelection();\n      cm.display.input.value = content;\n      if (cm.state.focused) selectInput(cm.display.input);\n      if (ie && !ie_lt9) cm.display.inputHasSelection = content;\n    } else if (user) {\n      cm.display.prevInput = cm.display.input.value = \"\";\n      if (ie && !ie_lt9) cm.display.inputHasSelection = null;\n    }\n    cm.display.inaccurateSelection = minimal;\n  }\n\n  function focusInput(cm) {\n    if (cm.options.readOnly != \"nocursor\" && (!mobile || document.activeElement != cm.display.input))\n      cm.display.input.focus();\n  }\n\n  function isReadOnly(cm) {\n    return cm.options.readOnly || cm.doc.cantEdit;\n  }\n\n  // EVENT HANDLERS\n\n  function registerEventHandlers(cm) {\n    var d = cm.display;\n    on(d.scroller, \"mousedown\", operation(cm, onMouseDown));\n    if (ie)\n      on(d.scroller, \"dblclick\", operation(cm, function(e) {\n        if (signalDOMEvent(cm, e)) return;\n        var pos = posFromMouse(cm, e);\n        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;\n        e_preventDefault(e);\n        var word = findWordAt(getLine(cm.doc, pos.line).text, pos);\n        extendSelection(cm.doc, word.from, word.to);\n      }));\n    else\n      on(d.scroller, \"dblclick\", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });\n    on(d.lineSpace, \"selectstart\", function(e) {\n      if (!eventInWidget(d, e)) e_preventDefault(e);\n    });\n    // Gecko browsers fire contextmenu *after* opening the menu, at\n    // which point we can't mess with it anymore. Context menu is\n    // handled in onMouseDown for Gecko.\n    if (!captureMiddleClick) on(d.scroller, \"contextmenu\", function(e) {onContextMenu(cm, e);});\n\n    on(d.scroller, \"scroll\", function() {\n      if (d.scroller.clientHeight) {\n        setScrollTop(cm, d.scroller.scrollTop);\n        setScrollLeft(cm, d.scroller.scrollLeft, true);\n        signal(cm, \"scroll\", cm);\n      }\n    });\n    on(d.scrollbarV, \"scroll\", function() {\n      if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);\n    });\n    on(d.scrollbarH, \"scroll\", function() {\n      if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);\n    });\n\n    on(d.scroller, \"mousewheel\", function(e){onScrollWheel(cm, e);});\n    on(d.scroller, \"DOMMouseScroll\", function(e){onScrollWheel(cm, e);});\n\n    function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }\n    on(d.scrollbarH, \"mousedown\", reFocus);\n    on(d.scrollbarV, \"mousedown\", reFocus);\n    // Prevent wrapper from ever scrolling\n    on(d.wrapper, \"scroll\", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });\n\n    var resizeTimer;\n    function onResize() {\n      if (resizeTimer == null) resizeTimer = setTimeout(function() {\n        resizeTimer = null;\n        // Might be a text scaling operation, clear size caches.\n        d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;\n        clearCaches(cm);\n        runInOp(cm, bind(regChange, cm));\n      }, 100);\n    }\n    on(window, \"resize\", onResize);\n    // Above handler holds on to the editor and its data structures.\n    // Here we poll to unregister it when the editor is no longer in\n    // the document, so that it can be garbage-collected.\n    function unregister() {\n      for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}\n      if (p) setTimeout(unregister, 5000);\n      else off(window, \"resize\", onResize);\n    }\n    setTimeout(unregister, 5000);\n\n    on(d.input, \"keyup\", operation(cm, function(e) {\n      if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;\n      if (e.keyCode == 16) cm.doc.sel.shift = false;\n    }));\n    on(d.input, \"input\", bind(fastPoll, cm));\n    on(d.input, \"keydown\", operation(cm, onKeyDown));\n    on(d.input, \"keypress\", operation(cm, onKeyPress));\n    on(d.input, \"focus\", bind(onFocus, cm));\n    on(d.input, \"blur\", bind(onBlur, cm));\n\n    function drag_(e) {\n      if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;\n      e_stop(e);\n    }\n    if (cm.options.dragDrop) {\n      on(d.scroller, \"dragstart\", function(e){onDragStart(cm, e);});\n      on(d.scroller, \"dragenter\", drag_);\n      on(d.scroller, \"dragover\", drag_);\n      on(d.scroller, \"drop\", operation(cm, onDrop));\n    }\n    on(d.scroller, \"paste\", function(e){\n      if (eventInWidget(d, e)) return;\n      focusInput(cm);\n      fastPoll(cm);\n    });\n    on(d.input, \"paste\", function() {\n      cm.state.pasteIncoming = true;\n      fastPoll(cm);\n    });\n\n    function prepareCopy() {\n      if (d.inaccurateSelection) {\n        d.prevInput = \"\";\n        d.inaccurateSelection = false;\n        d.input.value = cm.getSelection();\n        selectInput(d.input);\n      }\n    }\n    on(d.input, \"cut\", prepareCopy);\n    on(d.input, \"copy\", prepareCopy);\n\n    // Needed to handle Tab key in KHTML\n    if (khtml) on(d.sizer, \"mouseup\", function() {\n        if (document.activeElement == d.input) d.input.blur();\n        focusInput(cm);\n    });\n  }\n\n  function eventInWidget(display, e) {\n    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {\n      if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;\n    }\n  }\n\n  function posFromMouse(cm, e, liberal) {\n    var display = cm.display;\n    if (!liberal) {\n      var target = e_target(e);\n      if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||\n          target == display.scrollbarV || target == display.scrollbarV.firstChild ||\n          target == display.scrollbarFiller || target == display.gutterFiller) return null;\n    }\n    var x, y, space = getRect(display.lineSpace);\n    // Fails unpredictably on IE[67] when mouse is dragged around quickly.\n    try { x = e.clientX; y = e.clientY; } catch (e) { return null; }\n    return coordsChar(cm, x - space.left, y - space.top);\n  }\n\n  var lastClick, lastDoubleClick;\n  function onMouseDown(e) {\n    if (signalDOMEvent(this, e)) return;\n    var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;\n    sel.shift = e.shiftKey;\n\n    if (eventInWidget(display, e)) {\n      if (!webkit) {\n        display.scroller.draggable = false;\n        setTimeout(function(){display.scroller.draggable = true;}, 100);\n      }\n      return;\n    }\n    if (clickInGutter(cm, e)) return;\n    var start = posFromMouse(cm, e);\n\n    switch (e_button(e)) {\n    case 3:\n      if (captureMiddleClick) onContextMenu.call(cm, cm, e);\n      return;\n    case 2:\n      if (start) extendSelection(cm.doc, start);\n      setTimeout(bind(focusInput, cm), 20);\n      e_preventDefault(e);\n      return;\n    }\n    // For button 1, if it was clicked inside the editor\n    // (posFromMouse returning non-null), we have to adjust the\n    // selection.\n    if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}\n\n    if (!cm.state.focused) onFocus(cm);\n\n    var now = +new Date, type = \"single\";\n    if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {\n      type = \"triple\";\n      e_preventDefault(e);\n      setTimeout(bind(focusInput, cm), 20);\n      selectLine(cm, start.line);\n    } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {\n      type = \"double\";\n      lastDoubleClick = {time: now, pos: start};\n      e_preventDefault(e);\n      var word = findWordAt(getLine(doc, start.line).text, start);\n      extendSelection(cm.doc, word.from, word.to);\n    } else { lastClick = {time: now, pos: start}; }\n\n    var last = start;\n    if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&\n        !posLess(start, sel.from) && !posLess(sel.to, start) && type == \"single\") {\n      var dragEnd = operation(cm, function(e2) {\n        if (webkit) display.scroller.draggable = false;\n        cm.state.draggingText = false;\n        off(document, \"mouseup\", dragEnd);\n        off(display.scroller, \"drop\", dragEnd);\n        if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {\n          e_preventDefault(e2);\n          extendSelection(cm.doc, start);\n          focusInput(cm);\n        }\n      });\n      // Let the drag handler handle this.\n      if (webkit) display.scroller.draggable = true;\n      cm.state.draggingText = dragEnd;\n      // IE's approach to draggable\n      if (display.scroller.dragDrop) display.scroller.dragDrop();\n      on(document, \"mouseup\", dragEnd);\n      on(display.scroller, \"drop\", dragEnd);\n      return;\n    }\n    e_preventDefault(e);\n    if (type == \"single\") extendSelection(cm.doc, clipPos(doc, start));\n\n    var startstart = sel.from, startend = sel.to, lastPos = start;\n\n    function doSelect(cur) {\n      if (posEq(lastPos, cur)) return;\n      lastPos = cur;\n\n      if (type == \"single\") {\n        extendSelection(cm.doc, clipPos(doc, start), cur);\n        return;\n      }\n\n      startstart = clipPos(doc, startstart);\n      startend = clipPos(doc, startend);\n      if (type == \"double\") {\n        var word = findWordAt(getLine(doc, cur.line).text, cur);\n        if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);\n        else extendSelection(cm.doc, startstart, word.to);\n      } else if (type == \"triple\") {\n        if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));\n        else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));\n      }\n    }\n\n    var editorSize = getRect(display.wrapper);\n    // Used to ensure timeout re-tries don't fire when another extend\n    // happened in the meantime (clearTimeout isn't reliable -- at\n    // least on Chrome, the timeouts still happen even when cleared,\n    // if the clear happens after their scheduled firing time).\n    var counter = 0;\n\n    function extend(e) {\n      var curCount = ++counter;\n      var cur = posFromMouse(cm, e, true);\n      if (!cur) return;\n      if (!posEq(cur, last)) {\n        if (!cm.state.focused) onFocus(cm);\n        last = cur;\n        doSelect(cur);\n        var visible = visibleLines(display, doc);\n        if (cur.line >= visible.to || cur.line < visible.from)\n          setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);\n      } else {\n        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;\n        if (outside) setTimeout(operation(cm, function() {\n          if (counter != curCount) return;\n          display.scroller.scrollTop += outside;\n          extend(e);\n        }), 50);\n      }\n    }\n\n    function done(e) {\n      counter = Infinity;\n      e_preventDefault(e);\n      focusInput(cm);\n      off(document, \"mousemove\", move);\n      off(document, \"mouseup\", up);\n    }\n\n    var move = operation(cm, function(e) {\n      if (!ie && !e_button(e)) done(e);\n      else extend(e);\n    });\n    var up = operation(cm, done);\n    on(document, \"mousemove\", move);\n    on(document, \"mouseup\", up);\n  }\n\n  function clickInGutter(cm, e) {\n    var display = cm.display;\n    try { var mX = e.clientX, mY = e.clientY; }\n    catch(e) { return false; }\n\n    if (mX >= Math.floor(getRect(display.gutters).right)) return false;\n    e_preventDefault(e);\n    if (!hasHandler(cm, \"gutterClick\")) return true;\n\n    var lineBox = getRect(display.lineDiv);\n    if (mY > lineBox.bottom) return true;\n    mY -= lineBox.top - display.viewOffset;\n\n    for (var i = 0; i < cm.options.gutters.length; ++i) {\n      var g = display.gutters.childNodes[i];\n      if (g && getRect(g).right >= mX) {\n        var line = lineAtHeight(cm.doc, mY);\n        var gutter = cm.options.gutters[i];\n        signalLater(cm, \"gutterClick\", cm, line, gutter, e);\n        break;\n      }\n    }\n    return true;\n  }\n\n  // Kludge to work around strange IE behavior where it'll sometimes\n  // re-fire a series of drag-related events right after the drop (#1551)\n  var lastDrop = 0;\n\n  function onDrop(e) {\n    var cm = this;\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))\n      return;\n    e_preventDefault(e);\n    if (ie) lastDrop = +new Date;\n    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;\n    if (!pos || isReadOnly(cm)) return;\n    if (files && files.length && window.FileReader && window.File) {\n      var n = files.length, text = Array(n), read = 0;\n      var loadFile = function(file, i) {\n        var reader = new FileReader;\n        reader.onload = function() {\n          text[i] = reader.result;\n          if (++read == n) {\n            pos = clipPos(cm.doc, pos);\n            makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join(\"\\n\")), origin: \"paste\"}, \"around\");\n          }\n        };\n        reader.readAsText(file);\n      };\n      for (var i = 0; i < n; ++i) loadFile(files[i], i);\n    } else {\n      // Don't do a replace if the drop happened inside of the selected text.\n      if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {\n        cm.state.draggingText(e);\n        // Ensure the editor is re-focused\n        setTimeout(bind(focusInput, cm), 20);\n        return;\n      }\n      try {\n        var text = e.dataTransfer.getData(\"Text\");\n        if (text) {\n          var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;\n          setSelection(cm.doc, pos, pos);\n          if (cm.state.draggingText) replaceRange(cm.doc, \"\", curFrom, curTo, \"paste\");\n          cm.replaceSelection(text, null, \"paste\");\n          focusInput(cm);\n          onFocus(cm);\n        }\n      }\n      catch(e){}\n    }\n  }\n\n  function onDragStart(cm, e) {\n    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;\n\n    var txt = cm.getSelection();\n    e.dataTransfer.setData(\"Text\", txt);\n\n    // Use dummy image instead of default browsers image.\n    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.\n    if (e.dataTransfer.setDragImage && !safari) {\n      var img = elt(\"img\", null, null, \"position: fixed; left: 0; top: 0;\");\n      if (opera) {\n        img.width = img.height = 1;\n        cm.display.wrapper.appendChild(img);\n        // Force a relayout, or Opera won't use our image for some obscure reason\n        img._top = img.offsetTop;\n      }\n      e.dataTransfer.setDragImage(img, 0, 0);\n      if (opera) img.parentNode.removeChild(img);\n    }\n  }\n\n  function setScrollTop(cm, val) {\n    if (Math.abs(cm.doc.scrollTop - val) < 2) return;\n    cm.doc.scrollTop = val;\n    if (!gecko) updateDisplay(cm, [], val);\n    if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;\n    if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;\n    if (gecko) updateDisplay(cm, []);\n    startWorker(cm, 100);\n  }\n  function setScrollLeft(cm, val, isScroller) {\n    if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;\n    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);\n    cm.doc.scrollLeft = val;\n    alignHorizontally(cm);\n    if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;\n    if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;\n  }\n\n  // Since the delta values reported on mouse wheel events are\n  // unstandardized between browsers and even browser versions, and\n  // generally horribly unpredictable, this code starts by measuring\n  // the scroll effect that the first few mouse wheel events have,\n  // and, from that, detects the way it can convert deltas to pixel\n  // offsets afterwards.\n  //\n  // The reason we want to know the amount a wheel event will scroll\n  // is that it gives us a chance to update the display before the\n  // actual scrolling happens, reducing flickering.\n\n  var wheelSamples = 0, wheelPixelsPerUnit = null;\n  // Fill in a browser-detected starting value on browsers where we\n  // know one. These don't have to be accurate -- the result of them\n  // being wrong would just be a slight flicker on the first wheel\n  // scroll (if it is large enough).\n  if (ie) wheelPixelsPerUnit = -.53;\n  else if (gecko) wheelPixelsPerUnit = 15;\n  else if (chrome) wheelPixelsPerUnit = -.7;\n  else if (safari) wheelPixelsPerUnit = -1/3;\n\n  function onScrollWheel(cm, e) {\n    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;\n    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;\n    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;\n    else if (dy == null) dy = e.wheelDelta;\n\n    var display = cm.display, scroll = display.scroller;\n    // Quit if there's nothing to scroll here\n    if (!(dx && scroll.scrollWidth > scroll.clientWidth ||\n          dy && scroll.scrollHeight > scroll.clientHeight)) return;\n\n    // Webkit browsers on OS X abort momentum scrolls when the target\n    // of the scroll event is removed from the scrollable element.\n    // This hack (see related code in patchDisplay) makes sure the\n    // element is kept around.\n    if (dy && mac && webkit) {\n      for (var cur = e.target; cur != scroll; cur = cur.parentNode) {\n        if (cur.lineObj) {\n          cm.display.currentWheelTarget = cur;\n          break;\n        }\n      }\n    }\n\n    // On some browsers, horizontal scrolling will cause redraws to\n    // happen before the gutter has been realigned, causing it to\n    // wriggle around in a most unseemly way. When we have an\n    // estimated pixels/delta value, we just handle horizontal\n    // scrolling entirely here. It'll be slightly off from native, but\n    // better than glitching out.\n    if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {\n      if (dy)\n        setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));\n      setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));\n      e_preventDefault(e);\n      display.wheelStartX = null; // Abort measurement, if in progress\n      return;\n    }\n\n    if (dy && wheelPixelsPerUnit != null) {\n      var pixels = dy * wheelPixelsPerUnit;\n      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;\n      if (pixels < 0) top = Math.max(0, top + pixels - 50);\n      else bot = Math.min(cm.doc.height, bot + pixels + 50);\n      updateDisplay(cm, [], {top: top, bottom: bot});\n    }\n\n    if (wheelSamples < 20) {\n      if (display.wheelStartX == null) {\n        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;\n        display.wheelDX = dx; display.wheelDY = dy;\n        setTimeout(function() {\n          if (display.wheelStartX == null) return;\n          var movedX = scroll.scrollLeft - display.wheelStartX;\n          var movedY = scroll.scrollTop - display.wheelStartY;\n          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||\n            (movedX && display.wheelDX && movedX / display.wheelDX);\n          display.wheelStartX = display.wheelStartY = null;\n          if (!sample) return;\n          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);\n          ++wheelSamples;\n        }, 200);\n      } else {\n        display.wheelDX += dx; display.wheelDY += dy;\n      }\n    }\n  }\n\n  function doHandleBinding(cm, bound, dropShift) {\n    if (typeof bound == \"string\") {\n      bound = commands[bound];\n      if (!bound) return false;\n    }\n    // Ensure previous input has been read, so that the handler sees a\n    // consistent view of the document\n    if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;\n    var doc = cm.doc, prevShift = doc.sel.shift, done = false;\n    try {\n      if (isReadOnly(cm)) cm.state.suppressEdits = true;\n      if (dropShift) doc.sel.shift = false;\n      done = bound(cm) != Pass;\n    } finally {\n      doc.sel.shift = prevShift;\n      cm.state.suppressEdits = false;\n    }\n    return done;\n  }\n\n  function allKeyMaps(cm) {\n    var maps = cm.state.keyMaps.slice(0);\n    if (cm.options.extraKeys) maps.push(cm.options.extraKeys);\n    maps.push(cm.options.keyMap);\n    return maps;\n  }\n\n  var maybeTransition;\n  function handleKeyBinding(cm, e) {\n    // Handle auto keymap transitions\n    var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;\n    clearTimeout(maybeTransition);\n    if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {\n      if (getKeyMap(cm.options.keyMap) == startMap) {\n        cm.options.keyMap = (next.call ? next.call(null, cm) : next);\n        keyMapChanged(cm);\n      }\n    }, 50);\n\n    var name = keyName(e, true), handled = false;\n    if (!name) return false;\n    var keymaps = allKeyMaps(cm);\n\n    if (e.shiftKey) {\n      // First try to resolve full name (including 'Shift-'). Failing\n      // that, see if there is a cursor-motion command (starting with\n      // 'go') bound to the keyname without 'Shift-'.\n      handled = lookupKey(\"Shift-\" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})\n             || lookupKey(name, keymaps, function(b) {\n                  if (typeof b == \"string\" ? /^go[A-Z]/.test(b) : b.motion)\n                    return doHandleBinding(cm, b);\n                });\n    } else {\n      handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });\n    }\n\n    if (handled) {\n      e_preventDefault(e);\n      restartBlink(cm);\n      if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }\n      signalLater(cm, \"keyHandled\", cm, name, e);\n    }\n    return handled;\n  }\n\n  function handleCharBinding(cm, e, ch) {\n    var handled = lookupKey(\"'\" + ch + \"'\", allKeyMaps(cm),\n                            function(b) { return doHandleBinding(cm, b, true); });\n    if (handled) {\n      e_preventDefault(e);\n      restartBlink(cm);\n      signalLater(cm, \"keyHandled\", cm, \"'\" + ch + \"'\", e);\n    }\n    return handled;\n  }\n\n  var lastStoppedKey = null;\n  function onKeyDown(e) {\n    var cm = this;\n    if (!cm.state.focused) onFocus(cm);\n    if (ie && e.keyCode == 27) { e.returnValue = false; }\n    if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;\n    var code = e.keyCode;\n    // IE does strange things with escape.\n    cm.doc.sel.shift = code == 16 || e.shiftKey;\n    // First give onKeyEvent option a chance to handle this.\n    var handled = handleKeyBinding(cm, e);\n    if (opera) {\n      lastStoppedKey = handled ? code : null;\n      // Opera has no cut event... we try to at least catch the key combo\n      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))\n        cm.replaceSelection(\"\");\n    }\n  }\n\n  function onKeyPress(e) {\n    var cm = this;\n    if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;\n    var keyCode = e.keyCode, charCode = e.charCode;\n    if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}\n    if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;\n    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);\n    if (this.options.electricChars && this.doc.mode.electricChars &&\n        this.options.smartIndent && !isReadOnly(this) &&\n        this.doc.mode.electricChars.indexOf(ch) > -1)\n      setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, \"smart\");}), 75);\n    if (handleCharBinding(cm, e, ch)) return;\n    if (ie && !ie_lt9) cm.display.inputHasSelection = null;\n    fastPoll(cm);\n  }\n\n  function onFocus(cm) {\n    if (cm.options.readOnly == \"nocursor\") return;\n    if (!cm.state.focused) {\n      signal(cm, \"focus\", cm);\n      cm.state.focused = true;\n      if (cm.display.wrapper.className.search(/\\bCodeMirror-focused\\b/) == -1)\n        cm.display.wrapper.className += \" CodeMirror-focused\";\n      resetInput(cm, true);\n    }\n    slowPoll(cm);\n    restartBlink(cm);\n  }\n  function onBlur(cm) {\n    if (cm.state.focused) {\n      signal(cm, \"blur\", cm);\n      cm.state.focused = false;\n      cm.display.wrapper.className = cm.display.wrapper.className.replace(\" CodeMirror-focused\", \"\");\n    }\n    clearInterval(cm.display.blinker);\n    setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);\n  }\n\n  var detectingSelectAll;\n  function onContextMenu(cm, e) {\n    if (signalDOMEvent(cm, e, \"contextmenu\")) return;\n    var display = cm.display, sel = cm.doc.sel;\n    if (eventInWidget(display, e)) return;\n\n    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;\n    if (!pos || opera) return; // Opera is difficult.\n    if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))\n      operation(cm, setSelection)(cm.doc, pos, pos);\n\n    var oldCSS = display.input.style.cssText;\n    display.inputDiv.style.position = \"absolute\";\n    display.input.style.cssText = \"position: fixed; width: 30px; height: 30px; top: \" + (e.clientY - 5) +\n      \"px; left: \" + (e.clientX - 5) + \"px; z-index: 1000; background: white; outline: none;\" +\n      \"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);\";\n    focusInput(cm);\n    resetInput(cm, true);\n    // Adds \"Select all\" to context menu in FF\n    if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = \" \";\n\n    function prepareSelectAllHack() {\n      if (display.input.selectionStart != null) {\n        var extval = display.input.value = \" \" + (posEq(sel.from, sel.to) ? \"\" : display.input.value);\n        display.prevInput = \" \";\n        display.input.selectionStart = 1; display.input.selectionEnd = extval.length;\n      }\n    }\n    function rehide() {\n      display.inputDiv.style.position = \"relative\";\n      display.input.style.cssText = oldCSS;\n      if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;\n      slowPoll(cm);\n\n      // Try to detect the user choosing select-all\n      if (display.input.selectionStart != null) {\n        if (!ie || ie_lt9) prepareSelectAllHack();\n        clearTimeout(detectingSelectAll);\n        var i = 0, poll = function(){\n          if (display.prevInput == \" \" && display.input.selectionStart == 0)\n            operation(cm, commands.selectAll)(cm);\n          else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);\n          else resetInput(cm);\n        };\n        detectingSelectAll = setTimeout(poll, 200);\n      }\n    }\n\n    if (ie && !ie_lt9) prepareSelectAllHack();\n    if (captureMiddleClick) {\n      e_stop(e);\n      var mouseup = function() {\n        off(window, \"mouseup\", mouseup);\n        setTimeout(rehide, 20);\n      };\n      on(window, \"mouseup\", mouseup);\n    } else {\n      setTimeout(rehide, 50);\n    }\n  }\n\n  // UPDATING\n\n  var changeEnd = CodeMirror.changeEnd = function(change) {\n    if (!change.text) return change.to;\n    return Pos(change.from.line + change.text.length - 1,\n               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));\n  };\n\n  // Make sure a position will be valid after the given change.\n  function clipPostChange(doc, change, pos) {\n    if (!posLess(change.from, pos)) return clipPos(doc, pos);\n    var diff = (change.text.length - 1) - (change.to.line - change.from.line);\n    if (pos.line > change.to.line + diff) {\n      var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;\n      if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);\n      return clipToLen(pos, getLine(doc, preLine).text.length);\n    }\n    if (pos.line == change.to.line + diff)\n      return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +\n                       getLine(doc, change.to.line).text.length - change.to.ch);\n    var inside = pos.line - change.from.line;\n    return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));\n  }\n\n  // Hint can be null|\"end\"|\"start\"|\"around\"|{anchor,head}\n  function computeSelAfterChange(doc, change, hint) {\n    if (hint && typeof hint == \"object\") // Assumed to be {anchor, head} object\n      return {anchor: clipPostChange(doc, change, hint.anchor),\n              head: clipPostChange(doc, change, hint.head)};\n\n    if (hint == \"start\") return {anchor: change.from, head: change.from};\n\n    var end = changeEnd(change);\n    if (hint == \"around\") return {anchor: change.from, head: end};\n    if (hint == \"end\") return {anchor: end, head: end};\n\n    // hint is null, leave the selection alone as much as possible\n    var adjustPos = function(pos) {\n      if (posLess(pos, change.from)) return pos;\n      if (!posLess(change.to, pos)) return end;\n\n      var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;\n      if (pos.line == change.to.line) ch += end.ch - change.to.ch;\n      return Pos(line, ch);\n    };\n    return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};\n  }\n\n  function filterChange(doc, change, update) {\n    var obj = {\n      canceled: false,\n      from: change.from,\n      to: change.to,\n      text: change.text,\n      origin: change.origin,\n      cancel: function() { this.canceled = true; }\n    };\n    if (update) obj.update = function(from, to, text, origin) {\n      if (from) this.from = clipPos(doc, from);\n      if (to) this.to = clipPos(doc, to);\n      if (text) this.text = text;\n      if (origin !== undefined) this.origin = origin;\n    };\n    signal(doc, \"beforeChange\", doc, obj);\n    if (doc.cm) signal(doc.cm, \"beforeChange\", doc.cm, obj);\n\n    if (obj.canceled) return null;\n    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};\n  }\n\n  // Replace the range from from to to by the strings in replacement.\n  // change is a {from, to, text [, origin]} object\n  function makeChange(doc, change, selUpdate, ignoreReadOnly) {\n    if (doc.cm) {\n      if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);\n      if (doc.cm.state.suppressEdits) return;\n    }\n\n    if (hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\")) {\n      change = filterChange(doc, change, true);\n      if (!change) return;\n    }\n\n    // Possibly split or suppress the update based on the presence\n    // of read-only spans in its range.\n    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);\n    if (split) {\n      for (var i = split.length - 1; i >= 1; --i)\n        makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [\"\"]});\n      if (split.length)\n        makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);\n    } else {\n      makeChangeNoReadonly(doc, change, selUpdate);\n    }\n  }\n\n  function makeChangeNoReadonly(doc, change, selUpdate) {\n    var selAfter = computeSelAfterChange(doc, change, selUpdate);\n    addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);\n\n    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));\n    var rebased = [];\n\n    linkedDocs(doc, function(doc, sharedHist) {\n      if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n        rebaseHist(doc.history, change);\n        rebased.push(doc.history);\n      }\n      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));\n    });\n  }\n\n  function makeChangeFromHistory(doc, type) {\n    if (doc.cm && doc.cm.state.suppressEdits) return;\n\n    var hist = doc.history;\n    var event = (type == \"undo\" ? hist.done : hist.undone).pop();\n    if (!event) return;\n\n    var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,\n                anchorAfter: event.anchorBefore, headAfter: event.headBefore,\n                generation: hist.generation};\n    (type == \"undo\" ? hist.undone : hist.done).push(anti);\n    hist.generation = event.generation || ++hist.maxGeneration;\n\n    var filter = hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\");\n\n    for (var i = event.changes.length - 1; i >= 0; --i) {\n      var change = event.changes[i];\n      change.origin = type;\n      if (filter && !filterChange(doc, change, false)) {\n        (type == \"undo\" ? hist.done : hist.undone).length = 0;\n        return;\n      }\n\n      anti.changes.push(historyChangeFromChange(doc, change));\n\n      var after = i ? computeSelAfterChange(doc, change, null)\n                    : {anchor: event.anchorBefore, head: event.headBefore};\n      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));\n      var rebased = [];\n\n      linkedDocs(doc, function(doc, sharedHist) {\n        if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n          rebaseHist(doc.history, change);\n          rebased.push(doc.history);\n        }\n        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));\n      });\n    }\n  }\n\n  function shiftDoc(doc, distance) {\n    function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}\n    doc.first += distance;\n    if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);\n    doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);\n    doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);\n  }\n\n  function makeChangeSingleDoc(doc, change, selAfter, spans) {\n    if (doc.cm && !doc.cm.curOp)\n      return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);\n\n    if (change.to.line < doc.first) {\n      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));\n      return;\n    }\n    if (change.from.line > doc.lastLine()) return;\n\n    // Clip the change to the size of this doc\n    if (change.from.line < doc.first) {\n      var shift = change.text.length - 1 - (doc.first - change.from.line);\n      shiftDoc(doc, shift);\n      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),\n                text: [lst(change.text)], origin: change.origin};\n    }\n    var last = doc.lastLine();\n    if (change.to.line > last) {\n      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),\n                text: [change.text[0]], origin: change.origin};\n    }\n\n    change.removed = getBetween(doc, change.from, change.to);\n\n    if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);\n    if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);\n    else updateDoc(doc, change, spans, selAfter);\n  }\n\n  function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {\n    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;\n\n    var recomputeMaxLength = false, checkWidthStart = from.line;\n    if (!cm.options.lineWrapping) {\n      checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));\n      doc.iter(checkWidthStart, to.line + 1, function(line) {\n        if (line == display.maxLine) {\n          recomputeMaxLength = true;\n          return true;\n        }\n      });\n    }\n\n    if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))\n      cm.curOp.cursorActivity = true;\n\n    updateDoc(doc, change, spans, selAfter, estimateHeight(cm));\n\n    if (!cm.options.lineWrapping) {\n      doc.iter(checkWidthStart, from.line + change.text.length, function(line) {\n        var len = lineLength(doc, line);\n        if (len > display.maxLineLength) {\n          display.maxLine = line;\n          display.maxLineLength = len;\n          display.maxLineChanged = true;\n          recomputeMaxLength = false;\n        }\n      });\n      if (recomputeMaxLength) cm.curOp.updateMaxLine = true;\n    }\n\n    // Adjust frontier, schedule worker\n    doc.frontier = Math.min(doc.frontier, from.line);\n    startWorker(cm, 400);\n\n    var lendiff = change.text.length - (to.line - from.line) - 1;\n    // Remember that these lines changed, for updating the display\n    regChange(cm, from.line, to.line + 1, lendiff);\n\n    if (hasHandler(cm, \"change\")) {\n      var changeObj = {from: from, to: to,\n                       text: change.text,\n                       removed: change.removed,\n                       origin: change.origin};\n      if (cm.curOp.textChanged) {\n        for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}\n        cur.next = changeObj;\n      } else cm.curOp.textChanged = changeObj;\n    }\n  }\n\n  function replaceRange(doc, code, from, to, origin) {\n    if (!to) to = from;\n    if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }\n    if (typeof code == \"string\") code = splitLines(code);\n    makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);\n  }\n\n  // POSITION OBJECT\n\n  function Pos(line, ch) {\n    if (!(this instanceof Pos)) return new Pos(line, ch);\n    this.line = line; this.ch = ch;\n  }\n  CodeMirror.Pos = Pos;\n\n  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}\n  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}\n  function copyPos(x) {return Pos(x.line, x.ch);}\n\n  // SELECTION\n\n  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}\n  function clipPos(doc, pos) {\n    if (pos.line < doc.first) return Pos(doc.first, 0);\n    var last = doc.first + doc.size - 1;\n    if (pos.line > last) return Pos(last, getLine(doc, last).text.length);\n    return clipToLen(pos, getLine(doc, pos.line).text.length);\n  }\n  function clipToLen(pos, linelen) {\n    var ch = pos.ch;\n    if (ch == null || ch > linelen) return Pos(pos.line, linelen);\n    else if (ch < 0) return Pos(pos.line, 0);\n    else return pos;\n  }\n  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}\n\n  // If shift is held, this will move the selection anchor. Otherwise,\n  // it'll set the whole selection.\n  function extendSelection(doc, pos, other, bias) {\n    if (doc.sel.shift || doc.sel.extend) {\n      var anchor = doc.sel.anchor;\n      if (other) {\n        var posBefore = posLess(pos, anchor);\n        if (posBefore != posLess(other, anchor)) {\n          anchor = pos;\n          pos = other;\n        } else if (posBefore != posLess(pos, other)) {\n          pos = other;\n        }\n      }\n      setSelection(doc, anchor, pos, bias);\n    } else {\n      setSelection(doc, pos, other || pos, bias);\n    }\n    if (doc.cm) doc.cm.curOp.userSelChange = true;\n  }\n\n  function filterSelectionChange(doc, anchor, head) {\n    var obj = {anchor: anchor, head: head};\n    signal(doc, \"beforeSelectionChange\", doc, obj);\n    if (doc.cm) signal(doc.cm, \"beforeSelectionChange\", doc.cm, obj);\n    obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);\n    return obj;\n  }\n\n  // Update the selection. Last two args are only used by\n  // updateDoc, since they have to be expressed in the line\n  // numbers before the update.\n  function setSelection(doc, anchor, head, bias, checkAtomic) {\n    if (!checkAtomic && hasHandler(doc, \"beforeSelectionChange\") || doc.cm && hasHandler(doc.cm, \"beforeSelectionChange\")) {\n      var filtered = filterSelectionChange(doc, anchor, head);\n      head = filtered.head;\n      anchor = filtered.anchor;\n    }\n\n    var sel = doc.sel;\n    sel.goalColumn = null;\n    // Skip over atomic spans.\n    if (checkAtomic || !posEq(anchor, sel.anchor))\n      anchor = skipAtomic(doc, anchor, bias, checkAtomic != \"push\");\n    if (checkAtomic || !posEq(head, sel.head))\n      head = skipAtomic(doc, head, bias, checkAtomic != \"push\");\n\n    if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;\n\n    sel.anchor = anchor; sel.head = head;\n    var inv = posLess(head, anchor);\n    sel.from = inv ? head : anchor;\n    sel.to = inv ? anchor : head;\n\n    if (doc.cm)\n      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =\n        doc.cm.curOp.cursorActivity = true;\n\n    signalLater(doc, \"cursorActivity\", doc);\n  }\n\n  function reCheckSelection(cm) {\n    setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, \"push\");\n  }\n\n  function skipAtomic(doc, pos, bias, mayClear) {\n    var flipped = false, curPos = pos;\n    var dir = bias || 1;\n    doc.cantEdit = false;\n    search: for (;;) {\n      var line = getLine(doc, curPos.line);\n      if (line.markedSpans) {\n        for (var i = 0; i < line.markedSpans.length; ++i) {\n          var sp = line.markedSpans[i], m = sp.marker;\n          if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&\n              (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {\n            if (mayClear) {\n              signal(m, \"beforeCursorEnter\");\n              if (m.explicitlyCleared) {\n                if (!line.markedSpans) break;\n                else {--i; continue;}\n              }\n            }\n            if (!m.atomic) continue;\n            var newPos = m.find()[dir < 0 ? \"from\" : \"to\"];\n            if (posEq(newPos, curPos)) {\n              newPos.ch += dir;\n              if (newPos.ch < 0) {\n                if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));\n                else newPos = null;\n              } else if (newPos.ch > line.text.length) {\n                if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);\n                else newPos = null;\n              }\n              if (!newPos) {\n                if (flipped) {\n                  // Driven in a corner -- no valid cursor position found at all\n                  // -- try again *with* clearing, if we didn't already\n                  if (!mayClear) return skipAtomic(doc, pos, bias, true);\n                  // Otherwise, turn off editing until further notice, and return the start of the doc\n                  doc.cantEdit = true;\n                  return Pos(doc.first, 0);\n                }\n                flipped = true; newPos = pos; dir = -dir;\n              }\n            }\n            curPos = newPos;\n            continue search;\n          }\n        }\n      }\n      return curPos;\n    }\n  }\n\n  // SCROLLING\n\n  function scrollCursorIntoView(cm) {\n    var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);\n    if (!cm.state.focused) return;\n    var display = cm.display, box = getRect(display.sizer), doScroll = null;\n    if (coords.top + box.top < 0) doScroll = true;\n    else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;\n    if (doScroll != null && !phantom) {\n      var hidden = display.cursor.style.display == \"none\";\n      if (hidden) {\n        display.cursor.style.display = \"\";\n        display.cursor.style.left = coords.left + \"px\";\n        display.cursor.style.top = (coords.top - display.viewOffset) + \"px\";\n      }\n      display.cursor.scrollIntoView(doScroll);\n      if (hidden) display.cursor.style.display = \"none\";\n    }\n  }\n\n  function scrollPosIntoView(cm, pos, margin) {\n    if (margin == null) margin = 0;\n    for (;;) {\n      var changed = false, coords = cursorCoords(cm, pos);\n      var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);\n      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;\n      if (scrollPos.scrollTop != null) {\n        setScrollTop(cm, scrollPos.scrollTop);\n        if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;\n      }\n      if (scrollPos.scrollLeft != null) {\n        setScrollLeft(cm, scrollPos.scrollLeft);\n        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;\n      }\n      if (!changed) return coords;\n    }\n  }\n\n  function scrollIntoView(cm, x1, y1, x2, y2) {\n    var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);\n    if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);\n    if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);\n  }\n\n  function calculateScrollPos(cm, x1, y1, x2, y2) {\n    var display = cm.display, snapMargin = textHeight(cm.display);\n    if (y1 < 0) y1 = 0;\n    var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};\n    var docBottom = cm.doc.height + paddingVert(display);\n    var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;\n    if (y1 < screentop) {\n      result.scrollTop = atTop ? 0 : y1;\n    } else if (y2 > screentop + screen) {\n      var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);\n      if (newTop != screentop) result.scrollTop = newTop;\n    }\n\n    var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;\n    x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;\n    var gutterw = display.gutters.offsetWidth;\n    var atLeft = x1 < gutterw + 10;\n    if (x1 < screenleft + gutterw || atLeft) {\n      if (atLeft) x1 = 0;\n      result.scrollLeft = Math.max(0, x1 - 10 - gutterw);\n    } else if (x2 > screenw + screenleft - 3) {\n      result.scrollLeft = x2 + 10 - screenw;\n    }\n    return result;\n  }\n\n  function updateScrollPos(cm, left, top) {\n    cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,\n                                scrollTop: top == null ? cm.doc.scrollTop : top};\n  }\n\n  function addToScrollPos(cm, left, top) {\n    var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});\n    var scroll = cm.display.scroller;\n    pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));\n    pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));\n  }\n\n  // API UTILITIES\n\n  function indentLine(cm, n, how, aggressive) {\n    var doc = cm.doc;\n    if (how == null) how = \"add\";\n    if (how == \"smart\") {\n      if (!cm.doc.mode.indent) how = \"prev\";\n      else var state = getStateBefore(cm, n);\n    }\n\n    var tabSize = cm.options.tabSize;\n    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);\n    var curSpaceString = line.text.match(/^\\s*/)[0], indentation;\n    if (how == \"smart\") {\n      indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);\n      if (indentation == Pass) {\n        if (!aggressive) return;\n        how = \"prev\";\n      }\n    }\n    if (how == \"prev\") {\n      if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);\n      else indentation = 0;\n    } else if (how == \"add\") {\n      indentation = curSpace + cm.options.indentUnit;\n    } else if (how == \"subtract\") {\n      indentation = curSpace - cm.options.indentUnit;\n    } else if (typeof how == \"number\") {\n      indentation = curSpace + how;\n    }\n    indentation = Math.max(0, indentation);\n\n    var indentString = \"\", pos = 0;\n    if (cm.options.indentWithTabs)\n      for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += \"\\t\";}\n    if (pos < indentation) indentString += spaceStr(indentation - pos);\n\n    if (indentString != curSpaceString)\n      replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), \"+input\");\n    line.stateAfter = null;\n  }\n\n  function changeLine(cm, handle, op) {\n    var no = handle, line = handle, doc = cm.doc;\n    if (typeof handle == \"number\") line = getLine(doc, clipLine(doc, handle));\n    else no = lineNo(handle);\n    if (no == null) return null;\n    if (op(line, no)) regChange(cm, no, no + 1);\n    else return null;\n    return line;\n  }\n\n  function findPosH(doc, pos, dir, unit, visually) {\n    var line = pos.line, ch = pos.ch, origDir = dir;\n    var lineObj = getLine(doc, line);\n    var possible = true;\n    function findNextLine() {\n      var l = line + dir;\n      if (l < doc.first || l >= doc.first + doc.size) return (possible = false);\n      line = l;\n      return lineObj = getLine(doc, l);\n    }\n    function moveOnce(boundToLine) {\n      var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);\n      if (next == null) {\n        if (!boundToLine && findNextLine()) {\n          if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);\n          else ch = dir < 0 ? lineObj.text.length : 0;\n        } else return (possible = false);\n      } else ch = next;\n      return true;\n    }\n\n    if (unit == \"char\") moveOnce();\n    else if (unit == \"column\") moveOnce(true);\n    else if (unit == \"word\" || unit == \"group\") {\n      var sawType = null, group = unit == \"group\";\n      for (var first = true;; first = false) {\n        if (dir < 0 && !moveOnce(!first)) break;\n        var cur = lineObj.text.charAt(ch) || \"\\n\";\n        var type = isWordChar(cur) ? \"w\"\n          : !group ? null\n          : /\\s/.test(cur) ? null\n          : \"p\";\n        if (sawType && sawType != type) {\n          if (dir < 0) {dir = 1; moveOnce();}\n          break;\n        }\n        if (type) sawType = type;\n        if (dir > 0 && !moveOnce(!first)) break;\n      }\n    }\n    var result = skipAtomic(doc, Pos(line, ch), origDir, true);\n    if (!possible) result.hitSide = true;\n    return result;\n  }\n\n  function findPosV(cm, pos, dir, unit) {\n    var doc = cm.doc, x = pos.left, y;\n    if (unit == \"page\") {\n      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);\n      y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));\n    } else if (unit == \"line\") {\n      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;\n    }\n    for (;;) {\n      var target = coordsChar(cm, x, y);\n      if (!target.outside) break;\n      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }\n      y += dir * 5;\n    }\n    return target;\n  }\n\n  function findWordAt(line, pos) {\n    var start = pos.ch, end = pos.ch;\n    if (line) {\n      if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;\n      var startChar = line.charAt(start);\n      var check = isWordChar(startChar) ? isWordChar\n        : /\\s/.test(startChar) ? function(ch) {return /\\s/.test(ch);}\n        : function(ch) {return !/\\s/.test(ch) && !isWordChar(ch);};\n      while (start > 0 && check(line.charAt(start - 1))) --start;\n      while (end < line.length && check(line.charAt(end))) ++end;\n    }\n    return {from: Pos(pos.line, start), to: Pos(pos.line, end)};\n  }\n\n  function selectLine(cm, line) {\n    extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));\n  }\n\n  // PROTOTYPE\n\n  // The publicly visible API. Note that operation(null, f) means\n  // 'wrap f in an operation, performed on its `this` parameter'\n\n  CodeMirror.prototype = {\n    constructor: CodeMirror,\n    focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},\n\n    setOption: function(option, value) {\n      var options = this.options, old = options[option];\n      if (options[option] == value && option != \"mode\") return;\n      options[option] = value;\n      if (optionHandlers.hasOwnProperty(option))\n        operation(this, optionHandlers[option])(this, value, old);\n    },\n\n    getOption: function(option) {return this.options[option];},\n    getDoc: function() {return this.doc;},\n\n    addKeyMap: function(map, bottom) {\n      this.state.keyMaps[bottom ? \"push\" : \"unshift\"](map);\n    },\n    removeKeyMap: function(map) {\n      var maps = this.state.keyMaps;\n      for (var i = 0; i < maps.length; ++i)\n        if (maps[i] == map || (typeof maps[i] != \"string\" && maps[i].name == map)) {\n          maps.splice(i, 1);\n          return true;\n        }\n    },\n\n    addOverlay: operation(null, function(spec, options) {\n      var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);\n      if (mode.startState) throw new Error(\"Overlays may not be stateful.\");\n      this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});\n      this.state.modeGen++;\n      regChange(this);\n    }),\n    removeOverlay: operation(null, function(spec) {\n      var overlays = this.state.overlays;\n      for (var i = 0; i < overlays.length; ++i) {\n        var cur = overlays[i].modeSpec;\n        if (cur == spec || typeof spec == \"string\" && cur.name == spec) {\n          overlays.splice(i, 1);\n          this.state.modeGen++;\n          regChange(this);\n          return;\n        }\n      }\n    }),\n\n    indentLine: operation(null, function(n, dir, aggressive) {\n      if (typeof dir != \"string\" && typeof dir != \"number\") {\n        if (dir == null) dir = this.options.smartIndent ? \"smart\" : \"prev\";\n        else dir = dir ? \"add\" : \"subtract\";\n      }\n      if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);\n    }),\n    indentSelection: operation(null, function(how) {\n      var sel = this.doc.sel;\n      if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);\n      var e = sel.to.line - (sel.to.ch ? 0 : 1);\n      for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);\n    }),\n\n    // Fetch the parser token for a given character. Useful for hacks\n    // that want to inspect the mode state (say, for completion).\n    getTokenAt: function(pos, precise) {\n      var doc = this.doc;\n      pos = clipPos(doc, pos);\n      var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;\n      var line = getLine(doc, pos.line);\n      var stream = new StringStream(line.text, this.options.tabSize);\n      while (stream.pos < pos.ch && !stream.eol()) {\n        stream.start = stream.pos;\n        var style = mode.token(stream, state);\n      }\n      return {start: stream.start,\n              end: stream.pos,\n              string: stream.current(),\n              className: style || null, // Deprecated, use 'type' instead\n              type: style || null,\n              state: state};\n    },\n\n    getTokenTypeAt: function(pos) {\n      pos = clipPos(this.doc, pos);\n      var styles = getLineStyles(this, getLine(this.doc, pos.line));\n      var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;\n      if (ch == 0) return styles[2];\n      for (;;) {\n        var mid = (before + after) >> 1;\n        if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;\n        else if (styles[mid * 2 + 1] < ch) before = mid + 1;\n        else return styles[mid * 2 + 2];\n      }\n    },\n\n    getModeAt: function(pos) {\n      var mode = this.doc.mode;\n      if (!mode.innerMode) return mode;\n      return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;\n    },\n\n    getHelper: function(pos, type) {\n      if (!helpers.hasOwnProperty(type)) return;\n      var help = helpers[type], mode = this.getModeAt(pos);\n      return mode[type] && help[mode[type]] ||\n        mode.helperType && help[mode.helperType] ||\n        help[mode.name];\n    },\n\n    getStateAfter: function(line, precise) {\n      var doc = this.doc;\n      line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);\n      return getStateBefore(this, line + 1, precise);\n    },\n\n    cursorCoords: function(start, mode) {\n      var pos, sel = this.doc.sel;\n      if (start == null) pos = sel.head;\n      else if (typeof start == \"object\") pos = clipPos(this.doc, start);\n      else pos = start ? sel.from : sel.to;\n      return cursorCoords(this, pos, mode || \"page\");\n    },\n\n    charCoords: function(pos, mode) {\n      return charCoords(this, clipPos(this.doc, pos), mode || \"page\");\n    },\n\n    coordsChar: function(coords, mode) {\n      coords = fromCoordSystem(this, coords, mode || \"page\");\n      return coordsChar(this, coords.left, coords.top);\n    },\n\n    lineAtHeight: function(height, mode) {\n      height = fromCoordSystem(this, {top: height, left: 0}, mode || \"page\").top;\n      return lineAtHeight(this.doc, height + this.display.viewOffset);\n    },\n    heightAtLine: function(line, mode) {\n      var end = false, last = this.doc.first + this.doc.size - 1;\n      if (line < this.doc.first) line = this.doc.first;\n      else if (line > last) { line = last; end = true; }\n      var lineObj = getLine(this.doc, line);\n      return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || \"page\").top +\n        (end ? lineObj.height : 0);\n    },\n\n    defaultTextHeight: function() { return textHeight(this.display); },\n    defaultCharWidth: function() { return charWidth(this.display); },\n\n    setGutterMarker: operation(null, function(line, gutterID, value) {\n      return changeLine(this, line, function(line) {\n        var markers = line.gutterMarkers || (line.gutterMarkers = {});\n        markers[gutterID] = value;\n        if (!value && isEmpty(markers)) line.gutterMarkers = null;\n        return true;\n      });\n    }),\n\n    clearGutter: operation(null, function(gutterID) {\n      var cm = this, doc = cm.doc, i = doc.first;\n      doc.iter(function(line) {\n        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {\n          line.gutterMarkers[gutterID] = null;\n          regChange(cm, i, i + 1);\n          if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;\n        }\n        ++i;\n      });\n    }),\n\n    addLineClass: operation(null, function(handle, where, cls) {\n      return changeLine(this, handle, function(line) {\n        var prop = where == \"text\" ? \"textClass\" : where == \"background\" ? \"bgClass\" : \"wrapClass\";\n        if (!line[prop]) line[prop] = cls;\n        else if (new RegExp(\"(?:^|\\\\s)\" + cls + \"(?:$|\\\\s)\").test(line[prop])) return false;\n        else line[prop] += \" \" + cls;\n        return true;\n      });\n    }),\n\n    removeLineClass: operation(null, function(handle, where, cls) {\n      return changeLine(this, handle, function(line) {\n        var prop = where == \"text\" ? \"textClass\" : where == \"background\" ? \"bgClass\" : \"wrapClass\";\n        var cur = line[prop];\n        if (!cur) return false;\n        else if (cls == null) line[prop] = null;\n        else {\n          var found = cur.match(new RegExp(\"(?:^|\\\\s+)\" + cls + \"(?:$|\\\\s+)\"));\n          if (!found) return false;\n          var end = found.index + found[0].length;\n          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? \"\" : \" \") + cur.slice(end) || null;\n        }\n        return true;\n      });\n    }),\n\n    addLineWidget: operation(null, function(handle, node, options) {\n      return addLineWidget(this, handle, node, options);\n    }),\n\n    removeLineWidget: function(widget) { widget.clear(); },\n\n    lineInfo: function(line) {\n      if (typeof line == \"number\") {\n        if (!isLine(this.doc, line)) return null;\n        var n = line;\n        line = getLine(this.doc, line);\n        if (!line) return null;\n      } else {\n        var n = lineNo(line);\n        if (n == null) return null;\n      }\n      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,\n              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,\n              widgets: line.widgets};\n    },\n\n    getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},\n\n    addWidget: function(pos, node, scroll, vert, horiz) {\n      var display = this.display;\n      pos = cursorCoords(this, clipPos(this.doc, pos));\n      var top = pos.bottom, left = pos.left;\n      node.style.position = \"absolute\";\n      display.sizer.appendChild(node);\n      if (vert == \"over\") {\n        top = pos.top;\n      } else if (vert == \"above\" || vert == \"near\") {\n        var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),\n        hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);\n        // Default to positioning above (if specified and possible); otherwise default to positioning below\n        if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)\n          top = pos.top - node.offsetHeight;\n        else if (pos.bottom + node.offsetHeight <= vspace)\n          top = pos.bottom;\n        if (left + node.offsetWidth > hspace)\n          left = hspace - node.offsetWidth;\n      }\n      node.style.top = top + \"px\";\n      node.style.left = node.style.right = \"\";\n      if (horiz == \"right\") {\n        left = display.sizer.clientWidth - node.offsetWidth;\n        node.style.right = \"0px\";\n      } else {\n        if (horiz == \"left\") left = 0;\n        else if (horiz == \"middle\") left = (display.sizer.clientWidth - node.offsetWidth) / 2;\n        node.style.left = left + \"px\";\n      }\n      if (scroll)\n        scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);\n    },\n\n    triggerOnKeyDown: operation(null, onKeyDown),\n\n    execCommand: function(cmd) {return commands[cmd](this);},\n\n    findPosH: function(from, amount, unit, visually) {\n      var dir = 1;\n      if (amount < 0) { dir = -1; amount = -amount; }\n      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {\n        cur = findPosH(this.doc, cur, dir, unit, visually);\n        if (cur.hitSide) break;\n      }\n      return cur;\n    },\n\n    moveH: operation(null, function(dir, unit) {\n      var sel = this.doc.sel, pos;\n      if (sel.shift || sel.extend || posEq(sel.from, sel.to))\n        pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);\n      else\n        pos = dir < 0 ? sel.from : sel.to;\n      extendSelection(this.doc, pos, pos, dir);\n    }),\n\n    deleteH: operation(null, function(dir, unit) {\n      var sel = this.doc.sel;\n      if (!posEq(sel.from, sel.to)) replaceRange(this.doc, \"\", sel.from, sel.to, \"+delete\");\n      else replaceRange(this.doc, \"\", sel.from, findPosH(this.doc, sel.head, dir, unit, false), \"+delete\");\n      this.curOp.userSelChange = true;\n    }),\n\n    findPosV: function(from, amount, unit, goalColumn) {\n      var dir = 1, x = goalColumn;\n      if (amount < 0) { dir = -1; amount = -amount; }\n      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {\n        var coords = cursorCoords(this, cur, \"div\");\n        if (x == null) x = coords.left;\n        else coords.left = x;\n        cur = findPosV(this, coords, dir, unit);\n        if (cur.hitSide) break;\n      }\n      return cur;\n    },\n\n    moveV: operation(null, function(dir, unit) {\n      var sel = this.doc.sel;\n      var pos = cursorCoords(this, sel.head, \"div\");\n      if (sel.goalColumn != null) pos.left = sel.goalColumn;\n      var target = findPosV(this, pos, dir, unit);\n\n      if (unit == \"page\") addToScrollPos(this, 0, charCoords(this, target, \"div\").top - pos.top);\n      extendSelection(this.doc, target, target, dir);\n      sel.goalColumn = pos.left;\n    }),\n\n    toggleOverwrite: function(value) {\n      if (value != null && value == this.state.overwrite) return;\n      if (this.state.overwrite = !this.state.overwrite)\n        this.display.cursor.className += \" CodeMirror-overwrite\";\n      else\n        this.display.cursor.className = this.display.cursor.className.replace(\" CodeMirror-overwrite\", \"\");\n    },\n    hasFocus: function() { return this.state.focused; },\n\n    scrollTo: operation(null, function(x, y) {\n      updateScrollPos(this, x, y);\n    }),\n    getScrollInfo: function() {\n      var scroller = this.display.scroller, co = scrollerCutOff;\n      return {left: scroller.scrollLeft, top: scroller.scrollTop,\n              height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,\n              clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};\n    },\n\n    scrollIntoView: operation(null, function(pos, margin) {\n      if (typeof pos == \"number\") pos = Pos(pos, 0);\n      if (!margin) margin = 0;\n      var coords = pos;\n\n      if (!pos || pos.line != null) {\n        this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;\n        this.curOp.scrollToPosMargin = margin;\n        coords = cursorCoords(this, this.curOp.scrollToPos);\n      }\n      var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);\n      updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);\n    }),\n\n    setSize: operation(null, function(width, height) {\n      function interpret(val) {\n        return typeof val == \"number\" || /^\\d+$/.test(String(val)) ? val + \"px\" : val;\n      }\n      if (width != null) this.display.wrapper.style.width = interpret(width);\n      if (height != null) this.display.wrapper.style.height = interpret(height);\n      if (this.options.lineWrapping)\n        this.display.measureLineCache.length = this.display.measureLineCachePos = 0;\n      this.curOp.forceUpdate = true;\n    }),\n\n    operation: function(f){return runInOp(this, f);},\n\n    refresh: operation(null, function() {\n      clearCaches(this);\n      updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);\n      regChange(this);\n    }),\n\n    swapDoc: operation(null, function(doc) {\n      var old = this.doc;\n      old.cm = null;\n      attachDoc(this, doc);\n      clearCaches(this);\n      resetInput(this, true);\n      updateScrollPos(this, doc.scrollLeft, doc.scrollTop);\n      return old;\n    }),\n\n    getInputField: function(){return this.display.input;},\n    getWrapperElement: function(){return this.display.wrapper;},\n    getScrollerElement: function(){return this.display.scroller;},\n    getGutterElement: function(){return this.display.gutters;}\n  };\n  eventMixin(CodeMirror);\n\n  // OPTION DEFAULTS\n\n  var optionHandlers = CodeMirror.optionHandlers = {};\n\n  // The default configuration options.\n  var defaults = CodeMirror.defaults = {};\n\n  function option(name, deflt, handle, notOnInit) {\n    CodeMirror.defaults[name] = deflt;\n    if (handle) optionHandlers[name] =\n      notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;\n  }\n\n  var Init = CodeMirror.Init = {toString: function(){return \"CodeMirror.Init\";}};\n\n  // These two are, on init, called from the constructor because they\n  // have to be initialized before the editor can start at all.\n  option(\"value\", \"\", function(cm, val) {\n    cm.setValue(val);\n  }, true);\n  option(\"mode\", null, function(cm, val) {\n    cm.doc.modeOption = val;\n    loadMode(cm);\n  }, true);\n\n  option(\"indentUnit\", 2, loadMode, true);\n  option(\"indentWithTabs\", false);\n  option(\"smartIndent\", true);\n  option(\"tabSize\", 4, function(cm) {\n    loadMode(cm);\n    clearCaches(cm);\n    regChange(cm);\n  }, true);\n  option(\"electricChars\", true);\n  option(\"rtlMoveVisually\", !windows);\n\n  option(\"theme\", \"default\", function(cm) {\n    themeChanged(cm);\n    guttersChanged(cm);\n  }, true);\n  option(\"keyMap\", \"default\", keyMapChanged);\n  option(\"extraKeys\", null);\n\n  option(\"onKeyEvent\", null);\n  option(\"onDragEvent\", null);\n\n  option(\"lineWrapping\", false, wrappingChanged, true);\n  option(\"gutters\", [], function(cm) {\n    setGuttersForLineNumbers(cm.options);\n    guttersChanged(cm);\n  }, true);\n  option(\"fixedGutter\", true, function(cm, val) {\n    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + \"px\" : \"0\";\n    cm.refresh();\n  }, true);\n  option(\"coverGutterNextToScrollbar\", false, updateScrollbars, true);\n  option(\"lineNumbers\", false, function(cm) {\n    setGuttersForLineNumbers(cm.options);\n    guttersChanged(cm);\n  }, true);\n  option(\"firstLineNumber\", 1, guttersChanged, true);\n  option(\"lineNumberFormatter\", function(integer) {return integer;}, guttersChanged, true);\n  option(\"showCursorWhenSelecting\", false, updateSelection, true);\n\n  option(\"readOnly\", false, function(cm, val) {\n    if (val == \"nocursor\") {onBlur(cm); cm.display.input.blur();}\n    else if (!val) resetInput(cm, true);\n  });\n  option(\"dragDrop\", true);\n\n  option(\"cursorBlinkRate\", 530);\n  option(\"cursorScrollMargin\", 0);\n  option(\"cursorHeight\", 1);\n  option(\"workTime\", 100);\n  option(\"workDelay\", 100);\n  option(\"flattenSpans\", true);\n  option(\"pollInterval\", 100);\n  option(\"undoDepth\", 40, function(cm, val){cm.doc.history.undoDepth = val;});\n  option(\"historyEventDelay\", 500);\n  option(\"viewportMargin\", 10, function(cm){cm.refresh();}, true);\n  option(\"maxHighlightLength\", 10000, function(cm){loadMode(cm); cm.refresh();}, true);\n  option(\"moveInputWithCursor\", true, function(cm, val) {\n    if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;\n  });\n\n  option(\"tabindex\", null, function(cm, val) {\n    cm.display.input.tabIndex = val || \"\";\n  });\n  option(\"autofocus\", null);\n\n  // MODE DEFINITION AND QUERYING\n\n  // Known modes, by name and by MIME\n  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};\n\n  CodeMirror.defineMode = function(name, mode) {\n    if (!CodeMirror.defaults.mode && name != \"null\") CodeMirror.defaults.mode = name;\n    if (arguments.length > 2) {\n      mode.dependencies = [];\n      for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);\n    }\n    modes[name] = mode;\n  };\n\n  CodeMirror.defineMIME = function(mime, spec) {\n    mimeModes[mime] = spec;\n  };\n\n  CodeMirror.resolveMode = function(spec) {\n    if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec)) {\n      spec = mimeModes[spec];\n    } else if (spec && typeof spec.name == \"string\" && mimeModes.hasOwnProperty(spec.name)) {\n      var found = mimeModes[spec.name];\n      spec = createObj(found, spec);\n      spec.name = found.name;\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+xml$/.test(spec)) {\n      return CodeMirror.resolveMode(\"application/xml\");\n    }\n    if (typeof spec == \"string\") return {name: spec};\n    else return spec || {name: \"null\"};\n  };\n\n  CodeMirror.getMode = function(options, spec) {\n    var spec = CodeMirror.resolveMode(spec);\n    var mfactory = modes[spec.name];\n    if (!mfactory) return CodeMirror.getMode(options, \"text/plain\");\n    var modeObj = mfactory(options, spec);\n    if (modeExtensions.hasOwnProperty(spec.name)) {\n      var exts = modeExtensions[spec.name];\n      for (var prop in exts) {\n        if (!exts.hasOwnProperty(prop)) continue;\n        if (modeObj.hasOwnProperty(prop)) modeObj[\"_\" + prop] = modeObj[prop];\n        modeObj[prop] = exts[prop];\n      }\n    }\n    modeObj.name = spec.name;\n\n    return modeObj;\n  };\n\n  CodeMirror.defineMode(\"null\", function() {\n    return {token: function(stream) {stream.skipToEnd();}};\n  });\n  CodeMirror.defineMIME(\"text/plain\", \"null\");\n\n  var modeExtensions = CodeMirror.modeExtensions = {};\n  CodeMirror.extendMode = function(mode, properties) {\n    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});\n    copyObj(properties, exts);\n  };\n\n  // EXTENSIONS\n\n  CodeMirror.defineExtension = function(name, func) {\n    CodeMirror.prototype[name] = func;\n  };\n  CodeMirror.defineDocExtension = function(name, func) {\n    Doc.prototype[name] = func;\n  };\n  CodeMirror.defineOption = option;\n\n  var initHooks = [];\n  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};\n\n  var helpers = CodeMirror.helpers = {};\n  CodeMirror.registerHelper = function(type, name, value) {\n    if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};\n    helpers[type][name] = value;\n  };\n\n  // UTILITIES\n\n  CodeMirror.isWordChar = isWordChar;\n\n  // MODE STATE HANDLING\n\n  // Utility functions for working with state. Exported because modes\n  // sometimes need to do this.\n  function copyState(mode, state) {\n    if (state === true) return state;\n    if (mode.copyState) return mode.copyState(state);\n    var nstate = {};\n    for (var n in state) {\n      var val = state[n];\n      if (val instanceof Array) val = val.concat([]);\n      nstate[n] = val;\n    }\n    return nstate;\n  }\n  CodeMirror.copyState = copyState;\n\n  function startState(mode, a1, a2) {\n    return mode.startState ? mode.startState(a1, a2) : true;\n  }\n  CodeMirror.startState = startState;\n\n  CodeMirror.innerMode = function(mode, state) {\n    while (mode.innerMode) {\n      var info = mode.innerMode(state);\n      if (!info || info.mode == mode) break;\n      state = info.state;\n      mode = info.mode;\n    }\n    return info || {mode: mode, state: state};\n  };\n\n  // STANDARD COMMANDS\n\n  var commands = CodeMirror.commands = {\n    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},\n    killLine: function(cm) {\n      var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);\n      if (!sel && cm.getLine(from.line).length == from.ch)\n        cm.replaceRange(\"\", from, Pos(from.line + 1, 0), \"+delete\");\n      else cm.replaceRange(\"\", from, sel ? to : Pos(from.line), \"+delete\");\n    },\n    deleteLine: function(cm) {\n      var l = cm.getCursor().line;\n      cm.replaceRange(\"\", Pos(l, 0), Pos(l), \"+delete\");\n    },\n    delLineLeft: function(cm) {\n      var cur = cm.getCursor();\n      cm.replaceRange(\"\", Pos(cur.line, 0), cur, \"+delete\");\n    },\n    undo: function(cm) {cm.undo();},\n    redo: function(cm) {cm.redo();},\n    goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},\n    goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},\n    goLineStart: function(cm) {\n      cm.extendSelection(lineStart(cm, cm.getCursor().line));\n    },\n    goLineStartSmart: function(cm) {\n      var cur = cm.getCursor(), start = lineStart(cm, cur.line);\n      var line = cm.getLineHandle(start.line);\n      var order = getOrder(line);\n      if (!order || order[0].level == 0) {\n        var firstNonWS = Math.max(0, line.text.search(/\\S/));\n        var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;\n        cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));\n      } else cm.extendSelection(start);\n    },\n    goLineEnd: function(cm) {\n      cm.extendSelection(lineEnd(cm, cm.getCursor().line));\n    },\n    goLineRight: function(cm) {\n      var top = cm.charCoords(cm.getCursor(), \"div\").top + 5;\n      cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\"));\n    },\n    goLineLeft: function(cm) {\n      var top = cm.charCoords(cm.getCursor(), \"div\").top + 5;\n      cm.extendSelection(cm.coordsChar({left: 0, top: top}, \"div\"));\n    },\n    goLineUp: function(cm) {cm.moveV(-1, \"line\");},\n    goLineDown: function(cm) {cm.moveV(1, \"line\");},\n    goPageUp: function(cm) {cm.moveV(-1, \"page\");},\n    goPageDown: function(cm) {cm.moveV(1, \"page\");},\n    goCharLeft: function(cm) {cm.moveH(-1, \"char\");},\n    goCharRight: function(cm) {cm.moveH(1, \"char\");},\n    goColumnLeft: function(cm) {cm.moveH(-1, \"column\");},\n    goColumnRight: function(cm) {cm.moveH(1, \"column\");},\n    goWordLeft: function(cm) {cm.moveH(-1, \"word\");},\n    goGroupRight: function(cm) {cm.moveH(1, \"group\");},\n    goGroupLeft: function(cm) {cm.moveH(-1, \"group\");},\n    goWordRight: function(cm) {cm.moveH(1, \"word\");},\n    delCharBefore: function(cm) {cm.deleteH(-1, \"char\");},\n    delCharAfter: function(cm) {cm.deleteH(1, \"char\");},\n    delWordBefore: function(cm) {cm.deleteH(-1, \"word\");},\n    delWordAfter: function(cm) {cm.deleteH(1, \"word\");},\n    delGroupBefore: function(cm) {cm.deleteH(-1, \"group\");},\n    delGroupAfter: function(cm) {cm.deleteH(1, \"group\");},\n    indentAuto: function(cm) {cm.indentSelection(\"smart\");},\n    indentMore: function(cm) {cm.indentSelection(\"add\");},\n    indentLess: function(cm) {cm.indentSelection(\"subtract\");},\n    insertTab: function(cm) {cm.replaceSelection(\"\\t\", \"end\", \"+input\");},\n    defaultTab: function(cm) {\n      if (cm.somethingSelected()) cm.indentSelection(\"add\");\n      else cm.replaceSelection(\"\\t\", \"end\", \"+input\");\n    },\n    transposeChars: function(cm) {\n      var cur = cm.getCursor(), line = cm.getLine(cur.line);\n      if (cur.ch > 0 && cur.ch < line.length - 1)\n        cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),\n                        Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));\n    },\n    newlineAndIndent: function(cm) {\n      operation(cm, function() {\n        cm.replaceSelection(\"\\n\", \"end\", \"+input\");\n        cm.indentLine(cm.getCursor().line, null, true);\n      })();\n    },\n    toggleOverwrite: function(cm) {cm.toggleOverwrite();}\n  };\n\n  // STANDARD KEYMAPS\n\n  var keyMap = CodeMirror.keyMap = {};\n  keyMap.basic = {\n    \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\n    \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\n    \"Delete\": \"delCharAfter\", \"Backspace\": \"delCharBefore\", \"Tab\": \"defaultTab\", \"Shift-Tab\": \"indentAuto\",\n    \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\"\n  };\n  // Note that the save and find-related commands aren't defined by\n  // default. Unknown commands are simply ignored.\n  keyMap.pcDefault = {\n    \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\n    \"Ctrl-Home\": \"goDocStart\", \"Alt-Up\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Down\": \"goDocEnd\",\n    \"Ctrl-Left\": \"goGroupLeft\", \"Ctrl-Right\": \"goGroupRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\n    \"Ctrl-Backspace\": \"delGroupBefore\", \"Ctrl-Delete\": \"delGroupAfter\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\n    \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\n    \"Ctrl-[\": \"indentLess\", \"Ctrl-]\": \"indentMore\",\n    fallthrough: \"basic\"\n  };\n  keyMap.macDefault = {\n    \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\n    \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goGroupLeft\",\n    \"Alt-Right\": \"goGroupRight\", \"Cmd-Left\": \"goLineStart\", \"Cmd-Right\": \"goLineEnd\", \"Alt-Backspace\": \"delGroupBefore\",\n    \"Ctrl-Alt-Backspace\": \"delGroupAfter\", \"Alt-Delete\": \"delGroupAfter\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\n    \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\n    \"Cmd-[\": \"indentLess\", \"Cmd-]\": \"indentMore\", \"Cmd-Backspace\": \"delLineLeft\",\n    fallthrough: [\"basic\", \"emacsy\"]\n  };\n  keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\n  keyMap.emacsy = {\n    \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\n    \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\n    \"Ctrl-V\": \"goPageDown\", \"Shift-Ctrl-V\": \"goPageUp\", \"Ctrl-D\": \"delCharAfter\", \"Ctrl-H\": \"delCharBefore\",\n    \"Alt-D\": \"delWordAfter\", \"Alt-Backspace\": \"delWordBefore\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\"\n  };\n\n  // KEYMAP DISPATCH\n\n  function getKeyMap(val) {\n    if (typeof val == \"string\") return keyMap[val];\n    else return val;\n  }\n\n  function lookupKey(name, maps, handle) {\n    function lookup(map) {\n      map = getKeyMap(map);\n      var found = map[name];\n      if (found === false) return \"stop\";\n      if (found != null && handle(found)) return true;\n      if (map.nofallthrough) return \"stop\";\n\n      var fallthrough = map.fallthrough;\n      if (fallthrough == null) return false;\n      if (Object.prototype.toString.call(fallthrough) != \"[object Array]\")\n        return lookup(fallthrough);\n      for (var i = 0, e = fallthrough.length; i < e; ++i) {\n        var done = lookup(fallthrough[i]);\n        if (done) return done;\n      }\n      return false;\n    }\n\n    for (var i = 0; i < maps.length; ++i) {\n      var done = lookup(maps[i]);\n      if (done) return done != \"stop\";\n    }\n  }\n  function isModifierKey(event) {\n    var name = keyNames[event.keyCode];\n    return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\";\n  }\n  function keyName(event, noShift) {\n    if (opera && event.keyCode == 34 && event[\"char\"]) return false;\n    var name = keyNames[event.keyCode];\n    if (name == null || event.altGraphKey) return false;\n    if (event.altKey) name = \"Alt-\" + name;\n    if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = \"Ctrl-\" + name;\n    if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = \"Cmd-\" + name;\n    if (!noShift && event.shiftKey) name = \"Shift-\" + name;\n    return name;\n  }\n  CodeMirror.lookupKey = lookupKey;\n  CodeMirror.isModifierKey = isModifierKey;\n  CodeMirror.keyName = keyName;\n\n  // FROMTEXTAREA\n\n  CodeMirror.fromTextArea = function(textarea, options) {\n    if (!options) options = {};\n    options.value = textarea.value;\n    if (!options.tabindex && textarea.tabindex)\n      options.tabindex = textarea.tabindex;\n    if (!options.placeholder && textarea.placeholder)\n      options.placeholder = textarea.placeholder;\n    // Set autofocus to true if this textarea is focused, or if it has\n    // autofocus and no other element is focused.\n    if (options.autofocus == null) {\n      var hasFocus = document.body;\n      // doc.activeElement occasionally throws on IE\n      try { hasFocus = document.activeElement; } catch(e) {}\n      options.autofocus = hasFocus == textarea ||\n        textarea.getAttribute(\"autofocus\") != null && hasFocus == document.body;\n    }\n\n    function save() {textarea.value = cm.getValue();}\n    if (textarea.form) {\n      on(textarea.form, \"submit\", save);\n      // Deplorable hack to make the submit method do the right thing.\n      if (!options.leaveSubmitMethodAlone) {\n        var form = textarea.form, realSubmit = form.submit;\n        try {\n          var wrappedSubmit = form.submit = function() {\n            save();\n            form.submit = realSubmit;\n            form.submit();\n            form.submit = wrappedSubmit;\n          };\n        } catch(e) {}\n      }\n    }\n\n    textarea.style.display = \"none\";\n    var cm = CodeMirror(function(node) {\n      textarea.parentNode.insertBefore(node, textarea.nextSibling);\n    }, options);\n    cm.save = save;\n    cm.getTextArea = function() { return textarea; };\n    cm.toTextArea = function() {\n      save();\n      textarea.parentNode.removeChild(cm.getWrapperElement());\n      textarea.style.display = \"\";\n      if (textarea.form) {\n        off(textarea.form, \"submit\", save);\n        if (typeof textarea.form.submit == \"function\")\n          textarea.form.submit = realSubmit;\n      }\n    };\n    return cm;\n  };\n\n  // STRING STREAM\n\n  // Fed to the mode parsers, provides helper functions to make\n  // parsers more succinct.\n\n  // The character stream used by a mode's parser.\n  function StringStream(string, tabSize) {\n    this.pos = this.start = 0;\n    this.string = string;\n    this.tabSize = tabSize || 8;\n    this.lastColumnPos = this.lastColumnValue = 0;\n  }\n\n  StringStream.prototype = {\n    eol: function() {return this.pos >= this.string.length;},\n    sol: function() {return this.pos == 0;},\n    peek: function() {return this.string.charAt(this.pos) || undefined;},\n    next: function() {\n      if (this.pos < this.string.length)\n        return this.string.charAt(this.pos++);\n    },\n    eat: function(match) {\n      var ch = this.string.charAt(this.pos);\n      if (typeof match == \"string\") var ok = ch == match;\n      else var ok = ch && (match.test ? match.test(ch) : match(ch));\n      if (ok) {++this.pos; return ch;}\n    },\n    eatWhile: function(match) {\n      var start = this.pos;\n      while (this.eat(match)){}\n      return this.pos > start;\n    },\n    eatSpace: function() {\n      var start = this.pos;\n      while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;\n      return this.pos > start;\n    },\n    skipToEnd: function() {this.pos = this.string.length;},\n    skipTo: function(ch) {\n      var found = this.string.indexOf(ch, this.pos);\n      if (found > -1) {this.pos = found; return true;}\n    },\n    backUp: function(n) {this.pos -= n;},\n    column: function() {\n      if (this.lastColumnPos < this.start) {\n        this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);\n        this.lastColumnPos = this.start;\n      }\n      return this.lastColumnValue;\n    },\n    indentation: function() {return countColumn(this.string, null, this.tabSize);},\n    match: function(pattern, consume, caseInsensitive) {\n      if (typeof pattern == \"string\") {\n        var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};\n        var substr = this.string.substr(this.pos, pattern.length);\n        if (cased(substr) == cased(pattern)) {\n          if (consume !== false) this.pos += pattern.length;\n          return true;\n        }\n      } else {\n        var match = this.string.slice(this.pos).match(pattern);\n        if (match && match.index > 0) return null;\n        if (match && consume !== false) this.pos += match[0].length;\n        return match;\n      }\n    },\n    current: function(){return this.string.slice(this.start, this.pos);}\n  };\n  CodeMirror.StringStream = StringStream;\n\n  // TEXTMARKERS\n\n  function TextMarker(doc, type) {\n    this.lines = [];\n    this.type = type;\n    this.doc = doc;\n  }\n  CodeMirror.TextMarker = TextMarker;\n  eventMixin(TextMarker);\n\n  TextMarker.prototype.clear = function() {\n    if (this.explicitlyCleared) return;\n    var cm = this.doc.cm, withOp = cm && !cm.curOp;\n    if (withOp) startOperation(cm);\n    if (hasHandler(this, \"clear\")) {\n      var found = this.find();\n      if (found) signalLater(this, \"clear\", found.from, found.to);\n    }\n    var min = null, max = null;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (span.to != null) max = lineNo(line);\n      line.markedSpans = removeMarkedSpan(line.markedSpans, span);\n      if (span.from != null)\n        min = lineNo(line);\n      else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)\n        updateLineHeight(line, textHeight(cm.display));\n    }\n    if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {\n      var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);\n      if (len > cm.display.maxLineLength) {\n        cm.display.maxLine = visual;\n        cm.display.maxLineLength = len;\n        cm.display.maxLineChanged = true;\n      }\n    }\n\n    if (min != null && cm) regChange(cm, min, max + 1);\n    this.lines.length = 0;\n    this.explicitlyCleared = true;\n    if (this.atomic && this.doc.cantEdit) {\n      this.doc.cantEdit = false;\n      if (cm) reCheckSelection(cm);\n    }\n    if (withOp) endOperation(cm);\n  };\n\n  TextMarker.prototype.find = function() {\n    var from, to;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (span.from != null || span.to != null) {\n        var found = lineNo(line);\n        if (span.from != null) from = Pos(found, span.from);\n        if (span.to != null) to = Pos(found, span.to);\n      }\n    }\n    if (this.type == \"bookmark\") return from;\n    return from && {from: from, to: to};\n  };\n\n  TextMarker.prototype.changed = function() {\n    var pos = this.find(), cm = this.doc.cm;\n    if (!pos || !cm) return;\n    var line = getLine(this.doc, pos.from.line);\n    clearCachedMeasurement(cm, line);\n    if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) {\n      for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {\n        if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);\n        break;\n      }\n      runInOp(cm, function() {\n        cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;\n      });\n    }\n  };\n\n  TextMarker.prototype.attachLine = function(line) {\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp;\n      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)\n        (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);\n    }\n    this.lines.push(line);\n  };\n  TextMarker.prototype.detachLine = function(line) {\n    this.lines.splice(indexOf(this.lines, line), 1);\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp;\n      (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);\n    }\n  };\n\n  function markText(doc, from, to, options, type) {\n    if (options && options.shared) return markTextShared(doc, from, to, options, type);\n    if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);\n\n    var marker = new TextMarker(doc, type);\n    if (type == \"range\" && !posLess(from, to)) return marker;\n    if (options) copyObj(options, marker);\n    if (marker.replacedWith) {\n      marker.collapsed = true;\n      marker.replacedWith = elt(\"span\", [marker.replacedWith], \"CodeMirror-widget\");\n      if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;\n    }\n    if (marker.collapsed) sawCollapsedSpans = true;\n\n    if (marker.addToHistory)\n      addToHistory(doc, {from: from, to: to, origin: \"markText\"},\n                   {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);\n\n    var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;\n    doc.iter(curLine, to.line + 1, function(line) {\n      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)\n        updateMaxLine = true;\n      var span = {from: null, to: null, marker: marker};\n      size += line.text.length;\n      if (curLine == from.line) {span.from = from.ch; size -= from.ch;}\n      if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}\n      if (marker.collapsed) {\n        if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);\n        if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);\n        else updateLineHeight(line, 0);\n      }\n      addMarkedSpan(line, span);\n      ++curLine;\n    });\n    if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {\n      if (lineIsHidden(doc, line)) updateLineHeight(line, 0);\n    });\n\n    if (marker.clearOnEnter) on(marker, \"beforeCursorEnter\", function() { marker.clear(); });\n\n    if (marker.readOnly) {\n      sawReadOnlySpans = true;\n      if (doc.history.done.length || doc.history.undone.length)\n        doc.clearHistory();\n    }\n    if (marker.collapsed) {\n      if (collapsedAtStart != collapsedAtEnd)\n        throw new Error(\"Inserting collapsed marker overlapping an existing one\");\n      marker.size = size;\n      marker.atomic = true;\n    }\n    if (cm) {\n      if (updateMaxLine) cm.curOp.updateMaxLine = true;\n      if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)\n        regChange(cm, from.line, to.line + 1);\n      if (marker.atomic) reCheckSelection(cm);\n    }\n    return marker;\n  }\n\n  // SHARED TEXTMARKERS\n\n  function SharedTextMarker(markers, primary) {\n    this.markers = markers;\n    this.primary = primary;\n    for (var i = 0, me = this; i < markers.length; ++i) {\n      markers[i].parent = this;\n      on(markers[i], \"clear\", function(){me.clear();});\n    }\n  }\n  CodeMirror.SharedTextMarker = SharedTextMarker;\n  eventMixin(SharedTextMarker);\n\n  SharedTextMarker.prototype.clear = function() {\n    if (this.explicitlyCleared) return;\n    this.explicitlyCleared = true;\n    for (var i = 0; i < this.markers.length; ++i)\n      this.markers[i].clear();\n    signalLater(this, \"clear\");\n  };\n  SharedTextMarker.prototype.find = function() {\n    return this.primary.find();\n  };\n\n  function markTextShared(doc, from, to, options, type) {\n    options = copyObj(options);\n    options.shared = false;\n    var markers = [markText(doc, from, to, options, type)], primary = markers[0];\n    var widget = options.replacedWith;\n    linkedDocs(doc, function(doc) {\n      if (widget) options.replacedWith = widget.cloneNode(true);\n      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));\n      for (var i = 0; i < doc.linked.length; ++i)\n        if (doc.linked[i].isParent) return;\n      primary = lst(markers);\n    });\n    return new SharedTextMarker(markers, primary);\n  }\n\n  // TEXTMARKER SPANS\n\n  function getMarkedSpanFor(spans, marker) {\n    if (spans) for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.marker == marker) return span;\n    }\n  }\n  function removeMarkedSpan(spans, span) {\n    for (var r, i = 0; i < spans.length; ++i)\n      if (spans[i] != span) (r || (r = [])).push(spans[i]);\n    return r;\n  }\n  function addMarkedSpan(line, span) {\n    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];\n    span.marker.attachLine(line);\n  }\n\n  function markedSpansBefore(old, startCh, isInsert) {\n    if (old) for (var i = 0, nw; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);\n      if (startsBefore || marker.type == \"bookmark\" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {\n        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);\n        (nw || (nw = [])).push({from: span.from,\n                                to: endsAfter ? null : span.to,\n                                marker: marker});\n      }\n    }\n    return nw;\n  }\n\n  function markedSpansAfter(old, endCh, isInsert) {\n    if (old) for (var i = 0, nw; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);\n      if (endsAfter || marker.type == \"bookmark\" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {\n        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);\n        (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,\n                                to: span.to == null ? null : span.to - endCh,\n                                marker: marker});\n      }\n    }\n    return nw;\n  }\n\n  function stretchSpansOverChange(doc, change) {\n    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;\n    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;\n    if (!oldFirst && !oldLast) return null;\n\n    var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);\n    // Get the spans that 'stick out' on both sides\n    var first = markedSpansBefore(oldFirst, startCh, isInsert);\n    var last = markedSpansAfter(oldLast, endCh, isInsert);\n\n    // Next, merge those two ends\n    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);\n    if (first) {\n      // Fix up .to properties of first\n      for (var i = 0; i < first.length; ++i) {\n        var span = first[i];\n        if (span.to == null) {\n          var found = getMarkedSpanFor(last, span.marker);\n          if (!found) span.to = startCh;\n          else if (sameLine) span.to = found.to == null ? null : found.to + offset;\n        }\n      }\n    }\n    if (last) {\n      // Fix up .from in last (or move them into first in case of sameLine)\n      for (var i = 0; i < last.length; ++i) {\n        var span = last[i];\n        if (span.to != null) span.to += offset;\n        if (span.from == null) {\n          var found = getMarkedSpanFor(first, span.marker);\n          if (!found) {\n            span.from = offset;\n            if (sameLine) (first || (first = [])).push(span);\n          }\n        } else {\n          span.from += offset;\n          if (sameLine) (first || (first = [])).push(span);\n        }\n      }\n    }\n    if (sameLine && first) {\n      // Make sure we didn't create any zero-length spans\n      for (var i = 0; i < first.length; ++i)\n        if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != \"bookmark\")\n          first.splice(i--, 1);\n      if (!first.length) first = null;\n    }\n\n    var newMarkers = [first];\n    if (!sameLine) {\n      // Fill gap with whole-line-spans\n      var gap = change.text.length - 2, gapMarkers;\n      if (gap > 0 && first)\n        for (var i = 0; i < first.length; ++i)\n          if (first[i].to == null)\n            (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});\n      for (var i = 0; i < gap; ++i)\n        newMarkers.push(gapMarkers);\n      newMarkers.push(last);\n    }\n    return newMarkers;\n  }\n\n  function mergeOldSpans(doc, change) {\n    var old = getOldSpans(doc, change);\n    var stretched = stretchSpansOverChange(doc, change);\n    if (!old) return stretched;\n    if (!stretched) return old;\n\n    for (var i = 0; i < old.length; ++i) {\n      var oldCur = old[i], stretchCur = stretched[i];\n      if (oldCur && stretchCur) {\n        spans: for (var j = 0; j < stretchCur.length; ++j) {\n          var span = stretchCur[j];\n          for (var k = 0; k < oldCur.length; ++k)\n            if (oldCur[k].marker == span.marker) continue spans;\n          oldCur.push(span);\n        }\n      } else if (stretchCur) {\n        old[i] = stretchCur;\n      }\n    }\n    return old;\n  }\n\n  function removeReadOnlyRanges(doc, from, to) {\n    var markers = null;\n    doc.iter(from.line, to.line + 1, function(line) {\n      if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {\n        var mark = line.markedSpans[i].marker;\n        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))\n          (markers || (markers = [])).push(mark);\n      }\n    });\n    if (!markers) return null;\n    var parts = [{from: from, to: to}];\n    for (var i = 0; i < markers.length; ++i) {\n      var mk = markers[i], m = mk.find();\n      for (var j = 0; j < parts.length; ++j) {\n        var p = parts[j];\n        if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;\n        var newParts = [j, 1];\n        if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))\n          newParts.push({from: p.from, to: m.from});\n        if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))\n          newParts.push({from: m.to, to: p.to});\n        parts.splice.apply(parts, newParts);\n        j += newParts.length - 1;\n      }\n    }\n    return parts;\n  }\n\n  function collapsedSpanAt(line, ch) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) for (var sp, i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (!sp.marker.collapsed) continue;\n      if ((sp.from == null || sp.from < ch) &&\n          (sp.to == null || sp.to > ch) &&\n          (!found || found.width < sp.marker.width))\n        found = sp.marker;\n    }\n    return found;\n  }\n  function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }\n  function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }\n\n  function visualLine(doc, line) {\n    var merged;\n    while (merged = collapsedSpanAtStart(line))\n      line = getLine(doc, merged.find().from.line);\n    return line;\n  }\n\n  function lineIsHidden(doc, line) {\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) for (var sp, i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (!sp.marker.collapsed) continue;\n      if (sp.from == null) return true;\n      if (sp.marker.replacedWith) continue;\n      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))\n        return true;\n    }\n  }\n  function lineIsHiddenInner(doc, line, span) {\n    if (span.to == null) {\n      var end = span.marker.find().to, endLine = getLine(doc, end.line);\n      return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));\n    }\n    if (span.marker.inclusiveRight && span.to == line.text.length)\n      return true;\n    for (var sp, i = 0; i < line.markedSpans.length; ++i) {\n      sp = line.markedSpans[i];\n      if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&\n          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&\n          lineIsHiddenInner(doc, line, sp)) return true;\n    }\n  }\n\n  function detachMarkedSpans(line) {\n    var spans = line.markedSpans;\n    if (!spans) return;\n    for (var i = 0; i < spans.length; ++i)\n      spans[i].marker.detachLine(line);\n    line.markedSpans = null;\n  }\n\n  function attachMarkedSpans(line, spans) {\n    if (!spans) return;\n    for (var i = 0; i < spans.length; ++i)\n      spans[i].marker.attachLine(line);\n    line.markedSpans = spans;\n  }\n\n  // LINE WIDGETS\n\n  var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {\n    if (options) for (var opt in options) if (options.hasOwnProperty(opt))\n      this[opt] = options[opt];\n    this.cm = cm;\n    this.node = node;\n  };\n  eventMixin(LineWidget);\n  function widgetOperation(f) {\n    return function() {\n      var withOp = !this.cm.curOp;\n      if (withOp) startOperation(this.cm);\n      try {var result = f.apply(this, arguments);}\n      finally {if (withOp) endOperation(this.cm);}\n      return result;\n    };\n  }\n  LineWidget.prototype.clear = widgetOperation(function() {\n    var ws = this.line.widgets, no = lineNo(this.line);\n    if (no == null || !ws) return;\n    for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);\n    if (!ws.length) this.line.widgets = null;\n    var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;\n    updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));\n    if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);\n    regChange(this.cm, no, no + 1);\n  });\n  LineWidget.prototype.changed = widgetOperation(function() {\n    var oldH = this.height;\n    this.height = null;\n    var diff = widgetHeight(this) - oldH;\n    if (!diff) return;\n    updateLineHeight(this.line, this.line.height + diff);\n    var no = lineNo(this.line);\n    regChange(this.cm, no, no + 1);\n  });\n\n  function widgetHeight(widget) {\n    if (widget.height != null) return widget.height;\n    if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)\n      removeChildrenAndAdd(widget.cm.display.measure, elt(\"div\", [widget.node], null, \"position: relative\"));\n    return widget.height = widget.node.offsetHeight;\n  }\n\n  function addLineWidget(cm, handle, node, options) {\n    var widget = new LineWidget(cm, node, options);\n    if (widget.noHScroll) cm.display.alignWidgets = true;\n    changeLine(cm, handle, function(line) {\n      var widgets = line.widgets || (line.widgets = []);\n      if (widget.insertAt == null) widgets.push(widget);\n      else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);\n      widget.line = line;\n      if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {\n        var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;\n        updateLineHeight(line, line.height + widgetHeight(widget));\n        if (aboveVisible) addToScrollPos(cm, 0, widget.height);\n      }\n      return true;\n    });\n    return widget;\n  }\n\n  // LINE DATA STRUCTURE\n\n  // Line objects. These hold state related to a line, including\n  // highlighting info (the styles array).\n  var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {\n    this.text = text;\n    attachMarkedSpans(this, markedSpans);\n    this.height = estimateHeight ? estimateHeight(this) : 1;\n  };\n  eventMixin(Line);\n\n  function updateLine(line, text, markedSpans, estimateHeight) {\n    line.text = text;\n    if (line.stateAfter) line.stateAfter = null;\n    if (line.styles) line.styles = null;\n    if (line.order != null) line.order = null;\n    detachMarkedSpans(line);\n    attachMarkedSpans(line, markedSpans);\n    var estHeight = estimateHeight ? estimateHeight(line) : 1;\n    if (estHeight != line.height) updateLineHeight(line, estHeight);\n  }\n\n  function cleanUpLine(line) {\n    line.parent = null;\n    detachMarkedSpans(line);\n  }\n\n  // Run the given mode's parser over a line, update the styles\n  // array, which contains alternating fragments of text and CSS\n  // classes.\n  function runMode(cm, text, mode, state, f) {\n    var flattenSpans = mode.flattenSpans;\n    if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;\n    var curStart = 0, curStyle = null;\n    var stream = new StringStream(text, cm.options.tabSize), style;\n    if (text == \"\" && mode.blankLine) mode.blankLine(state);\n    while (!stream.eol()) {\n      if (stream.pos > cm.options.maxHighlightLength) {\n        flattenSpans = false;\n        // Webkit seems to refuse to render text nodes longer than 57444 characters\n        stream.pos = Math.min(text.length, stream.start + 50000);\n        style = null;\n      } else {\n        style = mode.token(stream, state);\n      }\n      if (!flattenSpans || curStyle != style) {\n        if (curStart < stream.start) f(stream.start, curStyle);\n        curStart = stream.start; curStyle = style;\n      }\n      stream.start = stream.pos;\n    }\n    if (curStart < stream.pos) f(stream.pos, curStyle);\n  }\n\n  function highlightLine(cm, line, state) {\n    // A styles array always starts with a number identifying the\n    // mode/overlays that it is based on (for easy invalidation).\n    var st = [cm.state.modeGen];\n    // Compute the base array of styles\n    runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);});\n\n    // Run overlays, adjust style array.\n    for (var o = 0; o < cm.state.overlays.length; ++o) {\n      var overlay = cm.state.overlays[o], i = 1, at = 0;\n      runMode(cm, line.text, overlay.mode, true, function(end, style) {\n        var start = i;\n        // Ensure there's a token end at the current position, and that i points at it\n        while (at < end) {\n          var i_end = st[i];\n          if (i_end > end)\n            st.splice(i, 1, end, st[i+1], i_end);\n          i += 2;\n          at = Math.min(end, i_end);\n        }\n        if (!style) return;\n        if (overlay.opaque) {\n          st.splice(start, i - start, end, style);\n          i = start + 2;\n        } else {\n          for (; start < i; start += 2) {\n            var cur = st[start+1];\n            st[start+1] = cur ? cur + \" \" + style : style;\n          }\n        }\n      });\n    }\n\n    return st;\n  }\n\n  function getLineStyles(cm, line) {\n    if (!line.styles || line.styles[0] != cm.state.modeGen)\n      line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));\n    return line.styles;\n  }\n\n  // Lightweight form of highlight -- proceed over this line and\n  // update state, but don't save a style array.\n  function processLine(cm, line, state) {\n    var mode = cm.doc.mode;\n    var stream = new StringStream(line.text, cm.options.tabSize);\n    if (line.text == \"\" && mode.blankLine) mode.blankLine(state);\n    while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {\n      mode.token(stream, state);\n      stream.start = stream.pos;\n    }\n  }\n\n  var styleToClassCache = {};\n  function styleToClass(style) {\n    if (!style) return null;\n    return styleToClassCache[style] ||\n      (styleToClassCache[style] = \"cm-\" + style.replace(/ +/g, \" cm-\"));\n  }\n\n  function lineContent(cm, realLine, measure, copyWidgets) {\n    var merged, line = realLine, empty = true;\n    while (merged = collapsedSpanAtStart(line))\n      line = getLine(cm.doc, merged.find().from.line);\n\n    var builder = {pre: elt(\"pre\"), col: 0, pos: 0,\n                   measure: null, measuredSomething: false, cm: cm,\n                   copyWidgets: copyWidgets};\n    if (line.textClass) builder.pre.className = line.textClass;\n\n    do {\n      if (line.text) empty = false;\n      builder.measure = line == realLine && measure;\n      builder.pos = 0;\n      builder.addToken = builder.measure ? buildTokenMeasure : buildToken;\n      if ((ie || webkit) && cm.getOption(\"lineWrapping\"))\n        builder.addToken = buildTokenSplitSpaces(builder.addToken);\n      var next = insertLineContent(line, builder, getLineStyles(cm, line));\n      if (measure && line == realLine && !builder.measuredSomething) {\n        measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));\n        builder.measuredSomething = true;\n      }\n      if (next) line = getLine(cm.doc, next.to.line);\n    } while (next);\n\n    if (measure && !builder.measuredSomething && !measure[0])\n      measure[0] = builder.pre.appendChild(empty ? elt(\"span\", \"\\u00a0\") : zeroWidthElement(cm.display.measure));\n    if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))\n      builder.pre.appendChild(document.createTextNode(\"\\u00a0\"));\n\n    var order;\n    // Work around problem with the reported dimensions of single-char\n    // direction spans on IE (issue #1129). See also the comment in\n    // cursorCoords.\n    if (measure && ie && (order = getOrder(line))) {\n      var l = order.length - 1;\n      if (order[l].from == order[l].to) --l;\n      var last = order[l], prev = order[l - 1];\n      if (last.from + 1 == last.to && prev && last.level < prev.level) {\n        var span = measure[builder.pos - 1];\n        if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),\n                                               span.nextSibling);\n      }\n    }\n\n    signal(cm, \"renderLine\", cm, realLine, builder.pre);\n    return builder.pre;\n  }\n\n  var tokenSpecialChars = /[\\t\\u0000-\\u0019\\u00ad\\u200b\\u2028\\u2029\\uFEFF]/g;\n  function buildToken(builder, text, style, startStyle, endStyle, title) {\n    if (!text) return;\n    if (!tokenSpecialChars.test(text)) {\n      builder.col += text.length;\n      var content = document.createTextNode(text);\n    } else {\n      var content = document.createDocumentFragment(), pos = 0;\n      while (true) {\n        tokenSpecialChars.lastIndex = pos;\n        var m = tokenSpecialChars.exec(text);\n        var skipped = m ? m.index - pos : text.length - pos;\n        if (skipped) {\n          content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));\n          builder.col += skipped;\n        }\n        if (!m) break;\n        pos += skipped + 1;\n        if (m[0] == \"\\t\") {\n          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;\n          content.appendChild(elt(\"span\", spaceStr(tabWidth), \"cm-tab\"));\n          builder.col += tabWidth;\n        } else {\n          var token = elt(\"span\", \"\\u2022\", \"cm-invalidchar\");\n          token.title = \"\\\\u\" + m[0].charCodeAt(0).toString(16);\n          content.appendChild(token);\n          builder.col += 1;\n        }\n      }\n    }\n    if (style || startStyle || endStyle || builder.measure) {\n      var fullStyle = style || \"\";\n      if (startStyle) fullStyle += startStyle;\n      if (endStyle) fullStyle += endStyle;\n      var token = elt(\"span\", [content], fullStyle);\n      if (title) token.title = title;\n      return builder.pre.appendChild(token);\n    }\n    builder.pre.appendChild(content);\n  }\n\n  function buildTokenMeasure(builder, text, style, startStyle, endStyle) {\n    var wrapping = builder.cm.options.lineWrapping;\n    for (var i = 0; i < text.length; ++i) {\n      var ch = text.charAt(i), start = i == 0;\n      if (ch >= \"\\ud800\" && ch < \"\\udbff\" && i < text.length - 1) {\n        ch = text.slice(i, i + 2);\n        ++i;\n      } else if (i && wrapping && spanAffectsWrapping(text, i)) {\n        builder.pre.appendChild(elt(\"wbr\"));\n      }\n      var old = builder.measure[builder.pos];\n      var span = builder.measure[builder.pos] =\n        buildToken(builder, ch, style,\n                   start && startStyle, i == text.length - 1 && endStyle);\n      if (old) span.leftSide = old.leftSide || old;\n      // In IE single-space nodes wrap differently than spaces\n      // embedded in larger text nodes, except when set to\n      // white-space: normal (issue #1268).\n      if (ie && wrapping && ch == \" \" && i && !/\\s/.test(text.charAt(i - 1)) &&\n          i < text.length - 1 && !/\\s/.test(text.charAt(i + 1)))\n        span.style.whiteSpace = \"normal\";\n      builder.pos += ch.length;\n    }\n    if (text.length) builder.measuredSomething = true;\n  }\n\n  function buildTokenSplitSpaces(inner) {\n    function split(old) {\n      var out = \" \";\n      for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? \" \" : \"\\u00a0\";\n      out += \" \";\n      return out;\n    }\n    return function(builder, text, style, startStyle, endStyle, title) {\n      return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title);\n    };\n  }\n\n  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {\n    var widget = !ignoreWidget && marker.replacedWith;\n    if (widget) {\n      if (builder.copyWidgets) widget = widget.cloneNode(true);\n      builder.pre.appendChild(widget);\n      if (builder.measure) {\n        if (size) {\n          builder.measure[builder.pos] = widget;\n        } else {\n          var elt = builder.measure[builder.pos] = zeroWidthElement(builder.cm.display.measure);\n          if (marker.type != \"bookmark\" || marker.insertLeft)\n            builder.pre.insertBefore(elt, widget);\n          else\n            builder.pre.appendChild(elt);\n        }\n        builder.measuredSomething = true;\n      }\n    }\n    builder.pos += size;\n  }\n\n  // Outputs a number of spans to make up a line, taking highlighting\n  // and marked text into account.\n  function insertLineContent(line, builder, styles) {\n    var spans = line.markedSpans, allText = line.text, at = 0;\n    if (!spans) {\n      for (var i = 1; i < styles.length; i+=2)\n        builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1]));\n      return;\n    }\n\n    var len = allText.length, pos = 0, i = 1, text = \"\", style;\n    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;\n    for (;;) {\n      if (nextChange == pos) { // Update current marker set\n        spanStyle = spanEndStyle = spanStartStyle = title = \"\";\n        collapsed = null; nextChange = Infinity;\n        var foundBookmark = null;\n        for (var j = 0; j < spans.length; ++j) {\n          var sp = spans[j], m = sp.marker;\n          if (sp.from <= pos && (sp.to == null || sp.to > pos)) {\n            if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = \"\"; }\n            if (m.className) spanStyle += \" \" + m.className;\n            if (m.startStyle && sp.from == pos) spanStartStyle += \" \" + m.startStyle;\n            if (m.endStyle && sp.to == nextChange) spanEndStyle += \" \" + m.endStyle;\n            if (m.title && !title) title = m.title;\n            if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))\n              collapsed = sp;\n          } else if (sp.from > pos && nextChange > sp.from) {\n            nextChange = sp.from;\n          }\n          if (m.type == \"bookmark\" && sp.from == pos && m.replacedWith) foundBookmark = m;\n        }\n        if (collapsed && (collapsed.from || 0) == pos) {\n          buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,\n                             collapsed.marker, collapsed.from == null);\n          if (collapsed.to == null) return collapsed.marker.find();\n        }\n        if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);\n      }\n      if (pos >= len) break;\n\n      var upto = Math.min(len, nextChange);\n      while (true) {\n        if (text) {\n          var end = pos + text.length;\n          if (!collapsed) {\n            var tokenText = end > upto ? text.slice(0, upto - pos) : text;\n            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,\n                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : \"\", title);\n          }\n          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}\n          pos = end;\n          spanStartStyle = \"\";\n        }\n        text = allText.slice(at, at = styles[i++]);\n        style = styleToClass(styles[i++]);\n      }\n    }\n  }\n\n  // DOCUMENT DATA STRUCTURE\n\n  function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {\n    function spansFor(n) {return markedSpans ? markedSpans[n] : null;}\n    function update(line, text, spans) {\n      updateLine(line, text, spans, estimateHeight);\n      signalLater(line, \"change\", line, change);\n    }\n\n    var from = change.from, to = change.to, text = change.text;\n    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);\n    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;\n\n    // First adjust the line structure\n    if (from.ch == 0 && to.ch == 0 && lastText == \"\") {\n      // This is a whole-line replace. Treated specially to make\n      // sure line objects move the way they are supposed to.\n      for (var i = 0, e = text.length - 1, added = []; i < e; ++i)\n        added.push(new Line(text[i], spansFor(i), estimateHeight));\n      update(lastLine, lastLine.text, lastSpans);\n      if (nlines) doc.remove(from.line, nlines);\n      if (added.length) doc.insert(from.line, added);\n    } else if (firstLine == lastLine) {\n      if (text.length == 1) {\n        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);\n      } else {\n        for (var added = [], i = 1, e = text.length - 1; i < e; ++i)\n          added.push(new Line(text[i], spansFor(i), estimateHeight));\n        added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));\n        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n        doc.insert(from.line + 1, added);\n      }\n    } else if (text.length == 1) {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));\n      doc.remove(from.line + 1, nlines);\n    } else {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);\n      for (var i = 1, e = text.length - 1, added = []; i < e; ++i)\n        added.push(new Line(text[i], spansFor(i), estimateHeight));\n      if (nlines > 1) doc.remove(from.line + 1, nlines - 1);\n      doc.insert(from.line + 1, added);\n    }\n\n    signalLater(doc, \"change\", doc, change);\n    setSelection(doc, selAfter.anchor, selAfter.head, null, true);\n  }\n\n  function LeafChunk(lines) {\n    this.lines = lines;\n    this.parent = null;\n    for (var i = 0, e = lines.length, height = 0; i < e; ++i) {\n      lines[i].parent = this;\n      height += lines[i].height;\n    }\n    this.height = height;\n  }\n\n  LeafChunk.prototype = {\n    chunkSize: function() { return this.lines.length; },\n    removeInner: function(at, n) {\n      for (var i = at, e = at + n; i < e; ++i) {\n        var line = this.lines[i];\n        this.height -= line.height;\n        cleanUpLine(line);\n        signalLater(line, \"delete\");\n      }\n      this.lines.splice(at, n);\n    },\n    collapse: function(lines) {\n      lines.splice.apply(lines, [lines.length, 0].concat(this.lines));\n    },\n    insertInner: function(at, lines, height) {\n      this.height += height;\n      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));\n      for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;\n    },\n    iterN: function(at, n, op) {\n      for (var e = at + n; at < e; ++at)\n        if (op(this.lines[at])) return true;\n    }\n  };\n\n  function BranchChunk(children) {\n    this.children = children;\n    var size = 0, height = 0;\n    for (var i = 0, e = children.length; i < e; ++i) {\n      var ch = children[i];\n      size += ch.chunkSize(); height += ch.height;\n      ch.parent = this;\n    }\n    this.size = size;\n    this.height = height;\n    this.parent = null;\n  }\n\n  BranchChunk.prototype = {\n    chunkSize: function() { return this.size; },\n    removeInner: function(at, n) {\n      this.size -= n;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var rm = Math.min(n, sz - at), oldHeight = child.height;\n          child.removeInner(at, rm);\n          this.height -= oldHeight - child.height;\n          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }\n          if ((n -= rm) == 0) break;\n          at = 0;\n        } else at -= sz;\n      }\n      if (this.size - n < 25) {\n        var lines = [];\n        this.collapse(lines);\n        this.children = [new LeafChunk(lines)];\n        this.children[0].parent = this;\n      }\n    },\n    collapse: function(lines) {\n      for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);\n    },\n    insertInner: function(at, lines, height) {\n      this.size += lines.length;\n      this.height += height;\n      for (var i = 0, e = this.children.length; i < e; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at <= sz) {\n          child.insertInner(at, lines, height);\n          if (child.lines && child.lines.length > 50) {\n            while (child.lines.length > 50) {\n              var spilled = child.lines.splice(child.lines.length - 25, 25);\n              var newleaf = new LeafChunk(spilled);\n              child.height -= newleaf.height;\n              this.children.splice(i + 1, 0, newleaf);\n              newleaf.parent = this;\n            }\n            this.maybeSpill();\n          }\n          break;\n        }\n        at -= sz;\n      }\n    },\n    maybeSpill: function() {\n      if (this.children.length <= 10) return;\n      var me = this;\n      do {\n        var spilled = me.children.splice(me.children.length - 5, 5);\n        var sibling = new BranchChunk(spilled);\n        if (!me.parent) { // Become the parent node\n          var copy = new BranchChunk(me.children);\n          copy.parent = me;\n          me.children = [copy, sibling];\n          me = copy;\n        } else {\n          me.size -= sibling.size;\n          me.height -= sibling.height;\n          var myIndex = indexOf(me.parent.children, me);\n          me.parent.children.splice(myIndex + 1, 0, sibling);\n        }\n        sibling.parent = me.parent;\n      } while (me.children.length > 10);\n      me.parent.maybeSpill();\n    },\n    iterN: function(at, n, op) {\n      for (var i = 0, e = this.children.length; i < e; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var used = Math.min(n, sz - at);\n          if (child.iterN(at, used, op)) return true;\n          if ((n -= used) == 0) break;\n          at = 0;\n        } else at -= sz;\n      }\n    }\n  };\n\n  var nextDocId = 0;\n  var Doc = CodeMirror.Doc = function(text, mode, firstLine) {\n    if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);\n    if (firstLine == null) firstLine = 0;\n\n    BranchChunk.call(this, [new LeafChunk([new Line(\"\", null)])]);\n    this.first = firstLine;\n    this.scrollTop = this.scrollLeft = 0;\n    this.cantEdit = false;\n    this.history = makeHistory();\n    this.cleanGeneration = 1;\n    this.frontier = firstLine;\n    var start = Pos(firstLine, 0);\n    this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};\n    this.id = ++nextDocId;\n    this.modeOption = mode;\n\n    if (typeof text == \"string\") text = splitLines(text);\n    updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});\n  };\n\n  Doc.prototype = createObj(BranchChunk.prototype, {\n    constructor: Doc,\n    iter: function(from, to, op) {\n      if (op) this.iterN(from - this.first, to - from, op);\n      else this.iterN(this.first, this.first + this.size, from);\n    },\n\n    insert: function(at, lines) {\n      var height = 0;\n      for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;\n      this.insertInner(at - this.first, lines, height);\n    },\n    remove: function(at, n) { this.removeInner(at - this.first, n); },\n\n    getValue: function(lineSep) {\n      var lines = getLines(this, this.first, this.first + this.size);\n      if (lineSep === false) return lines;\n      return lines.join(lineSep || \"\\n\");\n    },\n    setValue: function(code) {\n      var top = Pos(this.first, 0), last = this.first + this.size - 1;\n      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),\n                        text: splitLines(code), origin: \"setValue\"},\n                 {head: top, anchor: top}, true);\n    },\n    replaceRange: function(code, from, to, origin) {\n      from = clipPos(this, from);\n      to = to ? clipPos(this, to) : from;\n      replaceRange(this, code, from, to, origin);\n    },\n    getRange: function(from, to, lineSep) {\n      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));\n      if (lineSep === false) return lines;\n      return lines.join(lineSep || \"\\n\");\n    },\n\n    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},\n    setLine: function(line, text) {\n      if (isLine(this, line))\n        replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));\n    },\n    removeLine: function(line) {\n      if (line) replaceRange(this, \"\", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));\n      else replaceRange(this, \"\", Pos(0, 0), clipPos(this, Pos(1, 0)));\n    },\n\n    getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},\n    getLineNumber: function(line) {return lineNo(line);},\n\n    getLineHandleVisualStart: function(line) {\n      if (typeof line == \"number\") line = getLine(this, line);\n      return visualLine(this, line);\n    },\n\n    lineCount: function() {return this.size;},\n    firstLine: function() {return this.first;},\n    lastLine: function() {return this.first + this.size - 1;},\n\n    clipPos: function(pos) {return clipPos(this, pos);},\n\n    getCursor: function(start) {\n      var sel = this.sel, pos;\n      if (start == null || start == \"head\") pos = sel.head;\n      else if (start == \"anchor\") pos = sel.anchor;\n      else if (start == \"end\" || start === false) pos = sel.to;\n      else pos = sel.from;\n      return copyPos(pos);\n    },\n    somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},\n\n    setCursor: docOperation(function(line, ch, extend) {\n      var pos = clipPos(this, typeof line == \"number\" ? Pos(line, ch || 0) : line);\n      if (extend) extendSelection(this, pos);\n      else setSelection(this, pos, pos);\n    }),\n    setSelection: docOperation(function(anchor, head) {\n      setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));\n    }),\n    extendSelection: docOperation(function(from, to) {\n      extendSelection(this, clipPos(this, from), to && clipPos(this, to));\n    }),\n\n    getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},\n    replaceSelection: function(code, collapse, origin) {\n      makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || \"around\");\n    },\n    undo: docOperation(function() {makeChangeFromHistory(this, \"undo\");}),\n    redo: docOperation(function() {makeChangeFromHistory(this, \"redo\");}),\n\n    setExtending: function(val) {this.sel.extend = val;},\n\n    historySize: function() {\n      var hist = this.history;\n      return {undo: hist.done.length, redo: hist.undone.length};\n    },\n    clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},\n\n    markClean: function() {\n      this.cleanGeneration = this.changeGeneration();\n    },\n    changeGeneration: function() {\n      this.history.lastOp = this.history.lastOrigin = null;\n      return this.history.generation;\n    },\n    isClean: function (gen) {\n      return this.history.generation == (gen || this.cleanGeneration);\n    },\n\n    getHistory: function() {\n      return {done: copyHistoryArray(this.history.done),\n              undone: copyHistoryArray(this.history.undone)};\n    },\n    setHistory: function(histData) {\n      var hist = this.history = makeHistory(this.history.maxGeneration);\n      hist.done = histData.done.slice(0);\n      hist.undone = histData.undone.slice(0);\n    },\n\n    markText: function(from, to, options) {\n      return markText(this, clipPos(this, from), clipPos(this, to), options, \"range\");\n    },\n    setBookmark: function(pos, options) {\n      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),\n                      insertLeft: options && options.insertLeft};\n      pos = clipPos(this, pos);\n      return markText(this, pos, pos, realOpts, \"bookmark\");\n    },\n    findMarksAt: function(pos) {\n      pos = clipPos(this, pos);\n      var markers = [], spans = getLine(this, pos.line).markedSpans;\n      if (spans) for (var i = 0; i < spans.length; ++i) {\n        var span = spans[i];\n        if ((span.from == null || span.from <= pos.ch) &&\n            (span.to == null || span.to >= pos.ch))\n          markers.push(span.marker.parent || span.marker);\n      }\n      return markers;\n    },\n    getAllMarks: function() {\n      var markers = [];\n      this.iter(function(line) {\n        var sps = line.markedSpans;\n        if (sps) for (var i = 0; i < sps.length; ++i)\n          if (sps[i].from != null) markers.push(sps[i].marker);\n      });\n      return markers;\n    },\n\n    posFromIndex: function(off) {\n      var ch, lineNo = this.first;\n      this.iter(function(line) {\n        var sz = line.text.length + 1;\n        if (sz > off) { ch = off; return true; }\n        off -= sz;\n        ++lineNo;\n      });\n      return clipPos(this, Pos(lineNo, ch));\n    },\n    indexFromPos: function (coords) {\n      coords = clipPos(this, coords);\n      var index = coords.ch;\n      if (coords.line < this.first || coords.ch < 0) return 0;\n      this.iter(this.first, coords.line, function (line) {\n        index += line.text.length + 1;\n      });\n      return index;\n    },\n\n    copy: function(copyHistory) {\n      var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);\n      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;\n      doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,\n                 shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};\n      if (copyHistory) {\n        doc.history.undoDepth = this.history.undoDepth;\n        doc.setHistory(this.getHistory());\n      }\n      return doc;\n    },\n\n    linkedDoc: function(options) {\n      if (!options) options = {};\n      var from = this.first, to = this.first + this.size;\n      if (options.from != null && options.from > from) from = options.from;\n      if (options.to != null && options.to < to) to = options.to;\n      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);\n      if (options.sharedHist) copy.history = this.history;\n      (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});\n      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];\n      return copy;\n    },\n    unlinkDoc: function(other) {\n      if (other instanceof CodeMirror) other = other.doc;\n      if (this.linked) for (var i = 0; i < this.linked.length; ++i) {\n        var link = this.linked[i];\n        if (link.doc != other) continue;\n        this.linked.splice(i, 1);\n        other.unlinkDoc(this);\n        break;\n      }\n      // If the histories were shared, split them again\n      if (other.history == this.history) {\n        var splitIds = [other.id];\n        linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);\n        other.history = makeHistory();\n        other.history.done = copyHistoryArray(this.history.done, splitIds);\n        other.history.undone = copyHistoryArray(this.history.undone, splitIds);\n      }\n    },\n    iterLinkedDocs: function(f) {linkedDocs(this, f);},\n\n    getMode: function() {return this.mode;},\n    getEditor: function() {return this.cm;}\n  });\n\n  Doc.prototype.eachLine = Doc.prototype.iter;\n\n  // The Doc methods that should be available on CodeMirror instances\n  var dontDelegate = \"iter insert remove copy getEditor\".split(\" \");\n  for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)\n    CodeMirror.prototype[prop] = (function(method) {\n      return function() {return method.apply(this.doc, arguments);};\n    })(Doc.prototype[prop]);\n\n  eventMixin(Doc);\n\n  function linkedDocs(doc, f, sharedHistOnly) {\n    function propagate(doc, skip, sharedHist) {\n      if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {\n        var rel = doc.linked[i];\n        if (rel.doc == skip) continue;\n        var shared = sharedHist && rel.sharedHist;\n        if (sharedHistOnly && !shared) continue;\n        f(rel.doc, shared);\n        propagate(rel.doc, doc, shared);\n      }\n    }\n    propagate(doc, null, true);\n  }\n\n  function attachDoc(cm, doc) {\n    if (doc.cm) throw new Error(\"This document is already in use.\");\n    cm.doc = doc;\n    doc.cm = cm;\n    estimateLineHeights(cm);\n    loadMode(cm);\n    if (!cm.options.lineWrapping) computeMaxLength(cm);\n    cm.options.mode = doc.modeOption;\n    regChange(cm);\n  }\n\n  // LINE UTILITIES\n\n  function getLine(chunk, n) {\n    n -= chunk.first;\n    while (!chunk.lines) {\n      for (var i = 0;; ++i) {\n        var child = chunk.children[i], sz = child.chunkSize();\n        if (n < sz) { chunk = child; break; }\n        n -= sz;\n      }\n    }\n    return chunk.lines[n];\n  }\n\n  function getBetween(doc, start, end) {\n    var out = [], n = start.line;\n    doc.iter(start.line, end.line + 1, function(line) {\n      var text = line.text;\n      if (n == end.line) text = text.slice(0, end.ch);\n      if (n == start.line) text = text.slice(start.ch);\n      out.push(text);\n      ++n;\n    });\n    return out;\n  }\n  function getLines(doc, from, to) {\n    var out = [];\n    doc.iter(from, to, function(line) { out.push(line.text); });\n    return out;\n  }\n\n  function updateLineHeight(line, height) {\n    var diff = height - line.height;\n    for (var n = line; n; n = n.parent) n.height += diff;\n  }\n\n  function lineNo(line) {\n    if (line.parent == null) return null;\n    var cur = line.parent, no = indexOf(cur.lines, line);\n    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\n      for (var i = 0;; ++i) {\n        if (chunk.children[i] == cur) break;\n        no += chunk.children[i].chunkSize();\n      }\n    }\n    return no + cur.first;\n  }\n\n  function lineAtHeight(chunk, h) {\n    var n = chunk.first;\n    outer: do {\n      for (var i = 0, e = chunk.children.length; i < e; ++i) {\n        var child = chunk.children[i], ch = child.height;\n        if (h < ch) { chunk = child; continue outer; }\n        h -= ch;\n        n += child.chunkSize();\n      }\n      return n;\n    } while (!chunk.lines);\n    for (var i = 0, e = chunk.lines.length; i < e; ++i) {\n      var line = chunk.lines[i], lh = line.height;\n      if (h < lh) break;\n      h -= lh;\n    }\n    return n + i;\n  }\n\n  function heightAtLine(cm, lineObj) {\n    lineObj = visualLine(cm.doc, lineObj);\n\n    var h = 0, chunk = lineObj.parent;\n    for (var i = 0; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i];\n      if (line == lineObj) break;\n      else h += line.height;\n    }\n    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {\n      for (var i = 0; i < p.children.length; ++i) {\n        var cur = p.children[i];\n        if (cur == chunk) break;\n        else h += cur.height;\n      }\n    }\n    return h;\n  }\n\n  function getOrder(line) {\n    var order = line.order;\n    if (order == null) order = line.order = bidiOrdering(line.text);\n    return order;\n  }\n\n  // HISTORY\n\n  function makeHistory(startGen) {\n    return {\n      // Arrays of history events. Doing something adds an event to\n      // done and clears undo. Undoing moves events from done to\n      // undone, redoing moves them in the other direction.\n      done: [], undone: [], undoDepth: Infinity,\n      // Used to track when changes can be merged into a single undo\n      // event\n      lastTime: 0, lastOp: null, lastOrigin: null,\n      // Used by the isClean() method\n      generation: startGen || 1, maxGeneration: startGen || 1\n    };\n  }\n\n  function attachLocalSpans(doc, change, from, to) {\n    var existing = change[\"spans_\" + doc.id], n = 0;\n    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {\n      if (line.markedSpans)\n        (existing || (existing = change[\"spans_\" + doc.id] = {}))[n] = line.markedSpans;\n      ++n;\n    });\n  }\n\n  function historyChangeFromChange(doc, change) {\n    var from = { line: change.from.line, ch: change.from.ch };\n    var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};\n    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);\n    linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);\n    return histChange;\n  }\n\n  function addToHistory(doc, change, selAfter, opId) {\n    var hist = doc.history;\n    hist.undone.length = 0;\n    var time = +new Date, cur = lst(hist.done);\n\n    if (cur &&\n        (hist.lastOp == opId ||\n         hist.lastOrigin == change.origin && change.origin &&\n         ((change.origin.charAt(0) == \"+\" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||\n          change.origin.charAt(0) == \"*\"))) {\n      // Merge this change into the last event\n      var last = lst(cur.changes);\n      if (posEq(change.from, change.to) && posEq(change.from, last.to)) {\n        // Optimized case for simple insertion -- don't want to add\n        // new changesets for every character typed\n        last.to = changeEnd(change);\n      } else {\n        // Add new sub-event\n        cur.changes.push(historyChangeFromChange(doc, change));\n      }\n      cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;\n    } else {\n      // Can not be merged, start a new event.\n      cur = {changes: [historyChangeFromChange(doc, change)],\n             generation: hist.generation,\n             anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,\n             anchorAfter: selAfter.anchor, headAfter: selAfter.head};\n      hist.done.push(cur);\n      hist.generation = ++hist.maxGeneration;\n      while (hist.done.length > hist.undoDepth)\n        hist.done.shift();\n    }\n    hist.lastTime = time;\n    hist.lastOp = opId;\n    hist.lastOrigin = change.origin;\n  }\n\n  function removeClearedSpans(spans) {\n    if (!spans) return null;\n    for (var i = 0, out; i < spans.length; ++i) {\n      if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }\n      else if (out) out.push(spans[i]);\n    }\n    return !out ? spans : out.length ? out : null;\n  }\n\n  function getOldSpans(doc, change) {\n    var found = change[\"spans_\" + doc.id];\n    if (!found) return null;\n    for (var i = 0, nw = []; i < change.text.length; ++i)\n      nw.push(removeClearedSpans(found[i]));\n    return nw;\n  }\n\n  // Used both to provide a JSON-safe object in .getHistory, and, when\n  // detaching a document, to split the history in two\n  function copyHistoryArray(events, newGroup) {\n    for (var i = 0, copy = []; i < events.length; ++i) {\n      var event = events[i], changes = event.changes, newChanges = [];\n      copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,\n                 anchorAfter: event.anchorAfter, headAfter: event.headAfter});\n      for (var j = 0; j < changes.length; ++j) {\n        var change = changes[j], m;\n        newChanges.push({from: change.from, to: change.to, text: change.text});\n        if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\\d+)$/)) {\n          if (indexOf(newGroup, Number(m[1])) > -1) {\n            lst(newChanges)[prop] = change[prop];\n            delete change[prop];\n          }\n        }\n      }\n    }\n    return copy;\n  }\n\n  // Rebasing/resetting history to deal with externally-sourced changes\n\n  function rebaseHistSel(pos, from, to, diff) {\n    if (to < pos.line) {\n      pos.line += diff;\n    } else if (from < pos.line) {\n      pos.line = from;\n      pos.ch = 0;\n    }\n  }\n\n  // Tries to rebase an array of history events given a change in the\n  // document. If the change touches the same lines as the event, the\n  // event, and everything 'behind' it, is discarded. If the change is\n  // before the event, the event's positions are updated. Uses a\n  // copy-on-write scheme for the positions, to avoid having to\n  // reallocate them all on every rebase, but also avoid problems with\n  // shared position objects being unsafely updated.\n  function rebaseHistArray(array, from, to, diff) {\n    for (var i = 0; i < array.length; ++i) {\n      var sub = array[i], ok = true;\n      for (var j = 0; j < sub.changes.length; ++j) {\n        var cur = sub.changes[j];\n        if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }\n        if (to < cur.from.line) {\n          cur.from.line += diff;\n          cur.to.line += diff;\n        } else if (from <= cur.to.line) {\n          ok = false;\n          break;\n        }\n      }\n      if (!sub.copied) {\n        sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);\n        sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);\n        sub.copied = true;\n      }\n      if (!ok) {\n        array.splice(0, i + 1);\n        i = 0;\n      } else {\n        rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);\n        rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);\n      }\n    }\n  }\n\n  function rebaseHist(hist, change) {\n    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;\n    rebaseHistArray(hist.done, from, to, diff);\n    rebaseHistArray(hist.undone, from, to, diff);\n  }\n\n  // EVENT OPERATORS\n\n  function stopMethod() {e_stop(this);}\n  // Ensure an event has a stop method.\n  function addStop(event) {\n    if (!event.stop) event.stop = stopMethod;\n    return event;\n  }\n\n  function e_preventDefault(e) {\n    if (e.preventDefault) e.preventDefault();\n    else e.returnValue = false;\n  }\n  function e_stopPropagation(e) {\n    if (e.stopPropagation) e.stopPropagation();\n    else e.cancelBubble = true;\n  }\n  function e_defaultPrevented(e) {\n    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;\n  }\n  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\n  CodeMirror.e_stop = e_stop;\n  CodeMirror.e_preventDefault = e_preventDefault;\n  CodeMirror.e_stopPropagation = e_stopPropagation;\n\n  function e_target(e) {return e.target || e.srcElement;}\n  function e_button(e) {\n    var b = e.which;\n    if (b == null) {\n      if (e.button & 1) b = 1;\n      else if (e.button & 2) b = 3;\n      else if (e.button & 4) b = 2;\n    }\n    if (mac && e.ctrlKey && b == 1) b = 3;\n    return b;\n  }\n\n  // EVENT HANDLING\n\n  function on(emitter, type, f) {\n    if (emitter.addEventListener)\n      emitter.addEventListener(type, f, false);\n    else if (emitter.attachEvent)\n      emitter.attachEvent(\"on\" + type, f);\n    else {\n      var map = emitter._handlers || (emitter._handlers = {});\n      var arr = map[type] || (map[type] = []);\n      arr.push(f);\n    }\n  }\n\n  function off(emitter, type, f) {\n    if (emitter.removeEventListener)\n      emitter.removeEventListener(type, f, false);\n    else if (emitter.detachEvent)\n      emitter.detachEvent(\"on\" + type, f);\n    else {\n      var arr = emitter._handlers && emitter._handlers[type];\n      if (!arr) return;\n      for (var i = 0; i < arr.length; ++i)\n        if (arr[i] == f) { arr.splice(i, 1); break; }\n    }\n  }\n\n  function signal(emitter, type /*, values...*/) {\n    var arr = emitter._handlers && emitter._handlers[type];\n    if (!arr) return;\n    var args = Array.prototype.slice.call(arguments, 2);\n    for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);\n  }\n\n  var delayedCallbacks, delayedCallbackDepth = 0;\n  function signalLater(emitter, type /*, values...*/) {\n    var arr = emitter._handlers && emitter._handlers[type];\n    if (!arr) return;\n    var args = Array.prototype.slice.call(arguments, 2);\n    if (!delayedCallbacks) {\n      ++delayedCallbackDepth;\n      delayedCallbacks = [];\n      setTimeout(fireDelayed, 0);\n    }\n    function bnd(f) {return function(){f.apply(null, args);};};\n    for (var i = 0; i < arr.length; ++i)\n      delayedCallbacks.push(bnd(arr[i]));\n  }\n\n  function signalDOMEvent(cm, e, override) {\n    signal(cm, override || e.type, cm, e);\n    return e_defaultPrevented(e) || e.codemirrorIgnore;\n  }\n\n  function fireDelayed() {\n    --delayedCallbackDepth;\n    var delayed = delayedCallbacks;\n    delayedCallbacks = null;\n    for (var i = 0; i < delayed.length; ++i) delayed[i]();\n  }\n\n  function hasHandler(emitter, type) {\n    var arr = emitter._handlers && emitter._handlers[type];\n    return arr && arr.length > 0;\n  }\n\n  CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;\n\n  function eventMixin(ctor) {\n    ctor.prototype.on = function(type, f) {on(this, type, f);};\n    ctor.prototype.off = function(type, f) {off(this, type, f);};\n  }\n\n  // MISC UTILITIES\n\n  // Number of pixels added to scroller and sizer to hide scrollbar\n  var scrollerCutOff = 30;\n\n  // Returned or thrown by various protocols to signal 'I'm not\n  // handling this'.\n  var Pass = CodeMirror.Pass = {toString: function(){return \"CodeMirror.Pass\";}};\n\n  function Delayed() {this.id = null;}\n  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};\n\n  // Counts the column offset in a string, taking tabs into account.\n  // Used mostly to find indentation.\n  function countColumn(string, end, tabSize, startIndex, startValue) {\n    if (end == null) {\n      end = string.search(/[^\\s\\u00a0]/);\n      if (end == -1) end = string.length;\n    }\n    for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {\n      if (string.charAt(i) == \"\\t\") n += tabSize - (n % tabSize);\n      else ++n;\n    }\n    return n;\n  }\n  CodeMirror.countColumn = countColumn;\n\n  var spaceStrs = [\"\"];\n  function spaceStr(n) {\n    while (spaceStrs.length <= n)\n      spaceStrs.push(lst(spaceStrs) + \" \");\n    return spaceStrs[n];\n  }\n\n  function lst(arr) { return arr[arr.length-1]; }\n\n  function selectInput(node) {\n    if (ios) { // Mobile Safari apparently has a bug where select() is broken.\n      node.selectionStart = 0;\n      node.selectionEnd = node.value.length;\n    } else {\n      // Suppress mysterious IE10 errors\n      try { node.select(); }\n      catch(_e) {}\n    }\n  }\n\n  function indexOf(collection, elt) {\n    if (collection.indexOf) return collection.indexOf(elt);\n    for (var i = 0, e = collection.length; i < e; ++i)\n      if (collection[i] == elt) return i;\n    return -1;\n  }\n\n  function createObj(base, props) {\n    function Obj() {}\n    Obj.prototype = base;\n    var inst = new Obj();\n    if (props) copyObj(props, inst);\n    return inst;\n  }\n\n  function copyObj(obj, target) {\n    if (!target) target = {};\n    for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];\n    return target;\n  }\n\n  function emptyArray(size) {\n    for (var a = [], i = 0; i < size; ++i) a.push(undefined);\n    return a;\n  }\n\n  function bind(f) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return function(){return f.apply(null, args);};\n  }\n\n  var nonASCIISingleCaseWordChar = /[\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4db5\\u4e00-\\u9fcc\\uac00-\\ud7af]/;\n  function isWordChar(ch) {\n    return /\\w/.test(ch) || ch > \"\\x80\" &&\n      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));\n  }\n\n  function isEmpty(obj) {\n    for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;\n    return true;\n  }\n\n  var isExtendingChar = /[\\u0300-\\u036F\\u0483-\\u0487\\u0488-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1-\\u05C2\\u05C4-\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7-\\u06E8\\u06EA-\\u06ED\\uA66F\\uA670-\\uA672\\uA674-\\uA67D\\uA69F\\udc00-\\udfff]/;\n\n  // DOM UTILITIES\n\n  function elt(tag, content, className, style) {\n    var e = document.createElement(tag);\n    if (className) e.className = className;\n    if (style) e.style.cssText = style;\n    if (typeof content == \"string\") setTextContent(e, content);\n    else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);\n    return e;\n  }\n\n  function removeChildren(e) {\n    for (var count = e.childNodes.length; count > 0; --count)\n      e.removeChild(e.firstChild);\n    return e;\n  }\n\n  function removeChildrenAndAdd(parent, e) {\n    return removeChildren(parent).appendChild(e);\n  }\n\n  function setTextContent(e, str) {\n    if (ie_lt9) {\n      e.innerHTML = \"\";\n      e.appendChild(document.createTextNode(str));\n    } else e.textContent = str;\n  }\n\n  function getRect(node) {\n    return node.getBoundingClientRect();\n  }\n  CodeMirror.replaceGetRect = function(f) { getRect = f; };\n\n  // FEATURE DETECTION\n\n  // Detect drag-and-drop\n  var dragAndDrop = function() {\n    // There is *some* kind of drag-and-drop support in IE6-8, but I\n    // couldn't get it to work yet.\n    if (ie_lt9) return false;\n    var div = elt('div');\n    return \"draggable\" in div || \"dragDrop\" in div;\n  }();\n\n  // For a reason I have yet to figure out, some browsers disallow\n  // word wrapping between certain characters *only* if a new inline\n  // element is started between them. This makes it hard to reliably\n  // measure the position of things, since that requires inserting an\n  // extra span. This terribly fragile set of tests matches the\n  // character combinations that suffer from this phenomenon on the\n  // various browsers.\n  function spanAffectsWrapping() { return false; }\n  if (gecko) // Only for \"$'\"\n    spanAffectsWrapping = function(str, i) {\n      return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;\n    };\n  else if (safari && !/Version\\/([6-9]|\\d\\d)\\b/.test(navigator.userAgent))\n    spanAffectsWrapping = function(str, i) {\n      return /\\-[^ \\-?]|\\?[^ !\\'\\\"\\),.\\-\\/:;\\?\\]\\}]/.test(str.slice(i - 1, i + 1));\n    };\n  else if (webkit && !/Chrome\\/(?:29|[3-9]\\d|\\d\\d\\d)\\./.test(navigator.userAgent))\n    spanAffectsWrapping = function(str, i) {\n      if (i > 1 && str.charCodeAt(i - 1) == 45) {\n        if (/\\w/.test(str.charAt(i - 2)) && /[^\\-?\\.]/.test(str.charAt(i))) return true;\n        if (i > 2 && /[\\d\\.,]/.test(str.charAt(i - 2)) && /[\\d\\.,]/.test(str.charAt(i))) return false;\n      }\n      return /[~!#%&*)=+}\\]|\\\"\\.>,:;][({[<]|-[^\\-?\\.\\u2010-\\u201f\\u2026]|\\?[\\w~`@#$%\\^&*(_=+{[|><]|…[\\w~`@#$%\\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));\n    };\n\n  var knownScrollbarWidth;\n  function scrollbarWidth(measure) {\n    if (knownScrollbarWidth != null) return knownScrollbarWidth;\n    var test = elt(\"div\", null, null, \"width: 50px; height: 50px; overflow-x: scroll\");\n    removeChildrenAndAdd(measure, test);\n    if (test.offsetWidth)\n      knownScrollbarWidth = test.offsetHeight - test.clientHeight;\n    return knownScrollbarWidth || 0;\n  }\n\n  var zwspSupported;\n  function zeroWidthElement(measure) {\n    if (zwspSupported == null) {\n      var test = elt(\"span\", \"\\u200b\");\n      removeChildrenAndAdd(measure, elt(\"span\", [test, document.createTextNode(\"x\")]));\n      if (measure.firstChild.offsetHeight != 0)\n        zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;\n    }\n    if (zwspSupported) return elt(\"span\", \"\\u200b\");\n    else return elt(\"span\", \"\\u00a0\", null, \"display: inline-block; width: 1px; margin-right: -1px\");\n  }\n\n  // See if \"\".split is the broken IE version, if so, provide an\n  // alternative way to split lines.\n  var splitLines = \"\\n\\nb\".split(/\\n/).length != 3 ? function(string) {\n    var pos = 0, result = [], l = string.length;\n    while (pos <= l) {\n      var nl = string.indexOf(\"\\n\", pos);\n      if (nl == -1) nl = string.length;\n      var line = string.slice(pos, string.charAt(nl - 1) == \"\\r\" ? nl - 1 : nl);\n      var rt = line.indexOf(\"\\r\");\n      if (rt != -1) {\n        result.push(line.slice(0, rt));\n        pos += rt + 1;\n      } else {\n        result.push(line);\n        pos = nl + 1;\n      }\n    }\n    return result;\n  } : function(string){return string.split(/\\r\\n?|\\n/);};\n  CodeMirror.splitLines = splitLines;\n\n  var hasSelection = window.getSelection ? function(te) {\n    try { return te.selectionStart != te.selectionEnd; }\n    catch(e) { return false; }\n  } : function(te) {\n    try {var range = te.ownerDocument.selection.createRange();}\n    catch(e) {}\n    if (!range || range.parentElement() != te) return false;\n    return range.compareEndPoints(\"StartToEnd\", range) != 0;\n  };\n\n  var hasCopyEvent = (function() {\n    var e = elt(\"div\");\n    if (\"oncopy\" in e) return true;\n    e.setAttribute(\"oncopy\", \"return;\");\n    return typeof e.oncopy == 'function';\n  })();\n\n  // KEY NAMING\n\n  var keyNames = {3: \"Enter\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\n                  19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\n                  36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\n                  46: \"Delete\", 59: \";\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\", 109: \"-\", 107: \"=\", 127: \"Delete\",\n                  186: \";\", 187: \"=\", 188: \",\", 189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\",\n                  221: \"]\", 222: \"'\", 63276: \"PageUp\", 63277: \"PageDown\", 63275: \"End\", 63273: \"Home\",\n                  63234: \"Left\", 63232: \"Up\", 63235: \"Right\", 63233: \"Down\", 63302: \"Insert\", 63272: \"Delete\"};\n  CodeMirror.keyNames = keyNames;\n  (function() {\n    // Number keys\n    for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);\n    // Alphabetic keys\n    for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);\n    // Function keys\n    for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = \"F\" + i;\n  })();\n\n  // BIDI HELPERS\n\n  function iterateBidiSections(order, from, to, f) {\n    if (!order) return f(from, to, \"ltr\");\n    var found = false;\n    for (var i = 0; i < order.length; ++i) {\n      var part = order[i];\n      if (part.from < to && part.to > from || from == to && part.to == from) {\n        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? \"rtl\" : \"ltr\");\n        found = true;\n      }\n    }\n    if (!found) f(from, to, \"ltr\");\n  }\n\n  function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }\n  function bidiRight(part) { return part.level % 2 ? part.from : part.to; }\n\n  function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }\n  function lineRight(line) {\n    var order = getOrder(line);\n    if (!order) return line.text.length;\n    return bidiRight(lst(order));\n  }\n\n  function lineStart(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLine(cm.doc, line);\n    if (visual != line) lineN = lineNo(visual);\n    var order = getOrder(visual);\n    var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);\n    return Pos(lineN, ch);\n  }\n  function lineEnd(cm, lineN) {\n    var merged, line;\n    while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))\n      lineN = merged.find().to.line;\n    var order = getOrder(line);\n    var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);\n    return Pos(lineN, ch);\n  }\n\n  function compareBidiLevel(order, a, b) {\n    var linedir = order[0].level;\n    if (a == linedir) return true;\n    if (b == linedir) return false;\n    return a < b;\n  }\n  var bidiOther;\n  function getBidiPartAt(order, pos) {\n    for (var i = 0, found; i < order.length; ++i) {\n      var cur = order[i];\n      if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; }\n      if (cur.from == pos || cur.to == pos) {\n        if (found == null) {\n          found = i;\n        } else if (compareBidiLevel(order, cur.level, order[found].level)) {\n          bidiOther = found;\n          return i;\n        } else {\n          bidiOther = i;\n          return found;\n        }\n      }\n    }\n    bidiOther = null;\n    return found;\n  }\n\n  function moveInLine(line, pos, dir, byUnit) {\n    if (!byUnit) return pos + dir;\n    do pos += dir;\n    while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));\n    return pos;\n  }\n\n  // This is somewhat involved. It is needed in order to move\n  // 'visually' through bi-directional text -- i.e., pressing left\n  // should make the cursor go left, even when in RTL text. The\n  // tricky part is the 'jumps', where RTL and LTR text touch each\n  // other. This often requires the cursor offset to move more than\n  // one unit, in order to visually move one unit.\n  function moveVisually(line, start, dir, byUnit) {\n    var bidi = getOrder(line);\n    if (!bidi) return moveLogically(line, start, dir, byUnit);\n    var pos = getBidiPartAt(bidi, start), part = bidi[pos];\n    var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);\n\n    for (;;) {\n      if (target > part.from && target < part.to) return target;\n      if (target == part.from || target == part.to) {\n        if (getBidiPartAt(bidi, target) == pos) return target;\n        part = bidi[pos += dir];\n        return (dir > 0) == part.level % 2 ? part.to : part.from;\n      } else {\n        part = bidi[pos += dir];\n        if (!part) return null;\n        if ((dir > 0) == part.level % 2)\n          target = moveInLine(line, part.to, -1, byUnit);\n        else\n          target = moveInLine(line, part.from, 1, byUnit);\n      }\n    }\n  }\n\n  function moveLogically(line, start, dir, byUnit) {\n    var target = start + dir;\n    if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;\n    return target < 0 || target > line.text.length ? null : target;\n  }\n\n  // Bidirectional ordering algorithm\n  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm\n  // that this (partially) implements.\n\n  // One-char codes used for character types:\n  // L (L):   Left-to-Right\n  // R (R):   Right-to-Left\n  // r (AL):  Right-to-Left Arabic\n  // 1 (EN):  European Number\n  // + (ES):  European Number Separator\n  // % (ET):  European Number Terminator\n  // n (AN):  Arabic Number\n  // , (CS):  Common Number Separator\n  // m (NSM): Non-Spacing Mark\n  // b (BN):  Boundary Neutral\n  // s (B):   Paragraph Separator\n  // t (S):   Segment Separator\n  // w (WS):  Whitespace\n  // N (ON):  Other Neutrals\n\n  // Returns null if characters are ordered as they appear\n  // (left-to-right), or an array of sections ({from, to, level}\n  // objects) in the order in which they occur visually.\n  var bidiOrdering = (function() {\n    // Character types for codepoints 0 to 0xff\n    var lowTypes = \"bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL\";\n    // Character types for codepoints 0x600 to 0x6ff\n    var arabicTypes = \"rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr\";\n    function charType(code) {\n      if (code <= 0xff) return lowTypes.charAt(code);\n      else if (0x590 <= code && code <= 0x5f4) return \"R\";\n      else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);\n      else if (0x700 <= code && code <= 0x8ac) return \"r\";\n      else return \"L\";\n    }\n\n    var bidiRE = /[\\u0590-\\u05f4\\u0600-\\u06ff\\u0700-\\u08ac]/;\n    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;\n    // Browsers seem to always treat the boundaries of block elements as being L.\n    var outerType = \"L\";\n\n    return function(str) {\n      if (!bidiRE.test(str)) return false;\n      var len = str.length, types = [];\n      for (var i = 0, type; i < len; ++i)\n        types.push(type = charType(str.charCodeAt(i)));\n\n      // W1. Examine each non-spacing mark (NSM) in the level run, and\n      // change the type of the NSM to the type of the previous\n      // character. If the NSM is at the start of the level run, it will\n      // get the type of sor.\n      for (var i = 0, prev = outerType; i < len; ++i) {\n        var type = types[i];\n        if (type == \"m\") types[i] = prev;\n        else prev = type;\n      }\n\n      // W2. Search backwards from each instance of a European number\n      // until the first strong type (R, L, AL, or sor) is found. If an\n      // AL is found, change the type of the European number to Arabic\n      // number.\n      // W3. Change all ALs to R.\n      for (var i = 0, cur = outerType; i < len; ++i) {\n        var type = types[i];\n        if (type == \"1\" && cur == \"r\") types[i] = \"n\";\n        else if (isStrong.test(type)) { cur = type; if (type == \"r\") types[i] = \"R\"; }\n      }\n\n      // W4. A single European separator between two European numbers\n      // changes to a European number. A single common separator between\n      // two numbers of the same type changes to that type.\n      for (var i = 1, prev = types[0]; i < len - 1; ++i) {\n        var type = types[i];\n        if (type == \"+\" && prev == \"1\" && types[i+1] == \"1\") types[i] = \"1\";\n        else if (type == \",\" && prev == types[i+1] &&\n                 (prev == \"1\" || prev == \"n\")) types[i] = prev;\n        prev = type;\n      }\n\n      // W5. A sequence of European terminators adjacent to European\n      // numbers changes to all European numbers.\n      // W6. Otherwise, separators and terminators change to Other\n      // Neutral.\n      for (var i = 0; i < len; ++i) {\n        var type = types[i];\n        if (type == \",\") types[i] = \"N\";\n        else if (type == \"%\") {\n          for (var end = i + 1; end < len && types[end] == \"%\"; ++end) {}\n          var replace = (i && types[i-1] == \"!\") || (end < len - 1 && types[end] == \"1\") ? \"1\" : \"N\";\n          for (var j = i; j < end; ++j) types[j] = replace;\n          i = end - 1;\n        }\n      }\n\n      // W7. Search backwards from each instance of a European number\n      // until the first strong type (R, L, or sor) is found. If an L is\n      // found, then change the type of the European number to L.\n      for (var i = 0, cur = outerType; i < len; ++i) {\n        var type = types[i];\n        if (cur == \"L\" && type == \"1\") types[i] = \"L\";\n        else if (isStrong.test(type)) cur = type;\n      }\n\n      // N1. A sequence of neutrals takes the direction of the\n      // surrounding strong text if the text on both sides has the same\n      // direction. European and Arabic numbers act as if they were R in\n      // terms of their influence on neutrals. Start-of-level-run (sor)\n      // and end-of-level-run (eor) are used at level run boundaries.\n      // N2. Any remaining neutrals take the embedding direction.\n      for (var i = 0; i < len; ++i) {\n        if (isNeutral.test(types[i])) {\n          for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}\n          var before = (i ? types[i-1] : outerType) == \"L\";\n          var after = (end < len - 1 ? types[end] : outerType) == \"L\";\n          var replace = before || after ? \"L\" : \"R\";\n          for (var j = i; j < end; ++j) types[j] = replace;\n          i = end - 1;\n        }\n      }\n\n      // Here we depart from the documented algorithm, in order to avoid\n      // building up an actual levels array. Since there are only three\n      // levels (0, 1, 2) in an implementation that doesn't take\n      // explicit embedding into account, we can build up the order on\n      // the fly, without following the level-based algorithm.\n      var order = [], m;\n      for (var i = 0; i < len;) {\n        if (countsAsLeft.test(types[i])) {\n          var start = i;\n          for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}\n          order.push({from: start, to: i, level: 0});\n        } else {\n          var pos = i, at = order.length;\n          for (++i; i < len && types[i] != \"L\"; ++i) {}\n          for (var j = pos; j < i;) {\n            if (countsAsNum.test(types[j])) {\n              if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});\n              var nstart = j;\n              for (++j; j < i && countsAsNum.test(types[j]); ++j) {}\n              order.splice(at, 0, {from: nstart, to: j, level: 2});\n              pos = j;\n            } else ++j;\n          }\n          if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});\n        }\n      }\n      if (order[0].level == 1 && (m = str.match(/^\\s+/))) {\n        order[0].from = m[0].length;\n        order.unshift({from: 0, to: m[0].length, level: 0});\n      }\n      if (lst(order).level == 1 && (m = str.match(/\\s+$/))) {\n        lst(order).to -= m[0].length;\n        order.push({from: len - m[0].length, to: len, level: 0});\n      }\n      if (order[0].level != lst(order).level)\n        order.push({from: len, to: len, level: order[0].level});\n\n      return order;\n    };\n  })();\n\n  // THE END\n\n  CodeMirror.version = \"3.15.0\";\n\n  return CodeMirror;\n})();\n\n(function() {\n  'use strict';\n\n  var listRE = /^(\\s*)([*+-]|(\\d+)\\.)(\\s*)/,\n      unorderedBullets = '*+-';\n\n  CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {\n    var pos = cm.getCursor(),\n        inList = cm.getStateAfter(pos.line).list,\n        match;\n\n    if (!inList || !(match = cm.getLine(pos.line).match(listRE))) {\n      cm.execCommand('newlineAndIndent');\n      return;\n    }\n\n    var indent = match[1], after = match[4];\n    var bullet = unorderedBullets.indexOf(match[2]) >= 0\n      ? match[2]\n      : (parseInt(match[3], 10) + 1) + '.';\n\n    cm.replaceSelection('\\n' + indent + bullet + after, 'end');\n  };\n\n}());\n\nCodeMirror.defineMode(\"xml\", function(config, parserConfig) {\n  var indentUnit = config.indentUnit;\n  var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;\n  var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true;\n\n  var Kludges = parserConfig.htmlMode ? {\n    autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,\n                      'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,\n                      'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,\n                      'track': true, 'wbr': true},\n    implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,\n                       'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,\n                       'th': true, 'tr': true},\n    contextGrabbers: {\n      'dd': {'dd': true, 'dt': true},\n      'dt': {'dd': true, 'dt': true},\n      'li': {'li': true},\n      'option': {'option': true, 'optgroup': true},\n      'optgroup': {'optgroup': true},\n      'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,\n            'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,\n            'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,\n            'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,\n            'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},\n      'rp': {'rp': true, 'rt': true},\n      'rt': {'rp': true, 'rt': true},\n      'tbody': {'tbody': true, 'tfoot': true},\n      'td': {'td': true, 'th': true},\n      'tfoot': {'tbody': true},\n      'th': {'td': true, 'th': true},\n      'thead': {'tbody': true, 'tfoot': true},\n      'tr': {'tr': true}\n    },\n    doNotIndent: {\"pre\": true},\n    allowUnquoted: true,\n    allowMissing: true\n  } : {\n    autoSelfClosers: {},\n    implicitlyClosed: {},\n    contextGrabbers: {},\n    doNotIndent: {},\n    allowUnquoted: false,\n    allowMissing: false\n  };\n  var alignCDATA = parserConfig.alignCDATA;\n\n  // Return variables for tokenizers\n  var tagName, type;\n\n  function inText(stream, state) {\n    function chain(parser) {\n      state.tokenize = parser;\n      return parser(stream, state);\n    }\n\n    var ch = stream.next();\n    if (ch == \"<\") {\n      if (stream.eat(\"!\")) {\n        if (stream.eat(\"[\")) {\n          if (stream.match(\"CDATA[\")) return chain(inBlock(\"atom\", \"]]>\"));\n          else return null;\n        } else if (stream.match(\"--\")) {\n          return chain(inBlock(\"comment\", \"-->\"));\n        } else if (stream.match(\"DOCTYPE\", true, true)) {\n          stream.eatWhile(/[\\w\\._\\-]/);\n          return chain(doctype(1));\n        } else {\n          return null;\n        }\n      } else if (stream.eat(\"?\")) {\n        stream.eatWhile(/[\\w\\._\\-]/);\n        state.tokenize = inBlock(\"meta\", \"?>\");\n        return \"meta\";\n      } else {\n        var isClose = stream.eat(\"/\");\n        tagName = \"\";\n        var c;\n        while ((c = stream.eat(/[^\\s\\u00a0=<>\\\"\\'\\/?]/))) tagName += c;\n        if (!tagName) return \"error\";\n        type = isClose ? \"closeTag\" : \"openTag\";\n        state.tokenize = inTag;\n        return \"tag\";\n      }\n    } else if (ch == \"&\") {\n      var ok;\n      if (stream.eat(\"#\")) {\n        if (stream.eat(\"x\")) {\n          ok = stream.eatWhile(/[a-fA-F\\d]/) && stream.eat(\";\");\n        } else {\n          ok = stream.eatWhile(/[\\d]/) && stream.eat(\";\");\n        }\n      } else {\n        ok = stream.eatWhile(/[\\w\\.\\-:]/) && stream.eat(\";\");\n      }\n      return ok ? \"atom\" : \"error\";\n    } else {\n      stream.eatWhile(/[^&<]/);\n      return null;\n    }\n  }\n\n  function inTag(stream, state) {\n    var ch = stream.next();\n    if (ch == \">\" || (ch == \"/\" && stream.eat(\">\"))) {\n      state.tokenize = inText;\n      type = ch == \">\" ? \"endTag\" : \"selfcloseTag\";\n      return \"tag\";\n    } else if (ch == \"=\") {\n      type = \"equals\";\n      return null;\n    } else if (ch == \"<\") {\n      return \"error\";\n    } else if (/[\\'\\\"]/.test(ch)) {\n      state.tokenize = inAttribute(ch);\n      state.stringStartCol = stream.column();\n      return state.tokenize(stream, state);\n    } else {\n      stream.eatWhile(/[^\\s\\u00a0=<>\\\"\\']/);\n      return \"word\";\n    }\n  }\n\n  function inAttribute(quote) {\n    var closure = function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.next() == quote) {\n          state.tokenize = inTag;\n          break;\n        }\n      }\n      return \"string\";\n    };\n    closure.isInAttribute = true;\n    return closure;\n  }\n\n  function inBlock(style, terminator) {\n    return function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.match(terminator)) {\n          state.tokenize = inText;\n          break;\n        }\n        stream.next();\n      }\n      return style;\n    };\n  }\n  function doctype(depth) {\n    return function(stream, state) {\n      var ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == \"<\") {\n          state.tokenize = doctype(depth + 1);\n          return state.tokenize(stream, state);\n        } else if (ch == \">\") {\n          if (depth == 1) {\n            state.tokenize = inText;\n            break;\n          } else {\n            state.tokenize = doctype(depth - 1);\n            return state.tokenize(stream, state);\n          }\n        }\n      }\n      return \"meta\";\n    };\n  }\n\n  var curState, curStream, setStyle;\n  function pass() {\n    for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);\n  }\n  function cont() {\n    pass.apply(null, arguments);\n    return true;\n  }\n\n  function pushContext(tagName, startOfLine) {\n    var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);\n    curState.context = {\n      prev: curState.context,\n      tagName: tagName,\n      indent: curState.indented,\n      startOfLine: startOfLine,\n      noIndent: noIndent\n    };\n  }\n  function popContext() {\n    if (curState.context) curState.context = curState.context.prev;\n  }\n\n  function element(type) {\n    if (type == \"openTag\") {\n      curState.tagName = tagName;\n      curState.tagStart = curStream.column();\n      return cont(attributes, endtag(curState.startOfLine));\n    } else if (type == \"closeTag\") {\n      var err = false;\n      if (curState.context) {\n        if (curState.context.tagName != tagName) {\n          if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {\n            popContext();\n          }\n          err = !curState.context || curState.context.tagName != tagName;\n        }\n      } else {\n        err = true;\n      }\n      if (err) setStyle = \"error\";\n      return cont(endclosetag(err));\n    }\n    return cont();\n  }\n  function endtag(startOfLine) {\n    return function(type) {\n      var tagName = curState.tagName;\n      curState.tagName = curState.tagStart = null;\n      if (type == \"selfcloseTag\" ||\n          (type == \"endTag\" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {\n        maybePopContext(tagName.toLowerCase());\n        return cont();\n      }\n      if (type == \"endTag\") {\n        maybePopContext(tagName.toLowerCase());\n        pushContext(tagName, startOfLine);\n        return cont();\n      }\n      return cont();\n    };\n  }\n  function endclosetag(err) {\n    return function(type) {\n      if (err) setStyle = \"error\";\n      if (type == \"endTag\") { popContext(); return cont(); }\n      setStyle = \"error\";\n      return cont(arguments.callee);\n    };\n  }\n  function maybePopContext(nextTagName) {\n    var parentTagName;\n    while (true) {\n      if (!curState.context) {\n        return;\n      }\n      parentTagName = curState.context.tagName.toLowerCase();\n      if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||\n          !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {\n        return;\n      }\n      popContext();\n    }\n  }\n\n  function attributes(type) {\n    if (type == \"word\") {setStyle = \"attribute\"; return cont(attribute, attributes);}\n    if (type == \"endTag\" || type == \"selfcloseTag\") return pass();\n    setStyle = \"error\";\n    return cont(attributes);\n  }\n  function attribute(type) {\n    if (type == \"equals\") return cont(attvalue, attributes);\n    if (!Kludges.allowMissing) setStyle = \"error\";\n    else if (type == \"word\") setStyle = \"attribute\";\n    return (type == \"endTag\" || type == \"selfcloseTag\") ? pass() : cont();\n  }\n  function attvalue(type) {\n    if (type == \"string\") return cont(attvaluemaybe);\n    if (type == \"word\" && Kludges.allowUnquoted) {setStyle = \"string\"; return cont();}\n    setStyle = \"error\";\n    return (type == \"endTag\" || type == \"selfCloseTag\") ? pass() : cont();\n  }\n  function attvaluemaybe(type) {\n    if (type == \"string\") return cont(attvaluemaybe);\n    else return pass();\n  }\n\n  return {\n    startState: function() {\n      return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};\n    },\n\n    token: function(stream, state) {\n      if (!state.tagName && stream.sol()) {\n        state.startOfLine = true;\n        state.indented = stream.indentation();\n      }\n      if (stream.eatSpace()) return null;\n\n      setStyle = type = tagName = null;\n      var style = state.tokenize(stream, state);\n      state.type = type;\n      if ((style || type) && style != \"comment\") {\n        curState = state; curStream = stream;\n        while (true) {\n          var comb = state.cc.pop() || element;\n          if (comb(type || style)) break;\n        }\n      }\n      state.startOfLine = false;\n      return setStyle || style;\n    },\n\n    indent: function(state, textAfter, fullLine) {\n      var context = state.context;\n      // Indent multi-line strings (e.g. css).\n      if (state.tokenize.isInAttribute) {\n        return state.stringStartCol + 1;\n      }\n      if ((state.tokenize != inTag && state.tokenize != inText) ||\n          context && context.noIndent)\n        return fullLine ? fullLine.match(/^(\\s*)/)[0].length : 0;\n      // Indent the starts of attribute names.\n      if (state.tagName) {\n        if (multilineTagIndentPastTag)\n          return state.tagStart + state.tagName.length + 2;\n        else\n          return state.tagStart + indentUnit * multilineTagIndentFactor;\n      }\n      if (alignCDATA && /<!\\[CDATA\\[/.test(textAfter)) return 0;\n      if (context && /^<\\//.test(textAfter))\n        context = context.prev;\n      while (context && !context.startOfLine)\n        context = context.prev;\n      if (context) return context.indent + indentUnit;\n      else return 0;\n    },\n\n    electricChars: \"/\",\n    blockCommentStart: \"<!--\",\n    blockCommentEnd: \"-->\",\n\n    configuration: parserConfig.htmlMode ? \"html\" : \"xml\",\n    helperType: parserConfig.htmlMode ? \"html\" : \"xml\"\n  };\n});\n\nCodeMirror.defineMIME(\"text/xml\", \"xml\");\nCodeMirror.defineMIME(\"application/xml\", \"xml\");\nif (!CodeMirror.mimeModes.hasOwnProperty(\"text/html\"))\n  CodeMirror.defineMIME(\"text/html\", {name: \"xml\", htmlMode: true});\n\nCodeMirror.defineMode(\"markdown\", function(cmCfg, modeCfg) {\n\n  var htmlFound = CodeMirror.modes.hasOwnProperty(\"xml\");\n  var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: \"xml\", htmlMode: true} : \"text/plain\");\n  var aliases = {\n    html: \"htmlmixed\",\n    js: \"javascript\",\n    json: \"application/json\",\n    c: \"text/x-csrc\",\n    \"c++\": \"text/x-c++src\",\n    java: \"text/x-java\",\n    csharp: \"text/x-csharp\",\n    \"c#\": \"text/x-csharp\",\n    scala: \"text/x-scala\"\n  };\n\n  var getMode = (function () {\n    var i, modes = {}, mimes = {}, mime;\n\n    var list = [];\n    for (var m in CodeMirror.modes)\n      if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);\n    for (i = 0; i < list.length; i++) {\n      modes[list[i]] = list[i];\n    }\n    var mimesList = [];\n    for (var m in CodeMirror.mimeModes)\n      if (CodeMirror.mimeModes.propertyIsEnumerable(m))\n        mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});\n    for (i = 0; i < mimesList.length; i++) {\n      mime = mimesList[i].mime;\n      mimes[mime] = mimesList[i].mime;\n    }\n\n    for (var a in aliases) {\n      if (aliases[a] in modes || aliases[a] in mimes)\n        modes[a] = aliases[a];\n    }\n\n    return function (lang) {\n      return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;\n    };\n  }());\n\n  // Should underscores in words open/close em/strong?\n  if (modeCfg.underscoresBreakWords === undefined)\n    modeCfg.underscoresBreakWords = true;\n\n  // Turn on fenced code blocks? (\"```\" to start/end)\n  if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;\n\n  // Turn on task lists? (\"- [ ] \" and \"- [x] \")\n  if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;\n\n  var codeDepth = 0;\n\n  var header   = 'header'\n  ,   code     = 'comment'\n  ,   quote1   = 'atom'\n  ,   quote2   = 'number'\n  ,   list1    = 'variable-2'\n  ,   list2    = 'variable-3'\n  ,   list3    = 'keyword'\n  ,   hr       = 'hr'\n  ,   image    = 'tag'\n  ,   linkinline = 'link'\n  ,   linkemail = 'link'\n  ,   linktext = 'link'\n  ,   linkhref = 'string'\n  ,   em       = 'em'\n  ,   strong   = 'strong';\n\n  var hrRE = /^([*\\-=_])(?:\\s*\\1){2,}\\s*$/\n  ,   ulRE = /^[*\\-+]\\s+/\n  ,   olRE = /^[0-9]+\\.\\s+/\n  ,   taskListRE = /^\\[(x| )\\](?=\\s)/ // Must follow ulRE or olRE\n  ,   headerRE = /^(?:\\={1,}|-{1,})$/\n  ,   textRE = /^[^!\\[\\]*_\\\\<>` \"'(]+/;\n\n  function switchInline(stream, state, f) {\n    state.f = state.inline = f;\n    return f(stream, state);\n  }\n\n  function switchBlock(stream, state, f) {\n    state.f = state.block = f;\n    return f(stream, state);\n  }\n\n\n  // Blocks\n\n  function blankLine(state) {\n    // Reset linkTitle state\n    state.linkTitle = false;\n    // Reset EM state\n    state.em = false;\n    // Reset STRONG state\n    state.strong = false;\n    // Reset state.quote\n    state.quote = 0;\n    if (!htmlFound && state.f == htmlBlock) {\n      state.f = inlineNormal;\n      state.block = blockNormal;\n    }\n    // Reset state.trailingSpace\n    state.trailingSpace = 0;\n    state.trailingSpaceNewLine = false;\n    // Mark this line as blank\n    state.thisLineHasContent = false;\n    return null;\n  }\n\n  function blockNormal(stream, state) {\n\n    var prevLineIsList = (state.list !== false);\n    if (state.list !== false && state.indentationDiff >= 0) { // Continued list\n      if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block\n        state.indentation -= state.indentationDiff;\n      }\n      state.list = null;\n    } else if (state.list !== false && state.indentation > 0) {\n      state.list = null;\n      state.listDepth = Math.floor(state.indentation / 4);\n    } else if (state.list !== false) { // No longer a list\n      state.list = false;\n      state.listDepth = 0;\n    }\n\n    if (state.indentationDiff >= 4) {\n      state.indentation -= 4;\n      stream.skipToEnd();\n      return code;\n    } else if (stream.eatSpace()) {\n      return null;\n    } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) {\n      state.header = true;\n    } else if (stream.eat('>')) {\n      state.indentation++;\n      state.quote = 1;\n      stream.eatSpace();\n      while (stream.eat('>')) {\n        stream.eatSpace();\n        state.quote++;\n      }\n    } else if (stream.peek() === '[') {\n      return switchInline(stream, state, footnoteLink);\n    } else if (stream.match(hrRE, true)) {\n      return hr;\n    } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) {\n      state.indentation += 4;\n      state.list = true;\n      state.listDepth++;\n      if (modeCfg.taskLists && stream.match(taskListRE, false)) {\n        state.taskList = true;\n      }\n    } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\\w+#]*)/, true)) {\n      // try switching mode\n      state.localMode = getMode(RegExp.$1);\n      if (state.localMode) state.localState = state.localMode.startState();\n      switchBlock(stream, state, local);\n      return code;\n    }\n\n    return switchInline(stream, state, state.inline);\n  }\n\n  function htmlBlock(stream, state) {\n    var style = htmlMode.token(stream, state.htmlState);\n    if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {\n      state.f = inlineNormal;\n      state.block = blockNormal;\n    }\n    if (state.md_inside && stream.current().indexOf(\">\")!=-1) {\n      state.f = inlineNormal;\n      state.block = blockNormal;\n      state.htmlState.context = undefined;\n    }\n    return style;\n  }\n\n  function local(stream, state) {\n    if (stream.sol() && stream.match(/^```/, true)) {\n      state.localMode = state.localState = null;\n      state.f = inlineNormal;\n      state.block = blockNormal;\n      return code;\n    } else if (state.localMode) {\n      return state.localMode.token(stream, state.localState);\n    } else {\n      stream.skipToEnd();\n      return code;\n    }\n  }\n\n  // Inline\n  function getType(state) {\n    var styles = [];\n\n    if (state.taskOpen) { return \"meta\"; }\n    if (state.taskClosed) { return \"property\"; }\n\n    if (state.strong) { styles.push(strong); }\n    if (state.em) { styles.push(em); }\n\n    if (state.linkText) { styles.push(linktext); }\n\n    if (state.code) { styles.push(code); }\n\n    if (state.header) { styles.push(header); }\n    if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); }\n    if (state.list !== false) {\n      var listMod = (state.listDepth - 1) % 3;\n      if (!listMod) {\n        styles.push(list1);\n      } else if (listMod === 1) {\n        styles.push(list2);\n      } else {\n        styles.push(list3);\n      }\n    }\n\n    if (state.trailingSpaceNewLine) {\n      styles.push(\"trailing-space-new-line\");\n    } else if (state.trailingSpace) {\n      styles.push(\"trailing-space-\" + (state.trailingSpace % 2 ? \"a\" : \"b\"));\n    }\n\n    return styles.length ? styles.join(' ') : null;\n  }\n\n  function handleText(stream, state) {\n    if (stream.match(textRE, true)) {\n      return getType(state);\n    }\n    return undefined;\n  }\n\n  function inlineNormal(stream, state) {\n    var style = state.text(stream, state);\n    if (typeof style !== 'undefined')\n      return style;\n\n    if (state.list) { // List marker (*, +, -, 1., etc)\n      state.list = null;\n      return getType(state);\n    }\n\n    if (state.taskList) {\n      var taskOpen = stream.match(taskListRE, true)[1] !== \"x\";\n      if (taskOpen) state.taskOpen = true;\n      else state.taskClosed = true;\n      state.taskList = false;\n      return getType(state);\n    }\n\n    state.taskOpen = false;\n    state.taskClosed = false;\n\n    var ch = stream.next();\n\n    if (ch === '\\\\') {\n      stream.next();\n      return getType(state);\n    }\n\n    // Matches link titles present on next line\n    if (state.linkTitle) {\n      state.linkTitle = false;\n      var matchCh = ch;\n      if (ch === '(') {\n        matchCh = ')';\n      }\n      matchCh = (matchCh+'').replace(/([.?*+^$[\\]\\\\(){}|-])/g, \"\\\\$1\");\n      var regex = '^\\\\s*(?:[^' + matchCh + '\\\\\\\\]+|\\\\\\\\\\\\\\\\|\\\\\\\\.)' + matchCh;\n      if (stream.match(new RegExp(regex), true)) {\n        return linkhref;\n      }\n    }\n\n    // If this block is changed, it may need to be updated in GFM mode\n    if (ch === '`') {\n      var t = getType(state);\n      var before = stream.pos;\n      stream.eatWhile('`');\n      var difference = 1 + stream.pos - before;\n      if (!state.code) {\n        codeDepth = difference;\n        state.code = true;\n        return getType(state);\n      } else {\n        if (difference === codeDepth) { // Must be exact\n          state.code = false;\n          return t;\n        }\n        return getType(state);\n      }\n    } else if (state.code) {\n      return getType(state);\n    }\n\n    if (ch === '!' && stream.match(/\\[[^\\]]*\\] ?(?:\\(|\\[)/, false)) {\n      stream.match(/\\[[^\\]]*\\]/);\n      state.inline = state.f = linkHref;\n      return image;\n    }\n\n    if (ch === '[' && stream.match(/.*\\](\\(| ?\\[)/, false)) {\n      state.linkText = true;\n      return getType(state);\n    }\n\n    if (ch === ']' && state.linkText) {\n      var type = getType(state);\n      state.linkText = false;\n      state.inline = state.f = linkHref;\n      return type;\n    }\n\n    if (ch === '<' && stream.match(/^(https?|ftps?):\\/\\/(?:[^\\\\>]|\\\\.)+>/, false)) {\n      return switchInline(stream, state, inlineElement(linkinline, '>'));\n    }\n\n    if (ch === '<' && stream.match(/^[^> \\\\]+@(?:[^\\\\>]|\\\\.)+>/, false)) {\n      return switchInline(stream, state, inlineElement(linkemail, '>'));\n    }\n\n    if (ch === '<' && stream.match(/^\\w/, false)) {\n      if (stream.string.indexOf(\">\")!=-1) {\n        var atts = stream.string.substring(1,stream.string.indexOf(\">\"));\n        if (/markdown\\s*=\\s*('|\"){0,1}1('|\"){0,1}/.test(atts)) {\n          state.md_inside = true;\n        }\n      }\n      stream.backUp(1);\n      return switchBlock(stream, state, htmlBlock);\n    }\n\n    if (ch === '<' && stream.match(/^\\/\\w*?>/)) {\n      state.md_inside = false;\n      return \"tag\";\n    }\n\n    var ignoreUnderscore = false;\n    if (!modeCfg.underscoresBreakWords) {\n      if (ch === '_' && stream.peek() !== '_' && stream.match(/(\\w)/, false)) {\n        var prevPos = stream.pos - 2;\n        if (prevPos >= 0) {\n          var prevCh = stream.string.charAt(prevPos);\n          if (prevCh !== '_' && prevCh.match(/(\\w)/, false)) {\n            ignoreUnderscore = true;\n          }\n        }\n      }\n    }\n    var t = getType(state);\n    if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {\n      if (state.strong === ch && stream.eat(ch)) { // Remove STRONG\n        state.strong = false;\n        return t;\n      } else if (!state.strong && stream.eat(ch)) { // Add STRONG\n        state.strong = ch;\n        return getType(state);\n      } else if (state.em === ch) { // Remove EM\n        state.em = false;\n        return t;\n      } else if (!state.em) { // Add EM\n        state.em = ch;\n        return getType(state);\n      }\n    } else if (ch === ' ') {\n      if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces\n        if (stream.peek() === ' ') { // Surrounded by spaces, ignore\n          return getType(state);\n        } else { // Not surrounded by spaces, back up pointer\n          stream.backUp(1);\n        }\n      }\n    }\n\n    if (ch === ' ') {\n      if (stream.match(/ +$/, false)) {\n        state.trailingSpace++;\n      } else if (state.trailingSpace) {\n        state.trailingSpaceNewLine = true;\n      }\n    }\n\n    return getType(state);\n  }\n\n  function linkHref(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    var ch = stream.next();\n    if (ch === '(' || ch === '[') {\n      return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));\n    }\n    return 'error';\n  }\n\n  function footnoteLink(stream, state) {\n    if (stream.match(/^[^\\]]*\\]:/, true)) {\n      state.f = footnoteUrl;\n      return linktext;\n    }\n    return switchInline(stream, state, inlineNormal);\n  }\n\n  function footnoteUrl(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    // Match URL\n    stream.match(/^[^\\s]+/, true);\n    // Check for link title\n    if (stream.peek() === undefined) { // End of line, set flag to check next line\n      state.linkTitle = true;\n    } else { // More content on line, check if link title\n      stream.match(/^(?:\\s+(?:\"(?:[^\"\\\\]|\\\\\\\\|\\\\.)+\"|'(?:[^'\\\\]|\\\\\\\\|\\\\.)+'|\\((?:[^)\\\\]|\\\\\\\\|\\\\.)+\\)))?/, true);\n    }\n    state.f = state.inline = inlineNormal;\n    return linkhref;\n  }\n\n  var savedInlineRE = [];\n  function inlineRE(endChar) {\n    if (!savedInlineRE[endChar]) {\n      // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)\n      endChar = (endChar+'').replace(/([.?*+^$[\\]\\\\(){}|-])/g, \"\\\\$1\");\n      // Match any non-endChar, escaped character, as well as the closing\n      // endChar.\n      savedInlineRE[endChar] = new RegExp('^(?:[^\\\\\\\\]|\\\\\\\\.)*?(' + endChar + ')');\n    }\n    return savedInlineRE[endChar];\n  }\n\n  function inlineElement(type, endChar, next) {\n    next = next || inlineNormal;\n    return function(stream, state) {\n      stream.match(inlineRE(endChar));\n      state.inline = state.f = next;\n      return type;\n    };\n  }\n\n  return {\n    startState: function() {\n      return {\n        f: blockNormal,\n\n        prevLineHasContent: false,\n        thisLineHasContent: false,\n\n        block: blockNormal,\n        htmlState: CodeMirror.startState(htmlMode),\n        indentation: 0,\n\n        inline: inlineNormal,\n        text: handleText,\n\n        linkText: false,\n        linkTitle: false,\n        em: false,\n        strong: false,\n        header: false,\n        taskList: false,\n        list: false,\n        listDepth: 0,\n        quote: 0,\n        trailingSpace: 0,\n        trailingSpaceNewLine: false\n      };\n    },\n\n    copyState: function(s) {\n      return {\n        f: s.f,\n\n        prevLineHasContent: s.prevLineHasContent,\n        thisLineHasContent: s.thisLineHasContent,\n\n        block: s.block,\n        htmlState: CodeMirror.copyState(htmlMode, s.htmlState),\n        indentation: s.indentation,\n\n        localMode: s.localMode,\n        localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,\n\n        inline: s.inline,\n        text: s.text,\n        linkTitle: s.linkTitle,\n        em: s.em,\n        strong: s.strong,\n        header: s.header,\n        taskList: s.taskList,\n        list: s.list,\n        listDepth: s.listDepth,\n        quote: s.quote,\n        trailingSpace: s.trailingSpace,\n        trailingSpaceNewLine: s.trailingSpaceNewLine,\n        md_inside: s.md_inside\n      };\n    },\n\n    token: function(stream, state) {\n      if (stream.sol()) {\n        if (stream.match(/^\\s*$/, true)) {\n          state.prevLineHasContent = false;\n          return blankLine(state);\n        } else {\n          state.prevLineHasContent = state.thisLineHasContent;\n          state.thisLineHasContent = true;\n        }\n\n        // Reset state.header\n        state.header = false;\n\n        // Reset state.taskList\n        state.taskList = false;\n\n        // Reset state.code\n        state.code = false;\n\n        // Reset state.trailingSpace\n        state.trailingSpace = 0;\n        state.trailingSpaceNewLine = false;\n\n        state.f = state.block;\n        var indentation = stream.match(/^\\s*/, true)[0].replace(/\\t/g, '    ').length;\n        var difference = Math.floor((indentation - state.indentation) / 4) * 4;\n        if (difference > 4) difference = 4;\n        var adjustedIndentation = state.indentation + difference;\n        state.indentationDiff = adjustedIndentation - state.indentation;\n        state.indentation = adjustedIndentation;\n        if (indentation > 0) return null;\n      }\n      return state.f(stream, state);\n    },\n\n    blankLine: blankLine,\n\n    getType: getType\n  };\n\n}, \"xml\");\n\nCodeMirror.defineMIME(\"text/x-markdown\", \"markdown\");\n\n\nvar isMac = /Mac/.test(navigator.platform);\n\nvar shortcuts = {\n  'Cmd-B': toggleBold,\n  'Cmd-I': toggleItalic,\n  'Cmd-K': drawLink,\n  'Cmd-Alt-I': drawImage,\n  \"Cmd-'\": toggleBlockquote,\n  'Cmd-Alt-L': toggleOrderedList,\n  'Cmd-L': toggleUnOrderedList\n};\n\n\n/**\n * Fix shortcut. Mac use Command, others use Ctrl.\n */\nfunction fixShortcut(name) {\n  if (isMac) {\n    name = name.replace('Ctrl', 'Cmd');\n  } else {\n    name = name.replace('Cmd', 'Ctrl');\n  }\n  return name;\n}\n\n\n/**\n * Create icon element for toolbar.\n */\nfunction createIcon(name, options) {\n  options = options || {};\n  var el = document.createElement('a');\n\n  var shortcut = options.shortcut || shortcuts[name];\n  if (shortcut) {\n    shortcut = fixShortcut(shortcut);\n    el.title = shortcut;\n    el.title = el.title.replace('Cmd', '⌘');\n    if (isMac) {\n      el.title = el.title.replace('Alt', '⌥');\n    }\n  }\n\n  el.className = options.className || 'eicon-' + name;\n  return el;\n}\n\nfunction createSep() {\n  el = document.createElement('i');\n  el.className = 'separator';\n  el.innerHTML = '|';\n  return el;\n}\n\n\n/**\n * The state of CodeMirror at the given position.\n */\nfunction getState(cm, pos) {\n  pos = pos || cm.getCursor('start');\n  var stat = cm.getTokenAt(pos);\n  if (!stat.type) return {};\n\n  var types = stat.type.split(' ');\n\n  var ret = {}, data, text;\n  for (var i = 0; i < types.length; i++) {\n    data = types[i];\n    if (data === 'strong') {\n      ret.bold = true;\n    } else if (data === 'variable-2') {\n      text = cm.getLine(pos.line);\n      if (/^\\s*\\d+\\.\\s/.test(text)) {\n        ret['ordered-list'] = true;\n      } else {\n        ret['unordered-list'] = true;\n      }\n    } else if (data === 'atom') {\n      ret.quote = true;\n    } else if (data === 'em') {\n      ret.italic = true;\n    }\n  }\n  return ret;\n}\n\n\n/**\n * Toggle full screen of the editor.\n */\nfunction toggleFullScreen(editor) {\n  var el = editor.codemirror.getWrapperElement();\n\n  // https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode\n  var doc = document;\n  var isFull = doc.fullScreen || doc.mozFullScreen || doc.webkitFullScreen;\n  var request = function() {\n    if (el.requestFullScreen) {\n      el.requestFullScreen();\n    } else if (el.mozRequestFullScreen) {\n      el.mozRequestFullScreen();\n    } else if (el.webkitRequestFullScreen) {\n      el.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);\n    }\n  };\n  var cancel = function() {\n    if (doc.cancelFullScreen) {\n      doc.cancelFullScreen();\n    } else if (doc.mozCancelFullScreen) {\n      doc.mozCancelFullScreen();\n    } else if (doc.webkitCancelFullScreen) {\n      doc.webkitCancelFullScreen();\n    }\n  };\n  if (!isFull) {\n    request();\n  } else if (cancel) {\n    cancel();\n  }\n}\n\n\n/**\n * Action for toggling bold.\n */\nfunction toggleBold(editor) {\n  var cm = editor.codemirror;\n  var stat = getState(cm);\n\n  var text;\n  var start = '**';\n  var end = '**';\n\n  var startPoint = cm.getCursor('start');\n  var endPoint = cm.getCursor('end');\n  if (stat.bold) {\n    text = cm.getLine(startPoint.line);\n    start = text.slice(0, startPoint.ch);\n    end = text.slice(startPoint.ch);\n\n    start = start.replace(/^(.*)?(\\*|\\_){2}(\\S+.*)?$/, '$1$3');\n    end = end.replace(/^(.*\\S+)?(\\*|\\_){2}(\\s+.*)?$/, '$1$3');\n    startPoint.ch -= 2;\n    endPoint.ch -= 2;\n    cm.setLine(startPoint.line, start + end);\n  } else {\n    text = cm.getSelection();\n    cm.replaceSelection(start + text + end);\n\n    startPoint.ch += 2;\n    endPoint.ch += 2;\n  }\n  cm.setSelection(startPoint, endPoint);\n  cm.focus();\n}\n\n\n/**\n * Action for toggling italic.\n */\nfunction toggleItalic(editor) {\n  var cm = editor.codemirror;\n  var stat = getState(cm);\n\n  var text;\n  var start = '*';\n  var end = '*';\n\n  var startPoint = cm.getCursor('start');\n  var endPoint = cm.getCursor('end');\n  if (stat.italic) {\n    text = cm.getLine(startPoint.line);\n    start = text.slice(0, startPoint.ch);\n    end = text.slice(startPoint.ch);\n\n    start = start.replace(/^(.*)?(\\*|\\_)(\\S+.*)?$/, '$1$3');\n    end = end.replace(/^(.*\\S+)?(\\*|\\_)(\\s+.*)?$/, '$1$3');\n    startPoint.ch -= 1;\n    endPoint.ch -= 1;\n    cm.setLine(startPoint.line, start + end);\n  } else {\n    text = cm.getSelection();\n    cm.replaceSelection(start + text + end);\n\n    startPoint.ch += 1;\n    endPoint.ch += 1;\n  }\n  cm.setSelection(startPoint, endPoint);\n  cm.focus();\n}\n\n\n/**\n * Action for toggling blockquote.\n */\nfunction toggleBlockquote(editor) {\n  var cm = editor.codemirror;\n  _toggleLine(cm, 'quote');\n}\n\n\n/**\n * Action for toggling ul.\n */\nfunction toggleUnOrderedList(editor) {\n  var cm = editor.codemirror;\n  _toggleLine(cm, 'unordered-list');\n}\n\n\n/**\n * Action for toggling ol.\n */\nfunction toggleOrderedList(editor) {\n  var cm = editor.codemirror;\n  _toggleLine(cm, 'ordered-list');\n}\n\n\n/**\n * Action for drawing a link.\n */\nfunction drawLink(editor) {\n  var cm = editor.codemirror;\n  var stat = getState(cm);\n  _replaceSelection(cm, stat.link, '[', '](http://)');\n}\n\n\n/**\n * Action for drawing an img.\n */\nfunction drawImage(editor) {\n  var cm = editor.codemirror;\n  var stat = getState(cm);\n  _replaceSelection(cm, stat.image, '![', '](http://)');\n}\n\n\n/**\n * Undo action.\n */\nfunction undo(editor) {\n  var cm = editor.codemirror;\n  cm.undo();\n  cm.focus();\n}\n\n\n/**\n * Redo action.\n */\nfunction redo(editor) {\n  var cm = editor.codemirror;\n  cm.redo();\n  cm.focus();\n}\n\n/**\n * Preview action.\n */\nfunction togglePreview(editor) {\n  var toolbar = editor.toolbar.preview;\n  var parse = editor.constructor.markdown;\n  var cm = editor.codemirror;\n  var wrapper = cm.getWrapperElement();\n  var preview = wrapper.lastChild;\n  if (!/editor-preview/.test(preview.className)) {\n    preview = document.createElement('div');\n    preview.className = 'editor-preview';\n    wrapper.appendChild(preview);\n  }\n  if (/editor-preview-active/.test(preview.className)) {\n    preview.className = preview.className.replace(\n      /\\s*editor-preview-active\\s*/g, ''\n    );\n    toolbar.className = toolbar.className.replace(/\\s*active\\s*/g, '');\n  } else {\n    /* When the preview button is clicked for the first time,\n     * give some time for the transition from editor.css to fire and the view to slide from right to left,\n     * instead of just appearing.\n     */\n    setTimeout(function() {preview.className += ' editor-preview-active'}, 1);\n    toolbar.className += ' active';\n  }\n  var text = cm.getValue();\n  preview.innerHTML = parse(text);\n}\n\nfunction _replaceSelection(cm, active, start, end) {\n  var text;\n  var startPoint = cm.getCursor('start');\n  var endPoint = cm.getCursor('end');\n  if (active) {\n    text = cm.getLine(startPoint.line);\n    start = text.slice(0, startPoint.ch);\n    end = text.slice(startPoint.ch);\n    cm.setLine(startPoint.line, start + end);\n  } else {\n    text = cm.getSelection();\n    cm.replaceSelection(start + text + end);\n\n    startPoint.ch += start.length;\n    endPoint.ch += start.length;\n  }\n  cm.setSelection(startPoint, endPoint);\n  cm.focus();\n}\n\n\nfunction _toggleLine(cm, name) {\n  var stat = getState(cm);\n  var startPoint = cm.getCursor('start');\n  var endPoint = cm.getCursor('end');\n  var repl = {\n    quote: /^(\\s*)\\>\\s+/,\n    'unordered-list': /^(\\s*)(\\*|\\-|\\+)\\s+/,\n    'ordered-list': /^(\\s*)\\d+\\.\\s+/\n  };\n  var map = {\n    quote: '> ',\n    'unordered-list': '* ',\n    'ordered-list': '1. '\n  };\n  for (var i = startPoint.line; i <= endPoint.line; i++) {\n    (function(i) {\n      var text = cm.getLine(i);\n      if (stat[name]) {\n        text = text.replace(repl[name], '$1');\n      } else {\n        text = map[name] + text;\n      }\n      cm.setLine(i, text);\n    })(i);\n  }\n  cm.focus();\n}\n\n\n/* The right word count in respect for CJK. */\nfunction wordCount(data) {\n  var pattern = /[a-zA-Z0-9_\\u0392-\\u03c9]+|[\\u4E00-\\u9FFF\\u3400-\\u4dbf\\uf900-\\ufaff\\u3040-\\u309f\\uac00-\\ud7af]+/g;\n  var m = data.match(pattern);\n  var count = 0;\n  if( m === null ) return count;\n  for (var i = 0; i < m.length; i++) {\n    if (m[i].charCodeAt(0) >= 0x4E00) {\n      count += m[i].length;\n    } else {\n      count += 1;\n    }\n  }\n  return count;\n}\n\nvar toolbar = [\n  {name: 'bold', action: toggleBold},\n  {name: 'italic', action: toggleItalic},\n  '|',\n\n  {name: 'quote', action: toggleBlockquote},\n  {name: 'unordered-list', action: toggleUnOrderedList},\n  {name: 'ordered-list', action: toggleOrderedList},\n  '|',\n\n  {name: 'link', action: drawLink},\n  {name: 'image', action: drawImage},\n  '|',\n\n  {name: 'info', action: 'http://lab.lepture.com/editor/markdown'},\n  {name: 'preview', action: togglePreview},\n  {name: 'fullscreen', action: toggleFullScreen}\n];\n\n/**\n * Interface of Editor.\n */\nfunction Editor(options) {\n  options = options || {};\n\n  if (options.element) {\n    this.element = options.element;\n  }\n\n  options.toolbar = options.toolbar || Editor.toolbar;\n  // you can customize toolbar with object\n  // [{name: 'bold', shortcut: 'Ctrl-B', className: 'icon-bold'}]\n\n  if (!options.hasOwnProperty('status')) {\n    options.status = ['lines', 'words', 'cursor'];\n  }\n\n  this.options = options;\n\n  // If user has passed an element, it should auto rendered\n  if (this.element) {\n    this.render();\n  }\n}\n\n/**\n * Default toolbar elements.\n */\nEditor.toolbar = toolbar;\n\n/**\n * Default markdown render.\n */\nEditor.markdown = function(text) {\n  if (window.markdowniter) {\n    // use marked as markdown parser\n    return markdowniter.render(text);\n  }\n};\n\n/**\n * Render editor to the given element.\n */\nEditor.prototype.render = function(el) {\n  if (!el) {\n    el = this.element || document.getElementsByTagName('textarea')[0];\n  }\n\n  if (this._rendered && this._rendered === el) {\n    // Already rendered.\n    return;\n  }\n\n  this.element = el;\n  var options = this.options;\n\n  var self = this;\n  var keyMaps = {};\n\n  for (var key in shortcuts) {\n    (function(key) {\n      keyMaps[fixShortcut(key)] = function(cm) {\n        shortcuts[key](self);\n      };\n    })(key);\n  }\n\n  keyMaps[\"Enter\"] = \"newlineAndIndentContinueMarkdownList\";\n\n  this.codemirror = CodeMirror.fromTextArea(el, {\n    mode: 'markdown',\n    theme: 'paper',\n    indentWithTabs: true,\n    lineNumbers: false,\n    extraKeys: keyMaps\n  });\n\n  if (options.toolbar !== false) {\n    this.createToolbar();\n  }\n  if (options.status !== false) {\n    this.createStatusbar();\n  }\n\n  this._rendered = this.element;\n};\n\nEditor.prototype.createToolbar = function(items) {\n  items = items || this.options.toolbar;\n\n  if (!items || items.length === 0) {\n    return;\n  }\n\n  var bar = document.createElement('div');\n  bar.className = 'editor-toolbar';\n\n  var self = this;\n\n  var el;\n  self.toolbar = {};\n\n  for (var i = 0; i < items.length; i++) {\n    (function(item) {\n      var el;\n      if (item.name) {\n        el = createIcon(item.name, item);\n      } else if (item === '|') {\n        el = createSep();\n      } else {\n        el = createIcon(item);\n      }\n\n      // bind events, special for info\n      if (item.action) {\n        if (typeof item.action === 'function') {\n          el.onclick = function(e) {\n            item.action(self);\n          };\n        } else if (typeof item.action === 'string') {\n          el.href = item.action;\n          el.target = '_blank';\n        }\n      }\n      self.toolbar[item.name || item] = el;\n      bar.appendChild(el);\n    })(items[i]);\n  }\n\n  var cm = this.codemirror;\n  cm.on('cursorActivity', function() {\n    var stat = getState(cm);\n\n    for (var key in self.toolbar) {\n      (function(key) {\n        var el = self.toolbar[key];\n        if (stat[key]) {\n          el.className += ' active';\n        } else {\n          el.className = el.className.replace(/\\s*active\\s*/g, '');\n        }\n      })(key);\n    }\n  });\n\n  var cmWrapper = cm.getWrapperElement();\n  cmWrapper.parentNode.insertBefore(bar, cmWrapper);\n  return bar;\n};\n\nEditor.prototype.createStatusbar = function(status) {\n  status = status || this.options.status;\n\n  if (!status || status.length === 0) return;\n\n  var bar = document.createElement('div');\n  bar.className = 'editor-statusbar';\n\n  var pos, cm = this.codemirror;\n  for (var i = 0; i < status.length; i++) {\n    (function(name) {\n      var el = document.createElement('span');\n      el.className = name;\n      if (name === 'words') {\n        el.innerHTML = '0';\n        cm.on('update', function() {\n          el.innerHTML = wordCount(cm.getValue());\n        });\n      } else if (name === 'lines') {\n        el.innerHTML = '0';\n        cm.on('update', function() {\n          el.innerHTML = cm.lineCount();\n        });\n      } else if (name === 'cursor') {\n        el.innerHTML = '0:0';\n        cm.on('cursorActivity', function() {\n          pos = cm.getCursor();\n          el.innerHTML = pos.line + ':' + pos.ch;\n        });\n      }\n      bar.appendChild(el);\n    })(status[i]);\n  }\n  var cmWrapper = this.codemirror.getWrapperElement();\n  cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);\n  return bar;\n};\n\n\n/**\n * Bind static methods for exports.\n */\nEditor.toggleBold = toggleBold;\nEditor.toggleItalic = toggleItalic;\nEditor.toggleBlockquote = toggleBlockquote;\nEditor.toggleUnOrderedList = toggleUnOrderedList;\nEditor.toggleOrderedList = toggleOrderedList;\nEditor.drawLink = drawLink;\nEditor.drawImage = drawImage;\nEditor.undo = undo;\nEditor.redo = redo;\nEditor.toggleFullScreen = toggleFullScreen;\n\n/**\n * Bind instance methods for exports.\n */\nEditor.prototype.toggleBold = function() {\n  toggleBold(this);\n};\nEditor.prototype.toggleItalic = function() {\n  toggleItalic(this);\n};\nEditor.prototype.toggleBlockquote = function() {\n  toggleBlockquote(this);\n};\nEditor.prototype.toggleUnOrderedList = function() {\n  toggleUnOrderedList(this);\n};\nEditor.prototype.toggleOrderedList = function() {\n  toggleOrderedList(this);\n};\nEditor.prototype.drawLink = function() {\n  drawLink(this);\n};\nEditor.prototype.drawImage = function() {\n  drawImage(this);\n};\nEditor.prototype.undo = function() {\n  undo(this);\n};\nEditor.prototype.redo = function() {\n  redo(this);\n};\nEditor.prototype.toggleFullScreen = function() {\n  toggleFullScreen(this);\n};\n\nglobal.Editor = Editor;\nglobal.CodeMirror = CodeMirror;\n})(this);\n"
  },
  {
    "path": "public/libs/editor/ext.js",
    "content": "(function(Editor, markdownit, WebUploader){\n\n    function _replaceSelection(cm, active, start, end) {\n        var text;\n        var startPoint = cm.getCursor('start');\n        var endPoint = cm.getCursor('end');\n        var end = end || '';\n        if (active) {\n            text = cm.getLine(startPoint.line);\n            start = text.slice(0, startPoint.ch);\n            end = text.slice(startPoint.ch);\n            cm.setLine(startPoint.line, start + end);\n        } else {\n            text = cm.getSelection();\n            cm.replaceSelection(start + text + end);\n\n            startPoint.ch += start.length;\n            endPoint.ch += start.length;\n        }\n        cm.setSelection(startPoint, endPoint);\n        cm.focus();\n    }\n\n    /**\n     * The state of CodeMirror at the given position.\n     */\n    function getState(cm, pos) {\n        pos = pos || cm.getCursor('start');\n        var stat = cm.getTokenAt(pos);\n        if (!stat.type) return {};\n\n        var types = stat.type.split(' ');\n\n        var ret = {}, data, text;\n        for (var i = 0; i < types.length; i++) {\n            data = types[i];\n            if (data === 'strong') {\n            ret.bold = true;\n            } else if (data === 'variable-2') {\n            text = cm.getLine(pos.line);\n            if (/^\\s*\\d+\\.\\s/.test(text)) {\n                ret['ordered-list'] = true;\n            } else {\n                ret['unordered-list'] = true;\n            }\n            } else if (data === 'atom') {\n            ret.quote = true;\n            } else if (data === 'em') {\n            ret.italic = true;\n            }\n        }\n        return ret;\n    }\n\n    // Set default options\n    var md = new markdownit();\n\n    md.set({\n      html:         false,        // Enable HTML tags in source\n      xhtmlOut:     false,        // Use '/' to close single tags (<br />)\n      breaks:       true,        // Convert '\\n' in paragraphs into <br>\n      langPrefix:   'language-',  // CSS language prefix for fenced blocks\n      linkify:      false,        // Autoconvert URL-like text to links\n      typographer:  false,        // Enable smartypants and other sweet transforms\n    });\n\n    window.markdowniter = md;\n\n    var toolbar = Editor.toolbar;\n\n    var replaceTool = function(name, callback){\n        for(var i=0, len=toolbar.length; i<len; i++){\n            var v = toolbar[i];\n            if(typeof(v) !== 'string' && v.name === name){\n                v.action = callback;\n                break;\n            }\n        }\n    };\n\n    var $body = $('body');\n\n    //添加链接工具\n    var ToolLink = function(){\n        var self = this;\n        this.$win = $([\n            '<div class=\"modal hide fade\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"editorToolImageTitle\" aria-hidden=\"true\">',\n                '<div class=\"modal-header\">',\n                    '<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">×</button>',\n                    '<h3 id=\"editorToolImageTitle\">添加链接</h3>',\n                '</div>',\n                '<div class=\"modal-body\">',\n                    '<form class=\"form-horizontal\">',\n                        '<div class=\"control-group\">',\n                            '<label class=\"control-label\">标题</label>',\n                            '<div class=\"controls\">',\n                                '<input type=\"text\" name=\"title\" placeholder=\"Title\">',\n                            '</div>',\n                        '</div>',\n                        '<div class=\"control-group\">',\n                            '<label class=\"control-label\">链接</label>',\n                            '<div class=\"controls\">',\n                                '<input type=\"text\" name=\"link\" value=\"http://\" placeholder=\"Link\">',\n                            '</div>',\n                        '</div>',\n                    '</form>',\n                '</div>',\n                '<div class=\"modal-footer\">',\n                    '<button class=\"btn btn-primary\" role=\"save\">确定</button>',\n                '</div>',\n            '</div>'\n        ].join('')).appendTo($body);\n\n        this.$win.on('click', '[role=save]', function(){\n            self.$win.find('form').submit();\n        }).on('submit', 'form', function(){\n            var $el = $(this);\n            var title = $el.find('[name=title]').val();\n            var link = $el.find('[name=link]').val();\n\n            self.$win.modal('hide');\n\n            var cm = self.editor.codemirror;\n            var stat = getState(cm);\n            _replaceSelection(cm, stat.link, '['+ title +']('+ link +')');\n\n            $el.find('[name=title]').val('');\n            $el.find('[name=link]').val('http://');\n\n            return false;\n        });\n    };\n\n    ToolLink.prototype.bind = function(editor){\n        this.editor = editor;\n        this.$win.modal('show');\n    };\n\n    //图片上传工具\n    var ToolImage = function(){\n        var self = this;\n        this.$win = $([\n            '<div class=\"modal hide fade\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"editorToolImageTitle\" aria-hidden=\"true\">',\n                '<div class=\"modal-header\">',\n                    '<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">×</button>',\n                    '<h3 id=\"editorToolImageTitle\">图片</h3>',\n                '</div>',\n                '<div class=\"modal-body\">',\n                    '<div class=\"upload-img\">',\n                        '<div class=\"button\">上传图片</div>',\n                        '<span class=\"tip\"></span>',\n                        '<div class=\"alert alert-error hide\"></div>',\n                    '</div>',\n                '</div>',\n            '</div>'\n        ].join('')).appendTo($body);\n\n        this.$upload = this.$win.find('.upload-img').css({\n            height: 50,\n            padding: '60px 0',\n            textAlign: 'center',\n            border: '4px dashed#ddd'\n        });\n\n        this.$uploadBtn = this.$upload.find('.button').css({\n            width: 86,\n            height: 40,\n            margin: '0 auto'\n        });\n\n        this.$uploadTip = this.$upload.find('.tip').hide();\n\n        this.file = false;\n        var _csrf = $('[name=_csrf]').val();\n\n        this.uploader = WebUploader.create({\n            swf: '/public/libs/webuploader/Uploader.swf',\n            server: '/upload?_csrf=' + _csrf,\n            pick: this.$uploadBtn[0],\n            paste: document.body,\n            dnd: this.$upload[0],\n            auto: true,\n            fileSingleSizeLimit: 1 * 1024 * 1024,\n            //sendAsBinary: true,\n            // 只允许选择图片文件。\n            accept: {\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }\n        });\n\n        this.uploader.on('beforeFileQueued', function(file){\n            if(self.file !== false || !self.editor){\n                return false;\n            }\n            self.showFile(file);\n        });\n\n        this.uploader.on('uploadProgress', function(file, percentage){\n            // console.log(percentage);\n            self.showProgress(file, percentage * 100);\n        });\n\n        this.uploader.on('uploadSuccess', function(file, res){\n            if(res.success){\n                self.$win.modal('hide');\n\n                var cm = self.editor.codemirror;\n                var stat = getState(cm);\n                _replaceSelection(cm, stat.image, '!['+ file.name +']('+ res.url +')');\n\n            }\n            else{\n                self.removeFile();\n                self.showError(res.msg || '服务器走神了，上传失败');\n            }\n        });\n\n        this.uploader.on('uploadComplete', function(file){\n            self.uploader.removeFile(file);\n            self.removeFile();\n        });\n\n        this.uploader.on('error', function(type){\n            self.removeFile();\n            switch(type){\n                case 'Q_EXCEED_SIZE_LIMIT':\n                case 'F_EXCEED_SIZE':\n                    self.showError('文件太大了, 不能超过1MB');\n                    break;\n                case 'Q_TYPE_DENIED':\n                    self.showError('只能上传图片');\n                    break;\n                default:\n                    self.showError('发生未知错误');\n            }\n        });\n\n        this.uploader.on('uploadError', function(){\n            self.removeFile();\n            self.showError('服务器走神了，上传失败');\n        });\n    };\n\n    ToolImage.prototype.removeFile = function(){\n        //var self = this;\n        this.file = false;\n        this.$uploadBtn.show();\n        this.$uploadTip.hide();\n    };\n\n    ToolImage.prototype.showFile = function(file){\n        //var self = this;\n        this.file = file;\n        this.$uploadBtn.hide();\n        this.$uploadTip.html('正在上传: ' + file.name).show();\n        this.hideError();\n    };\n\n    ToolImage.prototype.showError = function(error){\n        this.$upload.find('.alert-error').html(error).show();\n    };\n\n    ToolImage.prototype.hideError = function(error){\n        this.$upload.find('.alert-error').hide();\n    };\n\n    ToolImage.prototype.showProgress = function(file, percentage){\n        this.$uploadTip\n            .html('正在上传: ' + file.name + ' ' + percentage + '%')\n            .show();\n    };\n\n    ToolImage.prototype.bind = function(editor){\n        this.editor = editor;\n        this.$win.modal('show');\n    };\n\n    var toolImage = new ToolImage();\n    var toolLink = new ToolLink();\n\n    replaceTool('image', function(editor){\n        toolImage.bind(editor);\n    });\n    replaceTool('link', function(editor){\n        toolLink.bind(editor);\n    });\n\n    //当编辑器取得焦点时，绑定 toolImage；\n    var createToolbar = Editor.prototype.createToolbar;\n    Editor.prototype.createToolbar = function(items){\n        createToolbar.call(this, items);\n        var self = this;\n        $(self.codemirror.display.input).on('focus', function(){\n            toolImage.editor = self;\n        });\n    };\n\n    //追加内容\n    Editor.prototype.push = function(txt){\n        var cm = this.codemirror;\n        var line = cm.lastLine();\n        cm.setLine(line, cm.getLine(line) + txt);\n    };\n})(window.Editor, window.markdownit, window.WebUploader);\n"
  },
  {
    "path": "public/libs/font-awesome/css/font-awesome.css",
    "content": "/*!\n *  Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */\n/* FONT PATH\n * -------------------------- */\n@font-face {\n  font-family: 'FontAwesome';\n  src: url('/public/libs/font-awesome/fonts/fontawesome-webfont.eot?v=4.2.0');\n  src: url('/public/libs/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('/public/libs/font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('/public/libs/font-awesome/fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('/public/libs/font-awesome/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n.fa {\n  display: inline-block;\n  font: normal normal normal 14px/1 FontAwesome;\n  font-size: inherit;\n  text-rendering: auto;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n/* makes the font 33% larger relative to the icon container */\n.fa-lg {\n  font-size: 1.33333333em;\n  line-height: 0.75em;\n  vertical-align: -15%;\n}\n.fa-2x {\n  font-size: 2em;\n}\n.fa-3x {\n  font-size: 3em;\n}\n.fa-4x {\n  font-size: 4em;\n}\n.fa-5x {\n  font-size: 5em;\n}\n.fa-fw {\n  width: 1.28571429em;\n  text-align: center;\n}\n.fa-ul {\n  padding-left: 0;\n  margin-left: 2.14285714em;\n  list-style-type: none;\n}\n.fa-ul > li {\n  position: relative;\n}\n.fa-li {\n  position: absolute;\n  left: -2.14285714em;\n  width: 2.14285714em;\n  top: 0.14285714em;\n  text-align: center;\n}\n.fa-li.fa-lg {\n  left: -1.85714286em;\n}\n.fa-border {\n  padding: .2em .25em .15em;\n  border: solid 0.08em #eeeeee;\n  border-radius: .1em;\n}\n.pull-right {\n  float: right;\n}\n.pull-left {\n  float: left;\n}\n.fa.pull-left {\n  margin-right: .3em;\n}\n.fa.pull-right {\n  margin-left: .3em;\n}\n.fa-spin {\n  -webkit-animation: fa-spin 2s infinite linear;\n  animation: fa-spin 2s infinite linear;\n}\n@-webkit-keyframes fa-spin {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@keyframes fa-spin {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n.fa-rotate-90 {\n  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);\n  -webkit-transform: rotate(90deg);\n  -ms-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n.fa-rotate-180 {\n  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);\n  -webkit-transform: rotate(180deg);\n  -ms-transform: rotate(180deg);\n  transform: rotate(180deg);\n}\n.fa-rotate-270 {\n  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);\n  -webkit-transform: rotate(270deg);\n  -ms-transform: rotate(270deg);\n  transform: rotate(270deg);\n}\n.fa-flip-horizontal {\n  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);\n  -webkit-transform: scale(-1, 1);\n  -ms-transform: scale(-1, 1);\n  transform: scale(-1, 1);\n}\n.fa-flip-vertical {\n  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);\n  -webkit-transform: scale(1, -1);\n  -ms-transform: scale(1, -1);\n  transform: scale(1, -1);\n}\n:root .fa-rotate-90,\n:root .fa-rotate-180,\n:root .fa-rotate-270,\n:root .fa-flip-horizontal,\n:root .fa-flip-vertical {\n  filter: none;\n}\n.fa-stack {\n  position: relative;\n  display: inline-block;\n  width: 2em;\n  height: 2em;\n  line-height: 2em;\n  vertical-align: middle;\n}\n.fa-stack-1x,\n.fa-stack-2x {\n  position: absolute;\n  left: 0;\n  width: 100%;\n  text-align: center;\n}\n.fa-stack-1x {\n  line-height: inherit;\n}\n.fa-stack-2x {\n  font-size: 2em;\n}\n.fa-inverse {\n  color: #ffffff;\n}\n/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen\n   readers do not read off random characters that represent icons */\n.fa-glass:before {\n  content: \"\\f000\";\n}\n.fa-music:before {\n  content: \"\\f001\";\n}\n.fa-search:before {\n  content: \"\\f002\";\n}\n.fa-envelope-o:before {\n  content: \"\\f003\";\n}\n.fa-heart:before {\n  content: \"\\f004\";\n}\n.fa-star:before {\n  content: \"\\f005\";\n}\n.fa-star-o:before {\n  content: \"\\f006\";\n}\n.fa-user:before {\n  content: \"\\f007\";\n}\n.fa-film:before {\n  content: \"\\f008\";\n}\n.fa-th-large:before {\n  content: \"\\f009\";\n}\n.fa-th:before {\n  content: \"\\f00a\";\n}\n.fa-th-list:before {\n  content: \"\\f00b\";\n}\n.fa-check:before {\n  content: \"\\f00c\";\n}\n.fa-remove:before,\n.fa-close:before,\n.fa-times:before {\n  content: \"\\f00d\";\n}\n.fa-search-plus:before {\n  content: \"\\f00e\";\n}\n.fa-search-minus:before {\n  content: \"\\f010\";\n}\n.fa-power-off:before {\n  content: \"\\f011\";\n}\n.fa-signal:before {\n  content: \"\\f012\";\n}\n.fa-gear:before,\n.fa-cog:before {\n  content: \"\\f013\";\n}\n.fa-trash-o:before {\n  content: \"\\f014\";\n}\n.fa-home:before {\n  content: \"\\f015\";\n}\n.fa-file-o:before {\n  content: \"\\f016\";\n}\n.fa-clock-o:before {\n  content: \"\\f017\";\n}\n.fa-road:before {\n  content: \"\\f018\";\n}\n.fa-download:before {\n  content: \"\\f019\";\n}\n.fa-arrow-circle-o-down:before {\n  content: \"\\f01a\";\n}\n.fa-arrow-circle-o-up:before {\n  content: \"\\f01b\";\n}\n.fa-inbox:before {\n  content: \"\\f01c\";\n}\n.fa-play-circle-o:before {\n  content: \"\\f01d\";\n}\n.fa-rotate-right:before,\n.fa-repeat:before {\n  content: \"\\f01e\";\n}\n.fa-refresh:before {\n  content: \"\\f021\";\n}\n.fa-list-alt:before {\n  content: \"\\f022\";\n}\n.fa-lock:before {\n  content: \"\\f023\";\n}\n.fa-flag:before {\n  content: \"\\f024\";\n}\n.fa-headphones:before {\n  content: \"\\f025\";\n}\n.fa-volume-off:before {\n  content: \"\\f026\";\n}\n.fa-volume-down:before {\n  content: \"\\f027\";\n}\n.fa-volume-up:before {\n  content: \"\\f028\";\n}\n.fa-qrcode:before {\n  content: \"\\f029\";\n}\n.fa-barcode:before {\n  content: \"\\f02a\";\n}\n.fa-tag:before {\n  content: \"\\f02b\";\n}\n.fa-tags:before {\n  content: \"\\f02c\";\n}\n.fa-book:before {\n  content: \"\\f02d\";\n}\n.fa-bookmark:before {\n  content: \"\\f02e\";\n}\n.fa-print:before {\n  content: \"\\f02f\";\n}\n.fa-camera:before {\n  content: \"\\f030\";\n}\n.fa-font:before {\n  content: \"\\f031\";\n}\n.fa-bold:before {\n  content: \"\\f032\";\n}\n.fa-italic:before {\n  content: \"\\f033\";\n}\n.fa-text-height:before {\n  content: \"\\f034\";\n}\n.fa-text-width:before {\n  content: \"\\f035\";\n}\n.fa-align-left:before {\n  content: \"\\f036\";\n}\n.fa-align-center:before {\n  content: \"\\f037\";\n}\n.fa-align-right:before {\n  content: \"\\f038\";\n}\n.fa-align-justify:before {\n  content: \"\\f039\";\n}\n.fa-list:before {\n  content: \"\\f03a\";\n}\n.fa-dedent:before,\n.fa-outdent:before {\n  content: \"\\f03b\";\n}\n.fa-indent:before {\n  content: \"\\f03c\";\n}\n.fa-video-camera:before {\n  content: \"\\f03d\";\n}\n.fa-photo:before,\n.fa-image:before,\n.fa-picture-o:before {\n  content: \"\\f03e\";\n}\n.fa-pencil:before {\n  content: \"\\f040\";\n}\n.fa-map-marker:before {\n  content: \"\\f041\";\n}\n.fa-adjust:before {\n  content: \"\\f042\";\n}\n.fa-tint:before {\n  content: \"\\f043\";\n}\n.fa-edit:before,\n.fa-pencil-square-o:before {\n  content: \"\\f044\";\n}\n.fa-share-square-o:before {\n  content: \"\\f045\";\n}\n.fa-check-square-o:before {\n  content: \"\\f046\";\n}\n.fa-arrows:before {\n  content: \"\\f047\";\n}\n.fa-step-backward:before {\n  content: \"\\f048\";\n}\n.fa-fast-backward:before {\n  content: \"\\f049\";\n}\n.fa-backward:before {\n  content: \"\\f04a\";\n}\n.fa-play:before {\n  content: \"\\f04b\";\n}\n.fa-pause:before {\n  content: \"\\f04c\";\n}\n.fa-stop:before {\n  content: \"\\f04d\";\n}\n.fa-forward:before {\n  content: \"\\f04e\";\n}\n.fa-fast-forward:before {\n  content: \"\\f050\";\n}\n.fa-step-forward:before {\n  content: \"\\f051\";\n}\n.fa-eject:before {\n  content: \"\\f052\";\n}\n.fa-chevron-left:before {\n  content: \"\\f053\";\n}\n.fa-chevron-right:before {\n  content: \"\\f054\";\n}\n.fa-plus-circle:before {\n  content: \"\\f055\";\n}\n.fa-minus-circle:before {\n  content: \"\\f056\";\n}\n.fa-times-circle:before {\n  content: \"\\f057\";\n}\n.fa-check-circle:before {\n  content: \"\\f058\";\n}\n.fa-question-circle:before {\n  content: \"\\f059\";\n}\n.fa-info-circle:before {\n  content: \"\\f05a\";\n}\n.fa-crosshairs:before {\n  content: \"\\f05b\";\n}\n.fa-times-circle-o:before {\n  content: \"\\f05c\";\n}\n.fa-check-circle-o:before {\n  content: \"\\f05d\";\n}\n.fa-ban:before {\n  content: \"\\f05e\";\n}\n.fa-arrow-left:before {\n  content: \"\\f060\";\n}\n.fa-arrow-right:before {\n  content: \"\\f061\";\n}\n.fa-arrow-up:before {\n  content: \"\\f062\";\n}\n.fa-arrow-down:before {\n  content: \"\\f063\";\n}\n.fa-mail-forward:before,\n.fa-share:before {\n  content: \"\\f064\";\n}\n.fa-expand:before {\n  content: \"\\f065\";\n}\n.fa-compress:before {\n  content: \"\\f066\";\n}\n.fa-plus:before {\n  content: \"\\f067\";\n}\n.fa-minus:before {\n  content: \"\\f068\";\n}\n.fa-asterisk:before {\n  content: \"\\f069\";\n}\n.fa-exclamation-circle:before {\n  content: \"\\f06a\";\n}\n.fa-gift:before {\n  content: \"\\f06b\";\n}\n.fa-leaf:before {\n  content: \"\\f06c\";\n}\n.fa-fire:before {\n  content: \"\\f06d\";\n}\n.fa-eye:before {\n  content: \"\\f06e\";\n}\n.fa-eye-slash:before {\n  content: \"\\f070\";\n}\n.fa-warning:before,\n.fa-exclamation-triangle:before {\n  content: \"\\f071\";\n}\n.fa-plane:before {\n  content: \"\\f072\";\n}\n.fa-calendar:before {\n  content: \"\\f073\";\n}\n.fa-random:before {\n  content: \"\\f074\";\n}\n.fa-comment:before {\n  content: \"\\f075\";\n}\n.fa-magnet:before {\n  content: \"\\f076\";\n}\n.fa-chevron-up:before {\n  content: \"\\f077\";\n}\n.fa-chevron-down:before {\n  content: \"\\f078\";\n}\n.fa-retweet:before {\n  content: \"\\f079\";\n}\n.fa-shopping-cart:before {\n  content: \"\\f07a\";\n}\n.fa-folder:before {\n  content: \"\\f07b\";\n}\n.fa-folder-open:before {\n  content: \"\\f07c\";\n}\n.fa-arrows-v:before {\n  content: \"\\f07d\";\n}\n.fa-arrows-h:before {\n  content: \"\\f07e\";\n}\n.fa-bar-chart-o:before,\n.fa-bar-chart:before {\n  content: \"\\f080\";\n}\n.fa-twitter-square:before {\n  content: \"\\f081\";\n}\n.fa-facebook-square:before {\n  content: \"\\f082\";\n}\n.fa-camera-retro:before {\n  content: \"\\f083\";\n}\n.fa-key:before {\n  content: \"\\f084\";\n}\n.fa-gears:before,\n.fa-cogs:before {\n  content: \"\\f085\";\n}\n.fa-comments:before {\n  content: \"\\f086\";\n}\n.fa-thumbs-o-up:before {\n  content: \"\\f087\";\n}\n.fa-thumbs-o-down:before {\n  content: \"\\f088\";\n}\n.fa-star-half:before {\n  content: \"\\f089\";\n}\n.fa-heart-o:before {\n  content: \"\\f08a\";\n}\n.fa-sign-out:before {\n  content: \"\\f08b\";\n}\n.fa-linkedin-square:before {\n  content: \"\\f08c\";\n}\n.fa-thumb-tack:before {\n  content: \"\\f08d\";\n}\n.fa-external-link:before {\n  content: \"\\f08e\";\n}\n.fa-sign-in:before {\n  content: \"\\f090\";\n}\n.fa-trophy:before {\n  content: \"\\f091\";\n}\n.fa-github-square:before {\n  content: \"\\f092\";\n}\n.fa-upload:before {\n  content: \"\\f093\";\n}\n.fa-lemon-o:before {\n  content: \"\\f094\";\n}\n.fa-phone:before {\n  content: \"\\f095\";\n}\n.fa-square-o:before {\n  content: \"\\f096\";\n}\n.fa-bookmark-o:before {\n  content: \"\\f097\";\n}\n.fa-phone-square:before {\n  content: \"\\f098\";\n}\n.fa-twitter:before {\n  content: \"\\f099\";\n}\n.fa-facebook:before {\n  content: \"\\f09a\";\n}\n.fa-github:before {\n  content: \"\\f09b\";\n}\n.fa-unlock:before {\n  content: \"\\f09c\";\n}\n.fa-credit-card:before {\n  content: \"\\f09d\";\n}\n.fa-rss:before {\n  content: \"\\f09e\";\n}\n.fa-hdd-o:before {\n  content: \"\\f0a0\";\n}\n.fa-bullhorn:before {\n  content: \"\\f0a1\";\n}\n.fa-bell:before {\n  content: \"\\f0f3\";\n}\n.fa-certificate:before {\n  content: \"\\f0a3\";\n}\n.fa-hand-o-right:before {\n  content: \"\\f0a4\";\n}\n.fa-hand-o-left:before {\n  content: \"\\f0a5\";\n}\n.fa-hand-o-up:before {\n  content: \"\\f0a6\";\n}\n.fa-hand-o-down:before {\n  content: \"\\f0a7\";\n}\n.fa-arrow-circle-left:before {\n  content: \"\\f0a8\";\n}\n.fa-arrow-circle-right:before {\n  content: \"\\f0a9\";\n}\n.fa-arrow-circle-up:before {\n  content: \"\\f0aa\";\n}\n.fa-arrow-circle-down:before {\n  content: \"\\f0ab\";\n}\n.fa-globe:before {\n  content: \"\\f0ac\";\n}\n.fa-wrench:before {\n  content: \"\\f0ad\";\n}\n.fa-tasks:before {\n  content: \"\\f0ae\";\n}\n.fa-filter:before {\n  content: \"\\f0b0\";\n}\n.fa-briefcase:before {\n  content: \"\\f0b1\";\n}\n.fa-arrows-alt:before {\n  content: \"\\f0b2\";\n}\n.fa-group:before,\n.fa-users:before {\n  content: \"\\f0c0\";\n}\n.fa-chain:before,\n.fa-link:before {\n  content: \"\\f0c1\";\n}\n.fa-cloud:before {\n  content: \"\\f0c2\";\n}\n.fa-flask:before {\n  content: \"\\f0c3\";\n}\n.fa-cut:before,\n.fa-scissors:before {\n  content: \"\\f0c4\";\n}\n.fa-copy:before,\n.fa-files-o:before {\n  content: \"\\f0c5\";\n}\n.fa-paperclip:before {\n  content: \"\\f0c6\";\n}\n.fa-save:before,\n.fa-floppy-o:before {\n  content: \"\\f0c7\";\n}\n.fa-square:before {\n  content: \"\\f0c8\";\n}\n.fa-navicon:before,\n.fa-reorder:before,\n.fa-bars:before {\n  content: \"\\f0c9\";\n}\n.fa-list-ul:before {\n  content: \"\\f0ca\";\n}\n.fa-list-ol:before {\n  content: \"\\f0cb\";\n}\n.fa-strikethrough:before {\n  content: \"\\f0cc\";\n}\n.fa-underline:before {\n  content: \"\\f0cd\";\n}\n.fa-table:before {\n  content: \"\\f0ce\";\n}\n.fa-magic:before {\n  content: \"\\f0d0\";\n}\n.fa-truck:before {\n  content: \"\\f0d1\";\n}\n.fa-pinterest:before {\n  content: \"\\f0d2\";\n}\n.fa-pinterest-square:before {\n  content: \"\\f0d3\";\n}\n.fa-google-plus-square:before {\n  content: \"\\f0d4\";\n}\n.fa-google-plus:before {\n  content: \"\\f0d5\";\n}\n.fa-money:before {\n  content: \"\\f0d6\";\n}\n.fa-caret-down:before {\n  content: \"\\f0d7\";\n}\n.fa-caret-up:before {\n  content: \"\\f0d8\";\n}\n.fa-caret-left:before {\n  content: \"\\f0d9\";\n}\n.fa-caret-right:before {\n  content: \"\\f0da\";\n}\n.fa-columns:before {\n  content: \"\\f0db\";\n}\n.fa-unsorted:before,\n.fa-sort:before {\n  content: \"\\f0dc\";\n}\n.fa-sort-down:before,\n.fa-sort-desc:before {\n  content: \"\\f0dd\";\n}\n.fa-sort-up:before,\n.fa-sort-asc:before {\n  content: \"\\f0de\";\n}\n.fa-envelope:before {\n  content: \"\\f0e0\";\n}\n.fa-linkedin:before {\n  content: \"\\f0e1\";\n}\n.fa-rotate-left:before,\n.fa-undo:before {\n  content: \"\\f0e2\";\n}\n.fa-legal:before,\n.fa-gavel:before {\n  content: \"\\f0e3\";\n}\n.fa-dashboard:before,\n.fa-tachometer:before {\n  content: \"\\f0e4\";\n}\n.fa-comment-o:before {\n  content: \"\\f0e5\";\n}\n.fa-comments-o:before {\n  content: \"\\f0e6\";\n}\n.fa-flash:before,\n.fa-bolt:before {\n  content: \"\\f0e7\";\n}\n.fa-sitemap:before {\n  content: \"\\f0e8\";\n}\n.fa-umbrella:before {\n  content: \"\\f0e9\";\n}\n.fa-paste:before,\n.fa-clipboard:before {\n  content: \"\\f0ea\";\n}\n.fa-lightbulb-o:before {\n  content: \"\\f0eb\";\n}\n.fa-exchange:before {\n  content: \"\\f0ec\";\n}\n.fa-cloud-download:before {\n  content: \"\\f0ed\";\n}\n.fa-cloud-upload:before {\n  content: \"\\f0ee\";\n}\n.fa-user-md:before {\n  content: \"\\f0f0\";\n}\n.fa-stethoscope:before {\n  content: \"\\f0f1\";\n}\n.fa-suitcase:before {\n  content: \"\\f0f2\";\n}\n.fa-bell-o:before {\n  content: \"\\f0a2\";\n}\n.fa-coffee:before {\n  content: \"\\f0f4\";\n}\n.fa-cutlery:before {\n  content: \"\\f0f5\";\n}\n.fa-file-text-o:before {\n  content: \"\\f0f6\";\n}\n.fa-building-o:before {\n  content: \"\\f0f7\";\n}\n.fa-hospital-o:before {\n  content: \"\\f0f8\";\n}\n.fa-ambulance:before {\n  content: \"\\f0f9\";\n}\n.fa-medkit:before {\n  content: \"\\f0fa\";\n}\n.fa-fighter-jet:before {\n  content: \"\\f0fb\";\n}\n.fa-beer:before {\n  content: \"\\f0fc\";\n}\n.fa-h-square:before {\n  content: \"\\f0fd\";\n}\n.fa-plus-square:before {\n  content: \"\\f0fe\";\n}\n.fa-angle-double-left:before {\n  content: \"\\f100\";\n}\n.fa-angle-double-right:before {\n  content: \"\\f101\";\n}\n.fa-angle-double-up:before {\n  content: \"\\f102\";\n}\n.fa-angle-double-down:before {\n  content: \"\\f103\";\n}\n.fa-angle-left:before {\n  content: \"\\f104\";\n}\n.fa-angle-right:before {\n  content: \"\\f105\";\n}\n.fa-angle-up:before {\n  content: \"\\f106\";\n}\n.fa-angle-down:before {\n  content: \"\\f107\";\n}\n.fa-desktop:before {\n  content: \"\\f108\";\n}\n.fa-laptop:before {\n  content: \"\\f109\";\n}\n.fa-tablet:before {\n  content: \"\\f10a\";\n}\n.fa-mobile-phone:before,\n.fa-mobile:before {\n  content: \"\\f10b\";\n}\n.fa-circle-o:before {\n  content: \"\\f10c\";\n}\n.fa-quote-left:before {\n  content: \"\\f10d\";\n}\n.fa-quote-right:before {\n  content: \"\\f10e\";\n}\n.fa-spinner:before {\n  content: \"\\f110\";\n}\n.fa-circle:before {\n  content: \"\\f111\";\n}\n.fa-mail-reply:before,\n.fa-reply:before {\n  content: \"\\f112\";\n}\n.fa-github-alt:before {\n  content: \"\\f113\";\n}\n.fa-folder-o:before {\n  content: \"\\f114\";\n}\n.fa-folder-open-o:before {\n  content: \"\\f115\";\n}\n.fa-smile-o:before {\n  content: \"\\f118\";\n}\n.fa-frown-o:before {\n  content: \"\\f119\";\n}\n.fa-meh-o:before {\n  content: \"\\f11a\";\n}\n.fa-gamepad:before {\n  content: \"\\f11b\";\n}\n.fa-keyboard-o:before {\n  content: \"\\f11c\";\n}\n.fa-flag-o:before {\n  content: \"\\f11d\";\n}\n.fa-flag-checkered:before {\n  content: \"\\f11e\";\n}\n.fa-terminal:before {\n  content: \"\\f120\";\n}\n.fa-code:before {\n  content: \"\\f121\";\n}\n.fa-mail-reply-all:before,\n.fa-reply-all:before {\n  content: \"\\f122\";\n}\n.fa-star-half-empty:before,\n.fa-star-half-full:before,\n.fa-star-half-o:before {\n  content: \"\\f123\";\n}\n.fa-location-arrow:before {\n  content: \"\\f124\";\n}\n.fa-crop:before {\n  content: \"\\f125\";\n}\n.fa-code-fork:before {\n  content: \"\\f126\";\n}\n.fa-unlink:before,\n.fa-chain-broken:before {\n  content: \"\\f127\";\n}\n.fa-question:before {\n  content: \"\\f128\";\n}\n.fa-info:before {\n  content: \"\\f129\";\n}\n.fa-exclamation:before {\n  content: \"\\f12a\";\n}\n.fa-superscript:before {\n  content: \"\\f12b\";\n}\n.fa-subscript:before {\n  content: \"\\f12c\";\n}\n.fa-eraser:before {\n  content: \"\\f12d\";\n}\n.fa-puzzle-piece:before {\n  content: \"\\f12e\";\n}\n.fa-microphone:before {\n  content: \"\\f130\";\n}\n.fa-microphone-slash:before {\n  content: \"\\f131\";\n}\n.fa-shield:before {\n  content: \"\\f132\";\n}\n.fa-calendar-o:before {\n  content: \"\\f133\";\n}\n.fa-fire-extinguisher:before {\n  content: \"\\f134\";\n}\n.fa-rocket:before {\n  content: \"\\f135\";\n}\n.fa-maxcdn:before {\n  content: \"\\f136\";\n}\n.fa-chevron-circle-left:before {\n  content: \"\\f137\";\n}\n.fa-chevron-circle-right:before {\n  content: \"\\f138\";\n}\n.fa-chevron-circle-up:before {\n  content: \"\\f139\";\n}\n.fa-chevron-circle-down:before {\n  content: \"\\f13a\";\n}\n.fa-html5:before {\n  content: \"\\f13b\";\n}\n.fa-css3:before {\n  content: \"\\f13c\";\n}\n.fa-anchor:before {\n  content: \"\\f13d\";\n}\n.fa-unlock-alt:before {\n  content: \"\\f13e\";\n}\n.fa-bullseye:before {\n  content: \"\\f140\";\n}\n.fa-ellipsis-h:before {\n  content: \"\\f141\";\n}\n.fa-ellipsis-v:before {\n  content: \"\\f142\";\n}\n.fa-rss-square:before {\n  content: \"\\f143\";\n}\n.fa-play-circle:before {\n  content: \"\\f144\";\n}\n.fa-ticket:before {\n  content: \"\\f145\";\n}\n.fa-minus-square:before {\n  content: \"\\f146\";\n}\n.fa-minus-square-o:before {\n  content: \"\\f147\";\n}\n.fa-level-up:before {\n  content: \"\\f148\";\n}\n.fa-level-down:before {\n  content: \"\\f149\";\n}\n.fa-check-square:before {\n  content: \"\\f14a\";\n}\n.fa-pencil-square:before {\n  content: \"\\f14b\";\n}\n.fa-external-link-square:before {\n  content: \"\\f14c\";\n}\n.fa-share-square:before {\n  content: \"\\f14d\";\n}\n.fa-compass:before {\n  content: \"\\f14e\";\n}\n.fa-toggle-down:before,\n.fa-caret-square-o-down:before {\n  content: \"\\f150\";\n}\n.fa-toggle-up:before,\n.fa-caret-square-o-up:before {\n  content: \"\\f151\";\n}\n.fa-toggle-right:before,\n.fa-caret-square-o-right:before {\n  content: \"\\f152\";\n}\n.fa-euro:before,\n.fa-eur:before {\n  content: \"\\f153\";\n}\n.fa-gbp:before {\n  content: \"\\f154\";\n}\n.fa-dollar:before,\n.fa-usd:before {\n  content: \"\\f155\";\n}\n.fa-rupee:before,\n.fa-inr:before {\n  content: \"\\f156\";\n}\n.fa-cny:before,\n.fa-rmb:before,\n.fa-yen:before,\n.fa-jpy:before {\n  content: \"\\f157\";\n}\n.fa-ruble:before,\n.fa-rouble:before,\n.fa-rub:before {\n  content: \"\\f158\";\n}\n.fa-won:before,\n.fa-krw:before {\n  content: \"\\f159\";\n}\n.fa-bitcoin:before,\n.fa-btc:before {\n  content: \"\\f15a\";\n}\n.fa-file:before {\n  content: \"\\f15b\";\n}\n.fa-file-text:before {\n  content: \"\\f15c\";\n}\n.fa-sort-alpha-asc:before {\n  content: \"\\f15d\";\n}\n.fa-sort-alpha-desc:before {\n  content: \"\\f15e\";\n}\n.fa-sort-amount-asc:before {\n  content: \"\\f160\";\n}\n.fa-sort-amount-desc:before {\n  content: \"\\f161\";\n}\n.fa-sort-numeric-asc:before {\n  content: \"\\f162\";\n}\n.fa-sort-numeric-desc:before {\n  content: \"\\f163\";\n}\n.fa-thumbs-up:before {\n  content: \"\\f164\";\n}\n.fa-thumbs-down:before {\n  content: \"\\f165\";\n}\n.fa-youtube-square:before {\n  content: \"\\f166\";\n}\n.fa-youtube:before {\n  content: \"\\f167\";\n}\n.fa-xing:before {\n  content: \"\\f168\";\n}\n.fa-xing-square:before {\n  content: \"\\f169\";\n}\n.fa-youtube-play:before {\n  content: \"\\f16a\";\n}\n.fa-dropbox:before {\n  content: \"\\f16b\";\n}\n.fa-stack-overflow:before {\n  content: \"\\f16c\";\n}\n.fa-instagram:before {\n  content: \"\\f16d\";\n}\n.fa-flickr:before {\n  content: \"\\f16e\";\n}\n.fa-adn:before {\n  content: \"\\f170\";\n}\n.fa-bitbucket:before {\n  content: \"\\f171\";\n}\n.fa-bitbucket-square:before {\n  content: \"\\f172\";\n}\n.fa-tumblr:before {\n  content: \"\\f173\";\n}\n.fa-tumblr-square:before {\n  content: \"\\f174\";\n}\n.fa-long-arrow-down:before {\n  content: \"\\f175\";\n}\n.fa-long-arrow-up:before {\n  content: \"\\f176\";\n}\n.fa-long-arrow-left:before {\n  content: \"\\f177\";\n}\n.fa-long-arrow-right:before {\n  content: \"\\f178\";\n}\n.fa-apple:before {\n  content: \"\\f179\";\n}\n.fa-windows:before {\n  content: \"\\f17a\";\n}\n.fa-android:before {\n  content: \"\\f17b\";\n}\n.fa-linux:before {\n  content: \"\\f17c\";\n}\n.fa-dribbble:before {\n  content: \"\\f17d\";\n}\n.fa-skype:before {\n  content: \"\\f17e\";\n}\n.fa-foursquare:before {\n  content: \"\\f180\";\n}\n.fa-trello:before {\n  content: \"\\f181\";\n}\n.fa-female:before {\n  content: \"\\f182\";\n}\n.fa-male:before {\n  content: \"\\f183\";\n}\n.fa-gittip:before {\n  content: \"\\f184\";\n}\n.fa-sun-o:before {\n  content: \"\\f185\";\n}\n.fa-moon-o:before {\n  content: \"\\f186\";\n}\n.fa-archive:before {\n  content: \"\\f187\";\n}\n.fa-bug:before {\n  content: \"\\f188\";\n}\n.fa-vk:before {\n  content: \"\\f189\";\n}\n.fa-weibo:before {\n  content: \"\\f18a\";\n}\n.fa-renren:before {\n  content: \"\\f18b\";\n}\n.fa-pagelines:before {\n  content: \"\\f18c\";\n}\n.fa-stack-exchange:before {\n  content: \"\\f18d\";\n}\n.fa-arrow-circle-o-right:before {\n  content: \"\\f18e\";\n}\n.fa-arrow-circle-o-left:before {\n  content: \"\\f190\";\n}\n.fa-toggle-left:before,\n.fa-caret-square-o-left:before {\n  content: \"\\f191\";\n}\n.fa-dot-circle-o:before {\n  content: \"\\f192\";\n}\n.fa-wheelchair:before {\n  content: \"\\f193\";\n}\n.fa-vimeo-square:before {\n  content: \"\\f194\";\n}\n.fa-turkish-lira:before,\n.fa-try:before {\n  content: \"\\f195\";\n}\n.fa-plus-square-o:before {\n  content: \"\\f196\";\n}\n.fa-space-shuttle:before {\n  content: \"\\f197\";\n}\n.fa-slack:before {\n  content: \"\\f198\";\n}\n.fa-envelope-square:before {\n  content: \"\\f199\";\n}\n.fa-wordpress:before {\n  content: \"\\f19a\";\n}\n.fa-openid:before {\n  content: \"\\f19b\";\n}\n.fa-institution:before,\n.fa-bank:before,\n.fa-university:before {\n  content: \"\\f19c\";\n}\n.fa-mortar-board:before,\n.fa-graduation-cap:before {\n  content: \"\\f19d\";\n}\n.fa-yahoo:before {\n  content: \"\\f19e\";\n}\n.fa-google:before {\n  content: \"\\f1a0\";\n}\n.fa-reddit:before {\n  content: \"\\f1a1\";\n}\n.fa-reddit-square:before {\n  content: \"\\f1a2\";\n}\n.fa-stumbleupon-circle:before {\n  content: \"\\f1a3\";\n}\n.fa-stumbleupon:before {\n  content: \"\\f1a4\";\n}\n.fa-delicious:before {\n  content: \"\\f1a5\";\n}\n.fa-digg:before {\n  content: \"\\f1a6\";\n}\n.fa-pied-piper:before {\n  content: \"\\f1a7\";\n}\n.fa-pied-piper-alt:before {\n  content: \"\\f1a8\";\n}\n.fa-drupal:before {\n  content: \"\\f1a9\";\n}\n.fa-joomla:before {\n  content: \"\\f1aa\";\n}\n.fa-language:before {\n  content: \"\\f1ab\";\n}\n.fa-fax:before {\n  content: \"\\f1ac\";\n}\n.fa-building:before {\n  content: \"\\f1ad\";\n}\n.fa-child:before {\n  content: \"\\f1ae\";\n}\n.fa-paw:before {\n  content: \"\\f1b0\";\n}\n.fa-spoon:before {\n  content: \"\\f1b1\";\n}\n.fa-cube:before {\n  content: \"\\f1b2\";\n}\n.fa-cubes:before {\n  content: \"\\f1b3\";\n}\n.fa-behance:before {\n  content: \"\\f1b4\";\n}\n.fa-behance-square:before {\n  content: \"\\f1b5\";\n}\n.fa-steam:before {\n  content: \"\\f1b6\";\n}\n.fa-steam-square:before {\n  content: \"\\f1b7\";\n}\n.fa-recycle:before {\n  content: \"\\f1b8\";\n}\n.fa-automobile:before,\n.fa-car:before {\n  content: \"\\f1b9\";\n}\n.fa-cab:before,\n.fa-taxi:before {\n  content: \"\\f1ba\";\n}\n.fa-tree:before {\n  content: \"\\f1bb\";\n}\n.fa-spotify:before {\n  content: \"\\f1bc\";\n}\n.fa-deviantart:before {\n  content: \"\\f1bd\";\n}\n.fa-soundcloud:before {\n  content: \"\\f1be\";\n}\n.fa-database:before {\n  content: \"\\f1c0\";\n}\n.fa-file-pdf-o:before {\n  content: \"\\f1c1\";\n}\n.fa-file-word-o:before {\n  content: \"\\f1c2\";\n}\n.fa-file-excel-o:before {\n  content: \"\\f1c3\";\n}\n.fa-file-powerpoint-o:before {\n  content: \"\\f1c4\";\n}\n.fa-file-photo-o:before,\n.fa-file-picture-o:before,\n.fa-file-image-o:before {\n  content: \"\\f1c5\";\n}\n.fa-file-zip-o:before,\n.fa-file-archive-o:before {\n  content: \"\\f1c6\";\n}\n.fa-file-sound-o:before,\n.fa-file-audio-o:before {\n  content: \"\\f1c7\";\n}\n.fa-file-movie-o:before,\n.fa-file-video-o:before {\n  content: \"\\f1c8\";\n}\n.fa-file-code-o:before {\n  content: \"\\f1c9\";\n}\n.fa-vine:before {\n  content: \"\\f1ca\";\n}\n.fa-codepen:before {\n  content: \"\\f1cb\";\n}\n.fa-jsfiddle:before {\n  content: \"\\f1cc\";\n}\n.fa-life-bouy:before,\n.fa-life-buoy:before,\n.fa-life-saver:before,\n.fa-support:before,\n.fa-life-ring:before {\n  content: \"\\f1cd\";\n}\n.fa-circle-o-notch:before {\n  content: \"\\f1ce\";\n}\n.fa-ra:before,\n.fa-rebel:before {\n  content: \"\\f1d0\";\n}\n.fa-ge:before,\n.fa-empire:before {\n  content: \"\\f1d1\";\n}\n.fa-git-square:before {\n  content: \"\\f1d2\";\n}\n.fa-git:before {\n  content: \"\\f1d3\";\n}\n.fa-hacker-news:before {\n  content: \"\\f1d4\";\n}\n.fa-tencent-weibo:before {\n  content: \"\\f1d5\";\n}\n.fa-qq:before {\n  content: \"\\f1d6\";\n}\n.fa-wechat:before,\n.fa-weixin:before {\n  content: \"\\f1d7\";\n}\n.fa-send:before,\n.fa-paper-plane:before {\n  content: \"\\f1d8\";\n}\n.fa-send-o:before,\n.fa-paper-plane-o:before {\n  content: \"\\f1d9\";\n}\n.fa-history:before {\n  content: \"\\f1da\";\n}\n.fa-circle-thin:before {\n  content: \"\\f1db\";\n}\n.fa-header:before {\n  content: \"\\f1dc\";\n}\n.fa-paragraph:before {\n  content: \"\\f1dd\";\n}\n.fa-sliders:before {\n  content: \"\\f1de\";\n}\n.fa-share-alt:before {\n  content: \"\\f1e0\";\n}\n.fa-share-alt-square:before {\n  content: \"\\f1e1\";\n}\n.fa-bomb:before {\n  content: \"\\f1e2\";\n}\n.fa-soccer-ball-o:before,\n.fa-futbol-o:before {\n  content: \"\\f1e3\";\n}\n.fa-tty:before {\n  content: \"\\f1e4\";\n}\n.fa-binoculars:before {\n  content: \"\\f1e5\";\n}\n.fa-plug:before {\n  content: \"\\f1e6\";\n}\n.fa-slideshare:before {\n  content: \"\\f1e7\";\n}\n.fa-twitch:before {\n  content: \"\\f1e8\";\n}\n.fa-yelp:before {\n  content: \"\\f1e9\";\n}\n.fa-newspaper-o:before {\n  content: \"\\f1ea\";\n}\n.fa-wifi:before {\n  content: \"\\f1eb\";\n}\n.fa-calculator:before {\n  content: \"\\f1ec\";\n}\n.fa-paypal:before {\n  content: \"\\f1ed\";\n}\n.fa-google-wallet:before {\n  content: \"\\f1ee\";\n}\n.fa-cc-visa:before {\n  content: \"\\f1f0\";\n}\n.fa-cc-mastercard:before {\n  content: \"\\f1f1\";\n}\n.fa-cc-discover:before {\n  content: \"\\f1f2\";\n}\n.fa-cc-amex:before {\n  content: \"\\f1f3\";\n}\n.fa-cc-paypal:before {\n  content: \"\\f1f4\";\n}\n.fa-cc-stripe:before {\n  content: \"\\f1f5\";\n}\n.fa-bell-slash:before {\n  content: \"\\f1f6\";\n}\n.fa-bell-slash-o:before {\n  content: \"\\f1f7\";\n}\n.fa-trash:before {\n  content: \"\\f1f8\";\n}\n.fa-copyright:before {\n  content: \"\\f1f9\";\n}\n.fa-at:before {\n  content: \"\\f1fa\";\n}\n.fa-eyedropper:before {\n  content: \"\\f1fb\";\n}\n.fa-paint-brush:before {\n  content: \"\\f1fc\";\n}\n.fa-birthday-cake:before {\n  content: \"\\f1fd\";\n}\n.fa-area-chart:before {\n  content: \"\\f1fe\";\n}\n.fa-pie-chart:before {\n  content: \"\\f200\";\n}\n.fa-line-chart:before {\n  content: \"\\f201\";\n}\n.fa-lastfm:before {\n  content: \"\\f202\";\n}\n.fa-lastfm-square:before {\n  content: \"\\f203\";\n}\n.fa-toggle-off:before {\n  content: \"\\f204\";\n}\n.fa-toggle-on:before {\n  content: \"\\f205\";\n}\n.fa-bicycle:before {\n  content: \"\\f206\";\n}\n.fa-bus:before {\n  content: \"\\f207\";\n}\n.fa-ioxhost:before {\n  content: \"\\f208\";\n}\n.fa-angellist:before {\n  content: \"\\f209\";\n}\n.fa-cc:before {\n  content: \"\\f20a\";\n}\n.fa-shekel:before,\n.fa-sheqel:before,\n.fa-ils:before {\n  content: \"\\f20b\";\n}\n.fa-meanpath:before {\n  content: \"\\f20c\";\n}\n"
  },
  {
    "path": "public/libs/jquery-2.1.0.js",
    "content": "/*!\n * jQuery JavaScript Library v2.1.0\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-01-23T21:10Z\n */\n\n(function (global, factory) {\n\n  if (typeof module === \"object\" && typeof module.exports === \"object\") {\n    // For CommonJS and CommonJS-like environments where a proper window is present,\n    // execute the factory and get jQuery\n    // For environments that do not inherently posses a window with a document\n    // (such as Node.js), expose a jQuery-making factory as module.exports\n    // This accentuates the need for the creation of a real window\n    // e.g. var jQuery = require(\"jquery\")(window);\n    // See ticket #14549 for more info\n    module.exports = global.document ?\n      factory(global, true) :\n      function (w) {\n        if (!w.document) {\n          throw new Error(\"jQuery requires a window with a document\");\n        }\n        return factory(w);\n      };\n  } else {\n    factory(global);\n  }\n\n// Pass this if window is not defined yet\n}(typeof window !== \"undefined\" ? window : this, function (window, noGlobal) {\n\n// Can't do this because several apps including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n// Support: Firefox 18+\n//\n\n  var arr = [];\n\n  var slice = arr.slice;\n\n  var concat = arr.concat;\n\n  var push = arr.push;\n\n  var indexOf = arr.indexOf;\n\n  var class2type = {};\n\n  var toString = class2type.toString;\n\n  var hasOwn = class2type.hasOwnProperty;\n\n  var trim = \"\".trim;\n\n  var support = {};\n\n\n  var\n  // Use the correct document accordingly with window argument (sandbox)\n    document = window.document,\n\n    version = \"2.1.0\",\n\n  // Define a local copy of jQuery\n    jQuery = function (selector, context) {\n      // The jQuery object is actually just the init constructor 'enhanced'\n      // Need init if jQuery is called (just allow error to be thrown if not included)\n      return new jQuery.fn.init(selector, context);\n    },\n\n  // Matches dashed string for camelizing\n    rmsPrefix = /^-ms-/,\n    rdashAlpha = /-([\\da-z])/gi,\n\n  // Used by jQuery.camelCase as callback to replace()\n    fcamelCase = function (all, letter) {\n      return letter.toUpperCase();\n    };\n\n  jQuery.fn = jQuery.prototype = {\n    // The current version of jQuery being used\n    jquery: version,\n\n    constructor: jQuery,\n\n    // Start with an empty selector\n    selector: \"\",\n\n    // The default length of a jQuery object is 0\n    length: 0,\n\n    toArray: function () {\n      return slice.call(this);\n    },\n\n    // Get the Nth element in the matched element set OR\n    // Get the whole matched element set as a clean array\n    get: function (num) {\n      return num != null ?\n\n        // Return a 'clean' array\n        ( num < 0 ? this[ num + this.length ] : this[ num ] ) :\n\n        // Return just the object\n        slice.call(this);\n    },\n\n    // Take an array of elements and push it onto the stack\n    // (returning the new matched element set)\n    pushStack: function (elems) {\n\n      // Build a new jQuery matched element set\n      var ret = jQuery.merge(this.constructor(), elems);\n\n      // Add the old object onto the stack (as a reference)\n      ret.prevObject = this;\n      ret.context = this.context;\n\n      // Return the newly-formed element set\n      return ret;\n    },\n\n    // Execute a callback for every element in the matched set.\n    // (You can seed the arguments with an array of args, but this is\n    // only used internally.)\n    each: function (callback, args) {\n      return jQuery.each(this, callback, args);\n    },\n\n    map: function (callback) {\n      return this.pushStack(jQuery.map(this, function (elem, i) {\n        return callback.call(elem, i, elem);\n      }));\n    },\n\n    slice: function () {\n      return this.pushStack(slice.apply(this, arguments));\n    },\n\n    first: function () {\n      return this.eq(0);\n    },\n\n    last: function () {\n      return this.eq(-1);\n    },\n\n    eq: function (i) {\n      var len = this.length,\n        j = +i + ( i < 0 ? len : 0 );\n      return this.pushStack(j >= 0 && j < len ? [ this[j] ] : []);\n    },\n\n    end: function () {\n      return this.prevObject || this.constructor(null);\n    },\n\n    // For internal use only.\n    // Behaves like an Array's method, not like a jQuery method.\n    push: push,\n    sort: arr.sort,\n    splice: arr.splice\n  };\n\n  jQuery.extend = jQuery.fn.extend = function () {\n    var options, name, src, copy, copyIsArray, clone,\n      target = arguments[0] || {},\n      i = 1,\n      length = arguments.length,\n      deep = false;\n\n    // Handle a deep copy situation\n    if (typeof target === \"boolean\") {\n      deep = target;\n\n      // skip the boolean and the target\n      target = arguments[ i ] || {};\n      i++;\n    }\n\n    // Handle case when target is a string or something (possible in deep copy)\n    if (typeof target !== \"object\" && !jQuery.isFunction(target)) {\n      target = {};\n    }\n\n    // extend jQuery itself if only one argument is passed\n    if (i === length) {\n      target = this;\n      i--;\n    }\n\n    for (; i < length; i++) {\n      // Only deal with non-null/undefined values\n      if ((options = arguments[ i ]) != null) {\n        // Extend the base object\n        for (name in options) {\n          src = target[ name ];\n          copy = options[ name ];\n\n          // Prevent never-ending loop\n          if (target === copy) {\n            continue;\n          }\n\n          // Recurse if we're merging plain objects or arrays\n          if (deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )) {\n            if (copyIsArray) {\n              copyIsArray = false;\n              clone = src && jQuery.isArray(src) ? src : [];\n\n            } else {\n              clone = src && jQuery.isPlainObject(src) ? src : {};\n            }\n\n            // Never move original objects, clone them\n            target[ name ] = jQuery.extend(deep, clone, copy);\n\n            // Don't bring in undefined values\n          } else if (copy !== undefined) {\n            target[ name ] = copy;\n          }\n        }\n      }\n    }\n\n    // Return the modified object\n    return target;\n  };\n\n  jQuery.extend({\n    // Unique for each copy of jQuery on the page\n    expando: \"jQuery\" + ( version + Math.random() ).replace(/\\D/g, \"\"),\n\n    // Assume jQuery is ready without the ready module\n    isReady: true,\n\n    error: function (msg) {\n      throw new Error(msg);\n    },\n\n    noop: function () {\n    },\n\n    // See test/unit/core.js for details concerning isFunction.\n    // Since version 1.3, DOM methods and functions like alert\n    // aren't supported. They return false on IE (#2968).\n    isFunction: function (obj) {\n      return jQuery.type(obj) === \"function\";\n    },\n\n    isArray: Array.isArray,\n\n    isWindow: function (obj) {\n      return obj != null && obj === obj.window;\n    },\n\n    isNumeric: function (obj) {\n      // parseFloat NaNs numeric-cast false positives (null|true|false|\"\")\n      // ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n      // subtraction forces infinities to NaN\n      return obj - parseFloat(obj) >= 0;\n    },\n\n    isPlainObject: function (obj) {\n      // Not plain objects:\n      // - Any object or value whose internal [[Class]] property is not \"[object Object]\"\n      // - DOM nodes\n      // - window\n      if (jQuery.type(obj) !== \"object\" || obj.nodeType || jQuery.isWindow(obj)) {\n        return false;\n      }\n\n      // Support: Firefox <20\n      // The try/catch suppresses exceptions thrown when attempting to access\n      // the \"constructor\" property of certain host objects, ie. |window.location|\n      // https://bugzilla.mozilla.org/show_bug.cgi?id=814622\n      try {\n        if (obj.constructor && !hasOwn.call(obj.constructor.prototype, \"isPrototypeOf\")) {\n          return false;\n        }\n      } catch (e) {\n        return false;\n      }\n\n      // If the function hasn't returned already, we're confident that\n      // |obj| is a plain object, created by {} or constructed with new Object\n      return true;\n    },\n\n    isEmptyObject: function (obj) {\n      var name;\n      for (name in obj) {\n        return false;\n      }\n      return true;\n    },\n\n    type: function (obj) {\n      if (obj == null) {\n        return obj + \"\";\n      }\n      // Support: Android < 4.0, iOS < 6 (functionish RegExp)\n      return typeof obj === \"object\" || typeof obj === \"function\" ?\n        class2type[ toString.call(obj) ] || \"object\" :\n        typeof obj;\n    },\n\n    // Evaluates a script in a global context\n    globalEval: function (code) {\n      var script,\n        indirect = eval;\n\n      code = jQuery.trim(code);\n\n      if (code) {\n        // If the code includes a valid, prologue position\n        // strict mode pragma, execute code by injecting a\n        // script tag into the document.\n        if (code.indexOf(\"use strict\") === 1) {\n          script = document.createElement(\"script\");\n          script.text = code;\n          document.head.appendChild(script).parentNode.removeChild(script);\n        } else {\n          // Otherwise, avoid the DOM node creation, insertion\n          // and removal by using an indirect global eval\n          indirect(code);\n        }\n      }\n    },\n\n    // Convert dashed to camelCase; used by the css and data modules\n    // Microsoft forgot to hump their vendor prefix (#9572)\n    camelCase: function (string) {\n      return string.replace(rmsPrefix, \"ms-\").replace(rdashAlpha, fcamelCase);\n    },\n\n    nodeName: function (elem, name) {\n      return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n    },\n\n    // args is for internal usage only\n    each: function (obj, callback, args) {\n      var value,\n        i = 0,\n        length = obj.length,\n        isArray = isArraylike(obj);\n\n      if (args) {\n        if (isArray) {\n          for (; i < length; i++) {\n            value = callback.apply(obj[ i ], args);\n\n            if (value === false) {\n              break;\n            }\n          }\n        } else {\n          for (i in obj) {\n            value = callback.apply(obj[ i ], args);\n\n            if (value === false) {\n              break;\n            }\n          }\n        }\n\n        // A special, fast, case for the most common use of each\n      } else {\n        if (isArray) {\n          for (; i < length; i++) {\n            value = callback.call(obj[ i ], i, obj[ i ]);\n\n            if (value === false) {\n              break;\n            }\n          }\n        } else {\n          for (i in obj) {\n            value = callback.call(obj[ i ], i, obj[ i ]);\n\n            if (value === false) {\n              break;\n            }\n          }\n        }\n      }\n\n      return obj;\n    },\n\n    trim: function (text) {\n      return text == null ? \"\" : trim.call(text);\n    },\n\n    // results is for internal usage only\n    makeArray: function (arr, results) {\n      var ret = results || [];\n\n      if (arr != null) {\n        if (isArraylike(Object(arr))) {\n          jQuery.merge(ret,\n              typeof arr === \"string\" ?\n              [ arr ] : arr\n          );\n        } else {\n          push.call(ret, arr);\n        }\n      }\n\n      return ret;\n    },\n\n    inArray: function (elem, arr, i) {\n      return arr == null ? -1 : indexOf.call(arr, elem, i);\n    },\n\n    merge: function (first, second) {\n      var len = +second.length,\n        j = 0,\n        i = first.length;\n\n      for (; j < len; j++) {\n        first[ i++ ] = second[ j ];\n      }\n\n      first.length = i;\n\n      return first;\n    },\n\n    grep: function (elems, callback, invert) {\n      var callbackInverse,\n        matches = [],\n        i = 0,\n        length = elems.length,\n        callbackExpect = !invert;\n\n      // Go through the array, only saving the items\n      // that pass the validator function\n      for (; i < length; i++) {\n        callbackInverse = !callback(elems[ i ], i);\n        if (callbackInverse !== callbackExpect) {\n          matches.push(elems[ i ]);\n        }\n      }\n\n      return matches;\n    },\n\n    // arg is for internal usage only\n    map: function (elems, callback, arg) {\n      var value,\n        i = 0,\n        length = elems.length,\n        isArray = isArraylike(elems),\n        ret = [];\n\n      // Go through the array, translating each of the items to their new values\n      if (isArray) {\n        for (; i < length; i++) {\n          value = callback(elems[ i ], i, arg);\n\n          if (value != null) {\n            ret.push(value);\n          }\n        }\n\n        // Go through every key on the object,\n      } else {\n        for (i in elems) {\n          value = callback(elems[ i ], i, arg);\n\n          if (value != null) {\n            ret.push(value);\n          }\n        }\n      }\n\n      // Flatten any nested arrays\n      return concat.apply([], ret);\n    },\n\n    // A global GUID counter for objects\n    guid: 1,\n\n    // Bind a function to a context, optionally partially applying any\n    // arguments.\n    proxy: function (fn, context) {\n      var tmp, args, proxy;\n\n      if (typeof context === \"string\") {\n        tmp = fn[ context ];\n        context = fn;\n        fn = tmp;\n      }\n\n      // Quick check to determine if target is callable, in the spec\n      // this throws a TypeError, but we will just return undefined.\n      if (!jQuery.isFunction(fn)) {\n        return undefined;\n      }\n\n      // Simulated bind\n      args = slice.call(arguments, 2);\n      proxy = function () {\n        return fn.apply(context || this, args.concat(slice.call(arguments)));\n      };\n\n      // Set the guid of unique handler to the same of original handler, so it can be removed\n      proxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n      return proxy;\n    },\n\n    now: Date.now,\n\n    // jQuery.support is not used in Core but other projects attach their\n    // properties to it so it needs to exist.\n    support: support\n  });\n\n// Populate the class2type map\n  jQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function (i, name) {\n    class2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n  });\n\n  function isArraylike(obj) {\n    var length = obj.length,\n      type = jQuery.type(obj);\n\n    if (type === \"function\" || jQuery.isWindow(obj)) {\n      return false;\n    }\n\n    if (obj.nodeType === 1 && length) {\n      return true;\n    }\n\n    return type === \"array\" || length === 0 ||\n      typeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n  }\n\n  var Sizzle =\n    /*!\n     * Sizzle CSS Selector Engine v1.10.16\n     * http://sizzlejs.com/\n     *\n     * Copyright 2013 jQuery Foundation, Inc. and other contributors\n     * Released under the MIT license\n     * http://jquery.org/license\n     *\n     * Date: 2014-01-13\n     */\n    (function (window) {\n\n      var i,\n        support,\n        Expr,\n        getText,\n        isXML,\n        compile,\n        outermostContext,\n        sortInput,\n        hasDuplicate,\n\n      // Local document vars\n        setDocument,\n        document,\n        docElem,\n        documentIsHTML,\n        rbuggyQSA,\n        rbuggyMatches,\n        matches,\n        contains,\n\n      // Instance-specific data\n        expando = \"sizzle\" + -(new Date()),\n        preferredDoc = window.document,\n        dirruns = 0,\n        done = 0,\n        classCache = createCache(),\n        tokenCache = createCache(),\n        compilerCache = createCache(),\n        sortOrder = function (a, b) {\n          if (a === b) {\n            hasDuplicate = true;\n          }\n          return 0;\n        },\n\n      // General-purpose constants\n        strundefined = typeof undefined,\n        MAX_NEGATIVE = 1 << 31,\n\n      // Instance methods\n        hasOwn = ({}).hasOwnProperty,\n        arr = [],\n        pop = arr.pop,\n        push_native = arr.push,\n        push = arr.push,\n        slice = arr.slice,\n      // Use a stripped-down indexOf if we can't use a native one\n        indexOf = arr.indexOf || function (elem) {\n          var i = 0,\n            len = this.length;\n          for (; i < len; i++) {\n            if (this[i] === elem) {\n              return i;\n            }\n          }\n          return -1;\n        },\n\n        booleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n      // Regular expressions\n\n      // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n        whitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n      // http://www.w3.org/TR/css3-syntax/#characters\n        characterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n      // Loosely modeled on CSS identifier characters\n      // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n      // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n        identifier = characterEncoding.replace(\"w\", \"w#\"),\n\n      // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n        attributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n          \"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n      // Prefer arguments quoted,\n      //   then not containing pseudos/brackets,\n      //   then attribute selectors/non-parenthetical expressions,\n      //   then anything else\n      // These preferences are here to reduce the number of selectors\n      //   needing tokenize in the PSEUDO preFilter\n        pseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace(3, 8) + \")*)|.*)\\\\)|)\",\n\n      // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n        rtrim = new RegExp(\"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\"),\n\n        rcomma = new RegExp(\"^\" + whitespace + \"*,\" + whitespace + \"*\"),\n        rcombinators = new RegExp(\"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\"),\n\n        rattributeQuotes = new RegExp(\"=\" + whitespace + \"*([^\\\\]'\\\"]*?)\" + whitespace + \"*\\\\]\", \"g\"),\n\n        rpseudo = new RegExp(pseudos),\n        ridentifier = new RegExp(\"^\" + identifier + \"$\"),\n\n        matchExpr = {\n          \"ID\": new RegExp(\"^#(\" + characterEncoding + \")\"),\n          \"CLASS\": new RegExp(\"^\\\\.(\" + characterEncoding + \")\"),\n          \"TAG\": new RegExp(\"^(\" + characterEncoding.replace(\"w\", \"w*\") + \")\"),\n          \"ATTR\": new RegExp(\"^\" + attributes),\n          \"PSEUDO\": new RegExp(\"^\" + pseudos),\n          \"CHILD\": new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n            \"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n            \"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\"),\n          \"bool\": new RegExp(\"^(?:\" + booleans + \")$\", \"i\"),\n          // For use in libraries implementing .is()\n          // We use this for POS matching in `select`\n          \"needsContext\": new RegExp(\"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n            whitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\")\n        },\n\n        rinputs = /^(?:input|select|textarea|button)$/i,\n        rheader = /^h\\d$/i,\n\n        rnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n      // Easily-parseable/retrievable ID or TAG or CLASS selectors\n        rquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n        rsibling = /[+~]/,\n        rescape = /'|\\\\/g,\n\n      // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n        runescape = new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\"),\n        funescape = function (_, escaped, escapedWhitespace) {\n          var high = \"0x\" + escaped - 0x10000;\n          // NaN means non-codepoint\n          // Support: Firefox\n          // Workaround erroneous numeric interpretation of +\"0x\"\n          return high !== high || escapedWhitespace ?\n            escaped :\n              high < 0 ?\n            // BMP codepoint\n            String.fromCharCode(high + 0x10000) :\n            // Supplemental Plane codepoint (surrogate pair)\n            String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);\n        };\n\n// Optimize for push.apply( _, NodeList )\n      try {\n        push.apply(\n          (arr = slice.call(preferredDoc.childNodes)),\n          preferredDoc.childNodes\n        );\n        // Support: Android<4.0\n        // Detect silently failing push.apply\n        arr[ preferredDoc.childNodes.length ].nodeType;\n      } catch (e) {\n        push = { apply: arr.length ?\n\n          // Leverage slice if possible\n          function (target, els) {\n            push_native.apply(target, slice.call(els));\n          } :\n\n          // Support: IE<9\n          // Otherwise append directly\n          function (target, els) {\n            var j = target.length,\n              i = 0;\n            // Can't trust NodeList.length\n            while ((target[j++] = els[i++])) {\n            }\n            target.length = j - 1;\n          }\n        };\n      }\n\n      function Sizzle(selector, context, results, seed) {\n        var match, elem, m, nodeType,\n        // QSA vars\n          i, groups, old, nid, newContext, newSelector;\n\n        if (( context ? context.ownerDocument || context : preferredDoc ) !== document) {\n          setDocument(context);\n        }\n\n        context = context || document;\n        results = results || [];\n\n        if (!selector || typeof selector !== \"string\") {\n          return results;\n        }\n\n        if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {\n          return [];\n        }\n\n        if (documentIsHTML && !seed) {\n\n          // Shortcuts\n          if ((match = rquickExpr.exec(selector))) {\n            // Speed-up: Sizzle(\"#ID\")\n            if ((m = match[1])) {\n              if (nodeType === 9) {\n                elem = context.getElementById(m);\n                // Check parentNode to catch when Blackberry 4.6 returns\n                // nodes that are no longer in the document (jQuery #6963)\n                if (elem && elem.parentNode) {\n                  // Handle the case where IE, Opera, and Webkit return items\n                  // by name instead of ID\n                  if (elem.id === m) {\n                    results.push(elem);\n                    return results;\n                  }\n                } else {\n                  return results;\n                }\n              } else {\n                // Context is not a document\n                if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) &&\n                  contains(context, elem) && elem.id === m) {\n                  results.push(elem);\n                  return results;\n                }\n              }\n\n              // Speed-up: Sizzle(\"TAG\")\n            } else if (match[2]) {\n              push.apply(results, context.getElementsByTagName(selector));\n              return results;\n\n              // Speed-up: Sizzle(\".CLASS\")\n            } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) {\n              push.apply(results, context.getElementsByClassName(m));\n              return results;\n            }\n          }\n\n          // QSA path\n          if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) {\n            nid = old = expando;\n            newContext = context;\n            newSelector = nodeType === 9 && selector;\n\n            // qSA works strangely on Element-rooted queries\n            // We can work around this by specifying an extra ID on the root\n            // and working up from there (Thanks to Andrew Dupont for the technique)\n            // IE 8 doesn't work on object elements\n            if (nodeType === 1 && context.nodeName.toLowerCase() !== \"object\") {\n              groups = tokenize(selector);\n\n              if ((old = context.getAttribute(\"id\"))) {\n                nid = old.replace(rescape, \"\\\\$&\");\n              } else {\n                context.setAttribute(\"id\", nid);\n              }\n              nid = \"[id='\" + nid + \"'] \";\n\n              i = groups.length;\n              while (i--) {\n                groups[i] = nid + toSelector(groups[i]);\n              }\n              newContext = rsibling.test(selector) && testContext(context.parentNode) || context;\n              newSelector = groups.join(\",\");\n            }\n\n            if (newSelector) {\n              try {\n                push.apply(results,\n                  newContext.querySelectorAll(newSelector)\n                );\n                return results;\n              } catch (qsaError) {\n              } finally {\n                if (!old) {\n                  context.removeAttribute(\"id\");\n                }\n              }\n            }\n          }\n        }\n\n        // All others\n        return select(selector.replace(rtrim, \"$1\"), context, results, seed);\n      }\n\n      /**\n       * Create key-value caches of limited size\n       * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n       *  property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n       *  deleting the oldest entry\n       */\n      function createCache() {\n        var keys = [];\n\n        function cache(key, value) {\n          // Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n          if (keys.push(key + \" \") > Expr.cacheLength) {\n            // Only keep the most recent entries\n            delete cache[ keys.shift() ];\n          }\n          return (cache[ key + \" \" ] = value);\n        }\n\n        return cache;\n      }\n\n      /**\n       * Mark a function for special use by Sizzle\n       * @param {Function} fn The function to mark\n       */\n      function markFunction(fn) {\n        fn[ expando ] = true;\n        return fn;\n      }\n\n      /**\n       * Support testing using an element\n       * @param {Function} fn Passed the created div and expects a boolean result\n       */\n      function assert(fn) {\n        var div = document.createElement(\"div\");\n\n        try {\n          return !!fn(div);\n        } catch (e) {\n          return false;\n        } finally {\n          // Remove from its parent by default\n          if (div.parentNode) {\n            div.parentNode.removeChild(div);\n          }\n          // release memory in IE\n          div = null;\n        }\n      }\n\n      /**\n       * Adds the same handler for all of the specified attrs\n       * @param {String} attrs Pipe-separated list of attributes\n       * @param {Function} handler The method that will be applied\n       */\n      function addHandle(attrs, handler) {\n        var arr = attrs.split(\"|\"),\n          i = attrs.length;\n\n        while (i--) {\n          Expr.attrHandle[ arr[i] ] = handler;\n        }\n      }\n\n      /**\n       * Checks document order of two siblings\n       * @param {Element} a\n       * @param {Element} b\n       * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n       */\n      function siblingCheck(a, b) {\n        var cur = b && a,\n          diff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n            ( ~b.sourceIndex || MAX_NEGATIVE ) -\n            ( ~a.sourceIndex || MAX_NEGATIVE );\n\n        // Use IE sourceIndex if available on both nodes\n        if (diff) {\n          return diff;\n        }\n\n        // Check if b follows a\n        if (cur) {\n          while ((cur = cur.nextSibling)) {\n            if (cur === b) {\n              return -1;\n            }\n          }\n        }\n\n        return a ? 1 : -1;\n      }\n\n      /**\n       * Returns a function to use in pseudos for input types\n       * @param {String} type\n       */\n      function createInputPseudo(type) {\n        return function (elem) {\n          var name = elem.nodeName.toLowerCase();\n          return name === \"input\" && elem.type === type;\n        };\n      }\n\n      /**\n       * Returns a function to use in pseudos for buttons\n       * @param {String} type\n       */\n      function createButtonPseudo(type) {\n        return function (elem) {\n          var name = elem.nodeName.toLowerCase();\n          return (name === \"input\" || name === \"button\") && elem.type === type;\n        };\n      }\n\n      /**\n       * Returns a function to use in pseudos for positionals\n       * @param {Function} fn\n       */\n      function createPositionalPseudo(fn) {\n        return markFunction(function (argument) {\n          argument = +argument;\n          return markFunction(function (seed, matches) {\n            var j,\n              matchIndexes = fn([], seed.length, argument),\n              i = matchIndexes.length;\n\n            // Match elements found at the specified indexes\n            while (i--) {\n              if (seed[ (j = matchIndexes[i]) ]) {\n                seed[j] = !(matches[j] = seed[j]);\n              }\n            }\n          });\n        });\n      }\n\n      /**\n       * Checks a node for validity as a Sizzle context\n       * @param {Element|Object=} context\n       * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n       */\n      function testContext(context) {\n        return context && typeof context.getElementsByTagName !== strundefined && context;\n      }\n\n// Expose support vars for convenience\n      support = Sizzle.support = {};\n\n      /**\n       * Detects XML nodes\n       * @param {Element|Object} elem An element or a document\n       * @returns {Boolean} True iff elem is a non-HTML XML node\n       */\n      isXML = Sizzle.isXML = function (elem) {\n        // documentElement is verified for cases where it doesn't yet exist\n        // (such as loading iframes in IE - #4833)\n        var documentElement = elem && (elem.ownerDocument || elem).documentElement;\n        return documentElement ? documentElement.nodeName !== \"HTML\" : false;\n      };\n\n      /**\n       * Sets document-related variables once based on the current document\n       * @param {Element|Object} [doc] An element or document object to use to set the document\n       * @returns {Object} Returns the current document\n       */\n      setDocument = Sizzle.setDocument = function (node) {\n        var hasCompare,\n          doc = node ? node.ownerDocument || node : preferredDoc,\n          parent = doc.defaultView;\n\n        // If no document and documentElement is available, return\n        if (doc === document || doc.nodeType !== 9 || !doc.documentElement) {\n          return document;\n        }\n\n        // Set our document\n        document = doc;\n        docElem = doc.documentElement;\n\n        // Support tests\n        documentIsHTML = !isXML(doc);\n\n        // Support: IE>8\n        // If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n        // IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n        // IE6-8 do not support the defaultView property so parent will be undefined\n        if (parent && parent !== parent.top) {\n          // IE11 does not have attachEvent, so all must suffer\n          if (parent.addEventListener) {\n            parent.addEventListener(\"unload\", function () {\n              setDocument();\n            }, false);\n          } else if (parent.attachEvent) {\n            parent.attachEvent(\"onunload\", function () {\n              setDocument();\n            });\n          }\n        }\n\n        /* Attributes\n         ---------------------------------------------------------------------- */\n\n        // Support: IE<8\n        // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\n        support.attributes = assert(function (div) {\n          div.className = \"i\";\n          return !div.getAttribute(\"className\");\n        });\n\n        /* getElement(s)By*\n         ---------------------------------------------------------------------- */\n\n        // Check if getElementsByTagName(\"*\") returns only elements\n        support.getElementsByTagName = assert(function (div) {\n          div.appendChild(doc.createComment(\"\"));\n          return !div.getElementsByTagName(\"*\").length;\n        });\n\n        // Check if getElementsByClassName can be trusted\n        support.getElementsByClassName = rnative.test(doc.getElementsByClassName) && assert(function (div) {\n          div.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\n\n          // Support: Safari<4\n          // Catch class over-caching\n          div.firstChild.className = \"i\";\n          // Support: Opera<10\n          // Catch gEBCN failure to find non-leading classes\n          return div.getElementsByClassName(\"i\").length === 2;\n        });\n\n        // Support: IE<10\n        // Check if getElementById returns elements by name\n        // The broken getElementById methods don't pick up programatically-set names,\n        // so use a roundabout getElementsByName test\n        support.getById = assert(function (div) {\n          docElem.appendChild(div).id = expando;\n          return !doc.getElementsByName || !doc.getElementsByName(expando).length;\n        });\n\n        // ID find and filter\n        if (support.getById) {\n          Expr.find[\"ID\"] = function (id, context) {\n            if (typeof context.getElementById !== strundefined && documentIsHTML) {\n              var m = context.getElementById(id);\n              // Check parentNode to catch when Blackberry 4.6 returns\n              // nodes that are no longer in the document #6963\n              return m && m.parentNode ? [m] : [];\n            }\n          };\n          Expr.filter[\"ID\"] = function (id) {\n            var attrId = id.replace(runescape, funescape);\n            return function (elem) {\n              return elem.getAttribute(\"id\") === attrId;\n            };\n          };\n        } else {\n          // Support: IE6/7\n          // getElementById is not reliable as a find shortcut\n          delete Expr.find[\"ID\"];\n\n          Expr.filter[\"ID\"] = function (id) {\n            var attrId = id.replace(runescape, funescape);\n            return function (elem) {\n              var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n              return node && node.value === attrId;\n            };\n          };\n        }\n\n        // Tag\n        Expr.find[\"TAG\"] = support.getElementsByTagName ?\n          function (tag, context) {\n            if (typeof context.getElementsByTagName !== strundefined) {\n              return context.getElementsByTagName(tag);\n            }\n          } :\n          function (tag, context) {\n            var elem,\n              tmp = [],\n              i = 0,\n              results = context.getElementsByTagName(tag);\n\n            // Filter out possible comments\n            if (tag === \"*\") {\n              while ((elem = results[i++])) {\n                if (elem.nodeType === 1) {\n                  tmp.push(elem);\n                }\n              }\n\n              return tmp;\n            }\n            return results;\n          };\n\n        // Class\n        Expr.find[\"CLASS\"] = support.getElementsByClassName && function (className, context) {\n          if (typeof context.getElementsByClassName !== strundefined && documentIsHTML) {\n            return context.getElementsByClassName(className);\n          }\n        };\n\n        /* QSA/matchesSelector\n         ---------------------------------------------------------------------- */\n\n        // QSA and matchesSelector support\n\n        // matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n        rbuggyMatches = [];\n\n        // qSa(:focus) reports false when true (Chrome 21)\n        // We allow this because of a bug in IE8/9 that throws an error\n        // whenever `document.activeElement` is accessed on an iframe\n        // So, we allow :focus to pass through QSA all the time to avoid the IE error\n        // See http://bugs.jquery.com/ticket/13378\n        rbuggyQSA = [];\n\n        if ((support.qsa = rnative.test(doc.querySelectorAll))) {\n          // Build QSA regex\n          // Regex strategy adopted from Diego Perini\n          assert(function (div) {\n            // Select is set to empty string on purpose\n            // This is to test IE's treatment of not explicitly\n            // setting a boolean content attribute,\n            // since its presence should be enough\n            // http://bugs.jquery.com/ticket/12359\n            div.innerHTML = \"<select t=''><option selected=''></option></select>\";\n\n            // Support: IE8, Opera 10-12\n            // Nothing should be selected when empty strings follow ^= or $= or *=\n            if (div.querySelectorAll(\"[t^='']\").length) {\n              rbuggyQSA.push(\"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\");\n            }\n\n            // Support: IE8\n            // Boolean attributes and \"value\" are not treated correctly\n            if (!div.querySelectorAll(\"[selected]\").length) {\n              rbuggyQSA.push(\"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\");\n            }\n\n            // Webkit/Opera - :checked should return selected option elements\n            // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n            // IE8 throws error here and will not see later tests\n            if (!div.querySelectorAll(\":checked\").length) {\n              rbuggyQSA.push(\":checked\");\n            }\n          });\n\n          assert(function (div) {\n            // Support: Windows 8 Native Apps\n            // The type and name attributes are restricted during .innerHTML assignment\n            var input = doc.createElement(\"input\");\n            input.setAttribute(\"type\", \"hidden\");\n            div.appendChild(input).setAttribute(\"name\", \"D\");\n\n            // Support: IE8\n            // Enforce case-sensitivity of name attribute\n            if (div.querySelectorAll(\"[name=d]\").length) {\n              rbuggyQSA.push(\"name\" + whitespace + \"*[*^$|!~]?=\");\n            }\n\n            // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n            // IE8 throws error here and will not see later tests\n            if (!div.querySelectorAll(\":enabled\").length) {\n              rbuggyQSA.push(\":enabled\", \":disabled\");\n            }\n\n            // Opera 10-11 does not throw on post-comma invalid pseudos\n            div.querySelectorAll(\"*,:x\");\n            rbuggyQSA.push(\",.*:\");\n          });\n        }\n\n        if ((support.matchesSelector = rnative.test((matches = docElem.webkitMatchesSelector ||\n          docElem.mozMatchesSelector ||\n          docElem.oMatchesSelector ||\n          docElem.msMatchesSelector)))) {\n\n          assert(function (div) {\n            // Check to see if it's possible to do matchesSelector\n            // on a disconnected node (IE 9)\n            support.disconnectedMatch = matches.call(div, \"div\");\n\n            // This should fail with an exception\n            // Gecko does not error, returns false instead\n            matches.call(div, \"[s!='']:x\");\n            rbuggyMatches.push(\"!=\", pseudos);\n          });\n        }\n\n        rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join(\"|\"));\n        rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join(\"|\"));\n\n        /* Contains\n         ---------------------------------------------------------------------- */\n        hasCompare = rnative.test(docElem.compareDocumentPosition);\n\n        // Element contains another\n        // Purposefully does not implement inclusive descendent\n        // As in, an element does not contain itself\n        contains = hasCompare || rnative.test(docElem.contains) ?\n          function (a, b) {\n            var adown = a.nodeType === 9 ? a.documentElement : a,\n              bup = b && b.parentNode;\n            return a === bup || !!( bup && bup.nodeType === 1 && (\n              adown.contains ?\n                adown.contains(bup) :\n                a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16\n              ));\n          } :\n          function (a, b) {\n            if (b) {\n              while ((b = b.parentNode)) {\n                if (b === a) {\n                  return true;\n                }\n              }\n            }\n            return false;\n          };\n\n        /* Sorting\n         ---------------------------------------------------------------------- */\n\n        // Document order sorting\n        sortOrder = hasCompare ?\n          function (a, b) {\n\n            // Flag for duplicate removal\n            if (a === b) {\n              hasDuplicate = true;\n              return 0;\n            }\n\n            // Sort on method existence if only one input has compareDocumentPosition\n            var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n            if (compare) {\n              return compare;\n            }\n\n            // Calculate position if both inputs belong to the same document\n            compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?\n              a.compareDocumentPosition(b) :\n\n              // Otherwise we know they are disconnected\n              1;\n\n            // Disconnected nodes\n            if (compare & 1 ||\n              (!support.sortDetached && b.compareDocumentPosition(a) === compare)) {\n\n              // Choose the first element that is related to our preferred document\n              if (a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) {\n                return -1;\n              }\n              if (b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) {\n                return 1;\n              }\n\n              // Maintain original order\n              return sortInput ?\n                ( indexOf.call(sortInput, a) - indexOf.call(sortInput, b) ) :\n                0;\n            }\n\n            return compare & 4 ? -1 : 1;\n          } :\n          function (a, b) {\n            // Exit early if the nodes are identical\n            if (a === b) {\n              hasDuplicate = true;\n              return 0;\n            }\n\n            var cur,\n              i = 0,\n              aup = a.parentNode,\n              bup = b.parentNode,\n              ap = [ a ],\n              bp = [ b ];\n\n            // Parentless nodes are either documents or disconnected\n            if (!aup || !bup) {\n              return a === doc ? -1 :\n                  b === doc ? 1 :\n                aup ? -1 :\n                  bup ? 1 :\n                    sortInput ?\n                      ( indexOf.call(sortInput, a) - indexOf.call(sortInput, b) ) :\n                      0;\n\n              // If the nodes are siblings, we can do a quick check\n            } else if (aup === bup) {\n              return siblingCheck(a, b);\n            }\n\n            // Otherwise we need full lists of their ancestors for comparison\n            cur = a;\n            while ((cur = cur.parentNode)) {\n              ap.unshift(cur);\n            }\n            cur = b;\n            while ((cur = cur.parentNode)) {\n              bp.unshift(cur);\n            }\n\n            // Walk down the tree looking for a discrepancy\n            while (ap[i] === bp[i]) {\n              i++;\n            }\n\n            return i ?\n              // Do a sibling check if the nodes have a common ancestor\n              siblingCheck(ap[i], bp[i]) :\n\n              // Otherwise nodes in our document sort first\n                ap[i] === preferredDoc ? -1 :\n                bp[i] === preferredDoc ? 1 :\n              0;\n          };\n\n        return doc;\n      };\n\n      Sizzle.matches = function (expr, elements) {\n        return Sizzle(expr, null, null, elements);\n      };\n\n      Sizzle.matchesSelector = function (elem, expr) {\n        // Set document vars if needed\n        if (( elem.ownerDocument || elem ) !== document) {\n          setDocument(elem);\n        }\n\n        // Make sure that attribute selectors are quoted\n        expr = expr.replace(rattributeQuotes, \"='$1']\");\n\n        if (support.matchesSelector && documentIsHTML &&\n          ( !rbuggyMatches || !rbuggyMatches.test(expr) ) &&\n          ( !rbuggyQSA || !rbuggyQSA.test(expr) )) {\n\n          try {\n            var ret = matches.call(elem, expr);\n\n            // IE 9's matchesSelector returns false on disconnected nodes\n            if (ret || support.disconnectedMatch ||\n              // As well, disconnected nodes are said to be in a document\n              // fragment in IE 9\n              elem.document && elem.document.nodeType !== 11) {\n              return ret;\n            }\n          } catch (e) {\n          }\n        }\n\n        return Sizzle(expr, document, null, [elem]).length > 0;\n      };\n\n      Sizzle.contains = function (context, elem) {\n        // Set document vars if needed\n        if (( context.ownerDocument || context ) !== document) {\n          setDocument(context);\n        }\n        return contains(context, elem);\n      };\n\n      Sizzle.attr = function (elem, name) {\n        // Set document vars if needed\n        if (( elem.ownerDocument || elem ) !== document) {\n          setDocument(elem);\n        }\n\n        var fn = Expr.attrHandle[ name.toLowerCase() ],\n        // Don't get fooled by Object.prototype properties (jQuery #13807)\n          val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ?\n            fn(elem, name, !documentIsHTML) :\n            undefined;\n\n        return val !== undefined ?\n          val :\n            support.attributes || !documentIsHTML ?\n          elem.getAttribute(name) :\n            (val = elem.getAttributeNode(name)) && val.specified ?\n          val.value :\n          null;\n      };\n\n      Sizzle.error = function (msg) {\n        throw new Error(\"Syntax error, unrecognized expression: \" + msg);\n      };\n\n      /**\n       * Document sorting and removing duplicates\n       * @param {ArrayLike} results\n       */\n      Sizzle.uniqueSort = function (results) {\n        var elem,\n          duplicates = [],\n          j = 0,\n          i = 0;\n\n        // Unless we *know* we can detect duplicates, assume their presence\n        hasDuplicate = !support.detectDuplicates;\n        sortInput = !support.sortStable && results.slice(0);\n        results.sort(sortOrder);\n\n        if (hasDuplicate) {\n          while ((elem = results[i++])) {\n            if (elem === results[ i ]) {\n              j = duplicates.push(i);\n            }\n          }\n          while (j--) {\n            results.splice(duplicates[ j ], 1);\n          }\n        }\n\n        // Clear input after sorting to release objects\n        // See https://github.com/jquery/sizzle/pull/225\n        sortInput = null;\n\n        return results;\n      };\n\n      /**\n       * Utility function for retrieving the text value of an array of DOM nodes\n       * @param {Array|Element} elem\n       */\n      getText = Sizzle.getText = function (elem) {\n        var node,\n          ret = \"\",\n          i = 0,\n          nodeType = elem.nodeType;\n\n        if (!nodeType) {\n          // If no nodeType, this is expected to be an array\n          while ((node = elem[i++])) {\n            // Do not traverse comment nodes\n            ret += getText(node);\n          }\n        } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) {\n          // Use textContent for elements\n          // innerText usage removed for consistency of new lines (jQuery #11153)\n          if (typeof elem.textContent === \"string\") {\n            return elem.textContent;\n          } else {\n            // Traverse its children\n            for (elem = elem.firstChild; elem; elem = elem.nextSibling) {\n              ret += getText(elem);\n            }\n          }\n        } else if (nodeType === 3 || nodeType === 4) {\n          return elem.nodeValue;\n        }\n        // Do not include comment or processing instruction nodes\n\n        return ret;\n      };\n\n      Expr = Sizzle.selectors = {\n\n        // Can be adjusted by the user\n        cacheLength: 50,\n\n        createPseudo: markFunction,\n\n        match: matchExpr,\n\n        attrHandle: {},\n\n        find: {},\n\n        relative: {\n          \">\": { dir: \"parentNode\", first: true },\n          \" \": { dir: \"parentNode\" },\n          \"+\": { dir: \"previousSibling\", first: true },\n          \"~\": { dir: \"previousSibling\" }\n        },\n\n        preFilter: {\n          \"ATTR\": function (match) {\n            match[1] = match[1].replace(runescape, funescape);\n\n            // Move the given value to match[3] whether quoted or unquoted\n            match[3] = ( match[4] || match[5] || \"\" ).replace(runescape, funescape);\n\n            if (match[2] === \"~=\") {\n              match[3] = \" \" + match[3] + \" \";\n            }\n\n            return match.slice(0, 4);\n          },\n\n          \"CHILD\": function (match) {\n            /* matches from matchExpr[\"CHILD\"]\n             1 type (only|nth|...)\n             2 what (child|of-type)\n             3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n             4 xn-component of xn+y argument ([+-]?\\d*n|)\n             5 sign of xn-component\n             6 x of xn-component\n             7 sign of y-component\n             8 y of y-component\n             */\n            match[1] = match[1].toLowerCase();\n\n            if (match[1].slice(0, 3) === \"nth\") {\n              // nth-* requires argument\n              if (!match[3]) {\n                Sizzle.error(match[0]);\n              }\n\n              // numeric x and y parameters for Expr.filter.CHILD\n              // remember that false/true cast respectively to 0/1\n              match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n              match[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n              // other types prohibit arguments\n            } else if (match[3]) {\n              Sizzle.error(match[0]);\n            }\n\n            return match;\n          },\n\n          \"PSEUDO\": function (match) {\n            var excess,\n              unquoted = !match[5] && match[2];\n\n            if (matchExpr[\"CHILD\"].test(match[0])) {\n              return null;\n            }\n\n            // Accept quoted arguments as-is\n            if (match[3] && match[4] !== undefined) {\n              match[2] = match[4];\n\n              // Strip excess characters from unquoted arguments\n            } else if (unquoted && rpseudo.test(unquoted) &&\n              // Get excess from tokenize (recursively)\n              (excess = tokenize(unquoted, true)) &&\n              // advance to the next closing parenthesis\n              (excess = unquoted.indexOf(\")\", unquoted.length - excess) - unquoted.length)) {\n\n              // excess is a negative index\n              match[0] = match[0].slice(0, excess);\n              match[2] = unquoted.slice(0, excess);\n            }\n\n            // Return only captures needed by the pseudo filter method (type and argument)\n            return match.slice(0, 3);\n          }\n        },\n\n        filter: {\n\n          \"TAG\": function (nodeNameSelector) {\n            var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase();\n            return nodeNameSelector === \"*\" ?\n              function () {\n                return true;\n              } :\n              function (elem) {\n                return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n              };\n          },\n\n          \"CLASS\": function (className) {\n            var pattern = classCache[ className + \" \" ];\n\n            return pattern ||\n              (pattern = new RegExp(\"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\")) &&\n              classCache(className, function (elem) {\n                return pattern.test(typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\");\n              });\n          },\n\n          \"ATTR\": function (name, operator, check) {\n            return function (elem) {\n              var result = Sizzle.attr(elem, name);\n\n              if (result == null) {\n                return operator === \"!=\";\n              }\n              if (!operator) {\n                return true;\n              }\n\n              result += \"\";\n\n              return operator === \"=\" ? result === check :\n                  operator === \"!=\" ? result !== check :\n                  operator === \"^=\" ? check && result.indexOf(check) === 0 :\n                  operator === \"*=\" ? check && result.indexOf(check) > -1 :\n                  operator === \"$=\" ? check && result.slice(-check.length) === check :\n                  operator === \"~=\" ? ( \" \" + result + \" \" ).indexOf(check) > -1 :\n                  operator === \"|=\" ? result === check || result.slice(0, check.length + 1) === check + \"-\" :\n                false;\n            };\n          },\n\n          \"CHILD\": function (type, what, argument, first, last) {\n            var simple = type.slice(0, 3) !== \"nth\",\n              forward = type.slice(-4) !== \"last\",\n              ofType = what === \"of-type\";\n\n            return first === 1 && last === 0 ?\n\n              // Shortcut for :nth-*(n)\n              function (elem) {\n                return !!elem.parentNode;\n              } :\n\n              function (elem, context, xml) {\n                var cache, outerCache, node, diff, nodeIndex, start,\n                  dir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n                  parent = elem.parentNode,\n                  name = ofType && elem.nodeName.toLowerCase(),\n                  useCache = !xml && !ofType;\n\n                if (parent) {\n\n                  // :(first|last|only)-(child|of-type)\n                  if (simple) {\n                    while (dir) {\n                      node = elem;\n                      while ((node = node[ dir ])) {\n                        if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) {\n                          return false;\n                        }\n                      }\n                      // Reverse direction for :only-* (if we haven't yet done so)\n                      start = dir = type === \"only\" && !start && \"nextSibling\";\n                    }\n                    return true;\n                  }\n\n                  start = [ forward ? parent.firstChild : parent.lastChild ];\n\n                  // non-xml :nth-child(...) stores cache data on `parent`\n                  if (forward && useCache) {\n                    // Seek `elem` from a previously-cached index\n                    outerCache = parent[ expando ] || (parent[ expando ] = {});\n                    cache = outerCache[ type ] || [];\n                    nodeIndex = cache[0] === dirruns && cache[1];\n                    diff = cache[0] === dirruns && cache[2];\n                    node = nodeIndex && parent.childNodes[ nodeIndex ];\n\n                    while ((node = ++nodeIndex && node && node[ dir ] ||\n\n                      // Fallback to seeking `elem` from the start\n                      (diff = nodeIndex = 0) || start.pop())) {\n\n                      // When found, cache indexes on `parent` and break\n                      if (node.nodeType === 1 && ++diff && node === elem) {\n                        outerCache[ type ] = [ dirruns, nodeIndex, diff ];\n                        break;\n                      }\n                    }\n\n                    // Use previously-cached element index if available\n                  } else if (useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns) {\n                    diff = cache[1];\n\n                    // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n                  } else {\n                    // Use the same loop as above to seek `elem` from the start\n                    while ((node = ++nodeIndex && node && node[ dir ] ||\n                      (diff = nodeIndex = 0) || start.pop())) {\n\n                      if (( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff) {\n                        // Cache the index of each encountered element\n                        if (useCache) {\n                          (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n                        }\n\n                        if (node === elem) {\n                          break;\n                        }\n                      }\n                    }\n                  }\n\n                  // Incorporate the offset, then check against cycle size\n                  diff -= last;\n                  return diff === first || ( diff % first === 0 && diff / first >= 0 );\n                }\n              };\n          },\n\n          \"PSEUDO\": function (pseudo, argument) {\n            // pseudo-class names are case-insensitive\n            // http://www.w3.org/TR/selectors/#pseudo-classes\n            // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n            // Remember that setFilters inherits from pseudos\n            var args,\n              fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n                Sizzle.error(\"unsupported pseudo: \" + pseudo);\n\n            // The user may use createPseudo to indicate that\n            // arguments are needed to create the filter function\n            // just as Sizzle does\n            if (fn[ expando ]) {\n              return fn(argument);\n            }\n\n            // But maintain support for old signatures\n            if (fn.length > 1) {\n              args = [ pseudo, pseudo, \"\", argument ];\n              return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ?\n                markFunction(function (seed, matches) {\n                  var idx,\n                    matched = fn(seed, argument),\n                    i = matched.length;\n                  while (i--) {\n                    idx = indexOf.call(seed, matched[i]);\n                    seed[ idx ] = !( matches[ idx ] = matched[i] );\n                  }\n                }) :\n                function (elem) {\n                  return fn(elem, 0, args);\n                };\n            }\n\n            return fn;\n          }\n        },\n\n        pseudos: {\n          // Potentially complex pseudos\n          \"not\": markFunction(function (selector) {\n            // Trim the selector passed to compile\n            // to avoid treating leading and trailing\n            // spaces as combinators\n            var input = [],\n              results = [],\n              matcher = compile(selector.replace(rtrim, \"$1\"));\n\n            return matcher[ expando ] ?\n              markFunction(function (seed, matches, context, xml) {\n                var elem,\n                  unmatched = matcher(seed, null, xml, []),\n                  i = seed.length;\n\n                // Match elements unmatched by `matcher`\n                while (i--) {\n                  if ((elem = unmatched[i])) {\n                    seed[i] = !(matches[i] = elem);\n                  }\n                }\n              }) :\n              function (elem, context, xml) {\n                input[0] = elem;\n                matcher(input, null, xml, results);\n                return !results.pop();\n              };\n          }),\n\n          \"has\": markFunction(function (selector) {\n            return function (elem) {\n              return Sizzle(selector, elem).length > 0;\n            };\n          }),\n\n          \"contains\": markFunction(function (text) {\n            return function (elem) {\n              return ( elem.textContent || elem.innerText || getText(elem) ).indexOf(text) > -1;\n            };\n          }),\n\n          // \"Whether an element is represented by a :lang() selector\n          // is based solely on the element's language value\n          // being equal to the identifier C,\n          // or beginning with the identifier C immediately followed by \"-\".\n          // The matching of C against the element's language value is performed case-insensitively.\n          // The identifier C does not have to be a valid language name.\"\n          // http://www.w3.org/TR/selectors/#lang-pseudo\n          \"lang\": markFunction(function (lang) {\n            // lang value must be a valid identifier\n            if (!ridentifier.test(lang || \"\")) {\n              Sizzle.error(\"unsupported lang: \" + lang);\n            }\n            lang = lang.replace(runescape, funescape).toLowerCase();\n            return function (elem) {\n              var elemLang;\n              do {\n                if ((elemLang = documentIsHTML ?\n                  elem.lang :\n                  elem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\"))) {\n\n                  elemLang = elemLang.toLowerCase();\n                  return elemLang === lang || elemLang.indexOf(lang + \"-\") === 0;\n                }\n              } while ((elem = elem.parentNode) && elem.nodeType === 1);\n              return false;\n            };\n          }),\n\n          // Miscellaneous\n          \"target\": function (elem) {\n            var hash = window.location && window.location.hash;\n            return hash && hash.slice(1) === elem.id;\n          },\n\n          \"root\": function (elem) {\n            return elem === docElem;\n          },\n\n          \"focus\": function (elem) {\n            return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n          },\n\n          // Boolean properties\n          \"enabled\": function (elem) {\n            return elem.disabled === false;\n          },\n\n          \"disabled\": function (elem) {\n            return elem.disabled === true;\n          },\n\n          \"checked\": function (elem) {\n            // In CSS3, :checked should return both checked and selected elements\n            // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n            var nodeName = elem.nodeName.toLowerCase();\n            return (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n          },\n\n          \"selected\": function (elem) {\n            // Accessing this property makes selected-by-default\n            // options in Safari work properly\n            if (elem.parentNode) {\n              elem.parentNode.selectedIndex;\n            }\n\n            return elem.selected === true;\n          },\n\n          // Contents\n          \"empty\": function (elem) {\n            // http://www.w3.org/TR/selectors/#empty-pseudo\n            // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n            //   but not by others (comment: 8; processing instruction: 7; etc.)\n            // nodeType < 6 works because attributes (2) do not appear as children\n            for (elem = elem.firstChild; elem; elem = elem.nextSibling) {\n              if (elem.nodeType < 6) {\n                return false;\n              }\n            }\n            return true;\n          },\n\n          \"parent\": function (elem) {\n            return !Expr.pseudos[\"empty\"](elem);\n          },\n\n          // Element/input types\n          \"header\": function (elem) {\n            return rheader.test(elem.nodeName);\n          },\n\n          \"input\": function (elem) {\n            return rinputs.test(elem.nodeName);\n          },\n\n          \"button\": function (elem) {\n            var name = elem.nodeName.toLowerCase();\n            return name === \"input\" && elem.type === \"button\" || name === \"button\";\n          },\n\n          \"text\": function (elem) {\n            var attr;\n            return elem.nodeName.toLowerCase() === \"input\" &&\n              elem.type === \"text\" &&\n\n              // Support: IE<8\n              // New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n              ( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === \"text\" );\n          },\n\n          // Position-in-collection\n          \"first\": createPositionalPseudo(function () {\n            return [ 0 ];\n          }),\n\n          \"last\": createPositionalPseudo(function (matchIndexes, length) {\n            return [ length - 1 ];\n          }),\n\n          \"eq\": createPositionalPseudo(function (matchIndexes, length, argument) {\n            return [ argument < 0 ? argument + length : argument ];\n          }),\n\n          \"even\": createPositionalPseudo(function (matchIndexes, length) {\n            var i = 0;\n            for (; i < length; i += 2) {\n              matchIndexes.push(i);\n            }\n            return matchIndexes;\n          }),\n\n          \"odd\": createPositionalPseudo(function (matchIndexes, length) {\n            var i = 1;\n            for (; i < length; i += 2) {\n              matchIndexes.push(i);\n            }\n            return matchIndexes;\n          }),\n\n          \"lt\": createPositionalPseudo(function (matchIndexes, length, argument) {\n            var i = argument < 0 ? argument + length : argument;\n            for (; --i >= 0;) {\n              matchIndexes.push(i);\n            }\n            return matchIndexes;\n          }),\n\n          \"gt\": createPositionalPseudo(function (matchIndexes, length, argument) {\n            var i = argument < 0 ? argument + length : argument;\n            for (; ++i < length;) {\n              matchIndexes.push(i);\n            }\n            return matchIndexes;\n          })\n        }\n      };\n\n      Expr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\n      for (i in { radio: true, checkbox: true, file: true, password: true, image: true }) {\n        Expr.pseudos[ i ] = createInputPseudo(i);\n      }\n      for (i in { submit: true, reset: true }) {\n        Expr.pseudos[ i ] = createButtonPseudo(i);\n      }\n\n// Easy API for creating new setFilters\n      function setFilters() {\n      }\n\n      setFilters.prototype = Expr.filters = Expr.pseudos;\n      Expr.setFilters = new setFilters();\n\n      function tokenize(selector, parseOnly) {\n        var matched, match, tokens, type,\n          soFar, groups, preFilters,\n          cached = tokenCache[ selector + \" \" ];\n\n        if (cached) {\n          return parseOnly ? 0 : cached.slice(0);\n        }\n\n        soFar = selector;\n        groups = [];\n        preFilters = Expr.preFilter;\n\n        while (soFar) {\n\n          // Comma and first run\n          if (!matched || (match = rcomma.exec(soFar))) {\n            if (match) {\n              // Don't consume trailing commas as valid\n              soFar = soFar.slice(match[0].length) || soFar;\n            }\n            groups.push((tokens = []));\n          }\n\n          matched = false;\n\n          // Combinators\n          if ((match = rcombinators.exec(soFar))) {\n            matched = match.shift();\n            tokens.push({\n              value: matched,\n              // Cast descendant combinators to space\n              type: match[0].replace(rtrim, \" \")\n            });\n            soFar = soFar.slice(matched.length);\n          }\n\n          // Filters\n          for (type in Expr.filter) {\n            if ((match = matchExpr[ type ].exec(soFar)) && (!preFilters[ type ] ||\n              (match = preFilters[ type ](match)))) {\n              matched = match.shift();\n              tokens.push({\n                value: matched,\n                type: type,\n                matches: match\n              });\n              soFar = soFar.slice(matched.length);\n            }\n          }\n\n          if (!matched) {\n            break;\n          }\n        }\n\n        // Return the length of the invalid excess\n        // if we're just parsing\n        // Otherwise, throw an error or return tokens\n        return parseOnly ?\n          soFar.length :\n          soFar ?\n            Sizzle.error(selector) :\n            // Cache the tokens\n            tokenCache(selector, groups).slice(0);\n      }\n\n      function toSelector(tokens) {\n        var i = 0,\n          len = tokens.length,\n          selector = \"\";\n        for (; i < len; i++) {\n          selector += tokens[i].value;\n        }\n        return selector;\n      }\n\n      function addCombinator(matcher, combinator, base) {\n        var dir = combinator.dir,\n          checkNonElements = base && dir === \"parentNode\",\n          doneName = done++;\n\n        return combinator.first ?\n          // Check against closest ancestor/preceding element\n          function (elem, context, xml) {\n            while ((elem = elem[ dir ])) {\n              if (elem.nodeType === 1 || checkNonElements) {\n                return matcher(elem, context, xml);\n              }\n            }\n          } :\n\n          // Check against all ancestor/preceding elements\n          function (elem, context, xml) {\n            var oldCache, outerCache,\n              newCache = [ dirruns, doneName ];\n\n            // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n            if (xml) {\n              while ((elem = elem[ dir ])) {\n                if (elem.nodeType === 1 || checkNonElements) {\n                  if (matcher(elem, context, xml)) {\n                    return true;\n                  }\n                }\n              }\n            } else {\n              while ((elem = elem[ dir ])) {\n                if (elem.nodeType === 1 || checkNonElements) {\n                  outerCache = elem[ expando ] || (elem[ expando ] = {});\n                  if ((oldCache = outerCache[ dir ]) &&\n                    oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName) {\n\n                    // Assign to newCache so results back-propagate to previous elements\n                    return (newCache[ 2 ] = oldCache[ 2 ]);\n                  } else {\n                    // Reuse newcache so results back-propagate to previous elements\n                    outerCache[ dir ] = newCache;\n\n                    // A match means we're done; a fail means we have to keep checking\n                    if ((newCache[ 2 ] = matcher(elem, context, xml))) {\n                      return true;\n                    }\n                  }\n                }\n              }\n            }\n          };\n      }\n\n      function elementMatcher(matchers) {\n        return matchers.length > 1 ?\n          function (elem, context, xml) {\n            var i = matchers.length;\n            while (i--) {\n              if (!matchers[i](elem, context, xml)) {\n                return false;\n              }\n            }\n            return true;\n          } :\n          matchers[0];\n      }\n\n      function condense(unmatched, map, filter, context, xml) {\n        var elem,\n          newUnmatched = [],\n          i = 0,\n          len = unmatched.length,\n          mapped = map != null;\n\n        for (; i < len; i++) {\n          if ((elem = unmatched[i])) {\n            if (!filter || filter(elem, context, xml)) {\n              newUnmatched.push(elem);\n              if (mapped) {\n                map.push(i);\n              }\n            }\n          }\n        }\n\n        return newUnmatched;\n      }\n\n      function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) {\n        if (postFilter && !postFilter[ expando ]) {\n          postFilter = setMatcher(postFilter);\n        }\n        if (postFinder && !postFinder[ expando ]) {\n          postFinder = setMatcher(postFinder, postSelector);\n        }\n        return markFunction(function (seed, results, context, xml) {\n          var temp, i, elem,\n            preMap = [],\n            postMap = [],\n            preexisting = results.length,\n\n          // Get initial elements from seed or context\n            elems = seed || multipleContexts(selector || \"*\", context.nodeType ? [ context ] : context, []),\n\n          // Prefilter to get matcher input, preserving a map for seed-results synchronization\n            matcherIn = preFilter && ( seed || !selector ) ?\n              condense(elems, preMap, preFilter, context, xml) :\n              elems,\n\n            matcherOut = matcher ?\n              // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n                postFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n              // ...intermediate processing is necessary\n              [] :\n\n              // ...otherwise use results directly\n              results :\n              matcherIn;\n\n          // Find primary matches\n          if (matcher) {\n            matcher(matcherIn, matcherOut, context, xml);\n          }\n\n          // Apply postFilter\n          if (postFilter) {\n            temp = condense(matcherOut, postMap);\n            postFilter(temp, [], context, xml);\n\n            // Un-match failing elements by moving them back to matcherIn\n            i = temp.length;\n            while (i--) {\n              if ((elem = temp[i])) {\n                matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n              }\n            }\n          }\n\n          if (seed) {\n            if (postFinder || preFilter) {\n              if (postFinder) {\n                // Get the final matcherOut by condensing this intermediate into postFinder contexts\n                temp = [];\n                i = matcherOut.length;\n                while (i--) {\n                  if ((elem = matcherOut[i])) {\n                    // Restore matcherIn since elem is not yet a final match\n                    temp.push((matcherIn[i] = elem));\n                  }\n                }\n                postFinder(null, (matcherOut = []), temp, xml);\n              }\n\n              // Move matched elements from seed to results to keep them synchronized\n              i = matcherOut.length;\n              while (i--) {\n                if ((elem = matcherOut[i]) &&\n                  (temp = postFinder ? indexOf.call(seed, elem) : preMap[i]) > -1) {\n\n                  seed[temp] = !(results[temp] = elem);\n                }\n              }\n            }\n\n            // Add elements to results, through postFinder if defined\n          } else {\n            matcherOut = condense(\n                matcherOut === results ?\n                matcherOut.splice(preexisting, matcherOut.length) :\n                matcherOut\n            );\n            if (postFinder) {\n              postFinder(null, results, matcherOut, xml);\n            } else {\n              push.apply(results, matcherOut);\n            }\n          }\n        });\n      }\n\n      function matcherFromTokens(tokens) {\n        var checkContext, matcher, j,\n          len = tokens.length,\n          leadingRelative = Expr.relative[ tokens[0].type ],\n          implicitRelative = leadingRelative || Expr.relative[\" \"],\n          i = leadingRelative ? 1 : 0,\n\n        // The foundational matcher ensures that elements are reachable from top-level context(s)\n          matchContext = addCombinator(function (elem) {\n            return elem === checkContext;\n          }, implicitRelative, true),\n          matchAnyContext = addCombinator(function (elem) {\n            return indexOf.call(checkContext, elem) > -1;\n          }, implicitRelative, true),\n          matchers = [ function (elem, context, xml) {\n            return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n              (checkContext = context).nodeType ?\n                matchContext(elem, context, xml) :\n                matchAnyContext(elem, context, xml) );\n          } ];\n\n        for (; i < len; i++) {\n          if ((matcher = Expr.relative[ tokens[i].type ])) {\n            matchers = [ addCombinator(elementMatcher(matchers), matcher) ];\n          } else {\n            matcher = Expr.filter[ tokens[i].type ].apply(null, tokens[i].matches);\n\n            // Return special upon seeing a positional matcher\n            if (matcher[ expando ]) {\n              // Find the next relative operator (if any) for proper handling\n              j = ++i;\n              for (; j < len; j++) {\n                if (Expr.relative[ tokens[j].type ]) {\n                  break;\n                }\n              }\n              return setMatcher(\n                  i > 1 && elementMatcher(matchers),\n                  i > 1 && toSelector(\n                  // If the preceding token was a descendant combinator, insert an implicit any-element `*`\n                  tokens.slice(0, i - 1).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n                ).replace(rtrim, \"$1\"),\n                matcher,\n                  i < j && matcherFromTokens(tokens.slice(i, j)),\n                  j < len && matcherFromTokens((tokens = tokens.slice(j))),\n                  j < len && toSelector(tokens)\n              );\n            }\n            matchers.push(matcher);\n          }\n        }\n\n        return elementMatcher(matchers);\n      }\n\n      function matcherFromGroupMatchers(elementMatchers, setMatchers) {\n        var bySet = setMatchers.length > 0,\n          byElement = elementMatchers.length > 0,\n          superMatcher = function (seed, context, xml, results, outermost) {\n            var elem, j, matcher,\n              matchedCount = 0,\n              i = \"0\",\n              unmatched = seed && [],\n              setMatched = [],\n              contextBackup = outermostContext,\n            // We must always have either seed elements or outermost context\n              elems = seed || byElement && Expr.find[\"TAG\"](\"*\", outermost),\n            // Use integer dirruns iff this is the outermost matcher\n              dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),\n              len = elems.length;\n\n            if (outermost) {\n              outermostContext = context !== document && context;\n            }\n\n            // Add elements passing elementMatchers directly to results\n            // Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n            // Support: IE<9, Safari\n            // Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n            for (; i !== len && (elem = elems[i]) != null; i++) {\n              if (byElement && elem) {\n                j = 0;\n                while ((matcher = elementMatchers[j++])) {\n                  if (matcher(elem, context, xml)) {\n                    results.push(elem);\n                    break;\n                  }\n                }\n                if (outermost) {\n                  dirruns = dirrunsUnique;\n                }\n              }\n\n              // Track unmatched elements for set filters\n              if (bySet) {\n                // They will have gone through all possible matchers\n                if ((elem = !matcher && elem)) {\n                  matchedCount--;\n                }\n\n                // Lengthen the array for every element, matched or not\n                if (seed) {\n                  unmatched.push(elem);\n                }\n              }\n            }\n\n            // Apply set filters to unmatched elements\n            matchedCount += i;\n            if (bySet && i !== matchedCount) {\n              j = 0;\n              while ((matcher = setMatchers[j++])) {\n                matcher(unmatched, setMatched, context, xml);\n              }\n\n              if (seed) {\n                // Reintegrate element matches to eliminate the need for sorting\n                if (matchedCount > 0) {\n                  while (i--) {\n                    if (!(unmatched[i] || setMatched[i])) {\n                      setMatched[i] = pop.call(results);\n                    }\n                  }\n                }\n\n                // Discard index placeholder values to get only actual matches\n                setMatched = condense(setMatched);\n              }\n\n              // Add matches to results\n              push.apply(results, setMatched);\n\n              // Seedless set matches succeeding multiple successful matchers stipulate sorting\n              if (outermost && !seed && setMatched.length > 0 &&\n                ( matchedCount + setMatchers.length ) > 1) {\n\n                Sizzle.uniqueSort(results);\n              }\n            }\n\n            // Override manipulation of globals by nested matchers\n            if (outermost) {\n              dirruns = dirrunsUnique;\n              outermostContext = contextBackup;\n            }\n\n            return unmatched;\n          };\n\n        return bySet ?\n          markFunction(superMatcher) :\n          superMatcher;\n      }\n\n      compile = Sizzle.compile = function (selector, group /* Internal Use Only */) {\n        var i,\n          setMatchers = [],\n          elementMatchers = [],\n          cached = compilerCache[ selector + \" \" ];\n\n        if (!cached) {\n          // Generate a function of recursive functions that can be used to check each element\n          if (!group) {\n            group = tokenize(selector);\n          }\n          i = group.length;\n          while (i--) {\n            cached = matcherFromTokens(group[i]);\n            if (cached[ expando ]) {\n              setMatchers.push(cached);\n            } else {\n              elementMatchers.push(cached);\n            }\n          }\n\n          // Cache the compiled function\n          cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers));\n        }\n        return cached;\n      };\n\n      function multipleContexts(selector, contexts, results) {\n        var i = 0,\n          len = contexts.length;\n        for (; i < len; i++) {\n          Sizzle(selector, contexts[i], results);\n        }\n        return results;\n      }\n\n      function select(selector, context, results, seed) {\n        var i, tokens, token, type, find,\n          match = tokenize(selector);\n\n        if (!seed) {\n          // Try to minimize operations if there is only one group\n          if (match.length === 1) {\n\n            // Take a shortcut and set the context if the root selector is an ID\n            tokens = match[0] = match[0].slice(0);\n            if (tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n              support.getById && context.nodeType === 9 && documentIsHTML &&\n              Expr.relative[ tokens[1].type ]) {\n\n              context = ( Expr.find[\"ID\"](token.matches[0].replace(runescape, funescape), context) || [] )[0];\n              if (!context) {\n                return results;\n              }\n              selector = selector.slice(tokens.shift().value.length);\n            }\n\n            // Fetch a seed set for right-to-left matching\n            i = matchExpr[\"needsContext\"].test(selector) ? 0 : tokens.length;\n            while (i--) {\n              token = tokens[i];\n\n              // Abort if we hit a combinator\n              if (Expr.relative[ (type = token.type) ]) {\n                break;\n              }\n              if ((find = Expr.find[ type ])) {\n                // Search, expanding context for leading sibling combinators\n                if ((seed = find(\n                  token.matches[0].replace(runescape, funescape),\n                    rsibling.test(tokens[0].type) && testContext(context.parentNode) || context\n                ))) {\n\n                  // If seed is empty or no tokens remain, we can return early\n                  tokens.splice(i, 1);\n                  selector = seed.length && toSelector(tokens);\n                  if (!selector) {\n                    push.apply(results, seed);\n                    return results;\n                  }\n\n                  break;\n                }\n              }\n            }\n          }\n        }\n\n        // Compile and execute a filtering function\n        // Provide `match` to avoid retokenization if we modified the selector above\n        compile(selector, match)(\n          seed,\n          context,\n          !documentIsHTML,\n          results,\n            rsibling.test(selector) && testContext(context.parentNode) || context\n        );\n        return results;\n      }\n\n// One-time assignments\n\n// Sort stability\n      support.sortStable = expando.split(\"\").sort(sortOrder).join(\"\") === expando;\n\n// Support: Chrome<14\n// Always assume duplicates if they aren't passed to the comparison function\n      support.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\n      setDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\n      support.sortDetached = assert(function (div1) {\n        // Should return 1, but returns 4 (following)\n        return div1.compareDocumentPosition(document.createElement(\"div\")) & 1;\n      });\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\n      if (!assert(function (div) {\n        div.innerHTML = \"<a href='#'></a>\";\n        return div.firstChild.getAttribute(\"href\") === \"#\";\n      })) {\n        addHandle(\"type|href|height|width\", function (elem, name, isXML) {\n          if (!isXML) {\n            return elem.getAttribute(name, name.toLowerCase() === \"type\" ? 1 : 2);\n          }\n        });\n      }\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\n      if (!support.attributes || !assert(function (div) {\n        div.innerHTML = \"<input/>\";\n        div.firstChild.setAttribute(\"value\", \"\");\n        return div.firstChild.getAttribute(\"value\") === \"\";\n      })) {\n        addHandle(\"value\", function (elem, name, isXML) {\n          if (!isXML && elem.nodeName.toLowerCase() === \"input\") {\n            return elem.defaultValue;\n          }\n        });\n      }\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\n      if (!assert(function (div) {\n        return div.getAttribute(\"disabled\") == null;\n      })) {\n        addHandle(booleans, function (elem, name, isXML) {\n          var val;\n          if (!isXML) {\n            return elem[ name ] === true ? name.toLowerCase() :\n                (val = elem.getAttributeNode(name)) && val.specified ?\n              val.value :\n              null;\n          }\n        });\n      }\n\n      return Sizzle;\n\n    })(window);\n\n\n  jQuery.find = Sizzle;\n  jQuery.expr = Sizzle.selectors;\n  jQuery.expr[\":\"] = jQuery.expr.pseudos;\n  jQuery.unique = Sizzle.uniqueSort;\n  jQuery.text = Sizzle.getText;\n  jQuery.isXMLDoc = Sizzle.isXML;\n  jQuery.contains = Sizzle.contains;\n\n\n  var rneedsContext = jQuery.expr.match.needsContext;\n\n  var rsingleTag = (/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/);\n\n\n  var risSimple = /^.[^:#\\[\\.,]*$/;\n\n// Implement the identical functionality for filter and not\n  function winnow(elements, qualifier, not) {\n    if (jQuery.isFunction(qualifier)) {\n      return jQuery.grep(elements, function (elem, i) {\n        /* jshint -W018 */\n        return !!qualifier.call(elem, i, elem) !== not;\n      });\n\n    }\n\n    if (qualifier.nodeType) {\n      return jQuery.grep(elements, function (elem) {\n        return ( elem === qualifier ) !== not;\n      });\n\n    }\n\n    if (typeof qualifier === \"string\") {\n      if (risSimple.test(qualifier)) {\n        return jQuery.filter(qualifier, elements, not);\n      }\n\n      qualifier = jQuery.filter(qualifier, elements);\n    }\n\n    return jQuery.grep(elements, function (elem) {\n      return ( indexOf.call(qualifier, elem) >= 0 ) !== not;\n    });\n  }\n\n  jQuery.filter = function (expr, elems, not) {\n    var elem = elems[ 0 ];\n\n    if (not) {\n      expr = \":not(\" + expr + \")\";\n    }\n\n    return elems.length === 1 && elem.nodeType === 1 ?\n      jQuery.find.matchesSelector(elem, expr) ? [ elem ] : [] :\n      jQuery.find.matches(expr, jQuery.grep(elems, function (elem) {\n        return elem.nodeType === 1;\n      }));\n  };\n\n  jQuery.fn.extend({\n    find: function (selector) {\n      var i,\n        len = this.length,\n        ret = [],\n        self = this;\n\n      if (typeof selector !== \"string\") {\n        return this.pushStack(jQuery(selector).filter(function () {\n          for (i = 0; i < len; i++) {\n            if (jQuery.contains(self[ i ], this)) {\n              return true;\n            }\n          }\n        }));\n      }\n\n      for (i = 0; i < len; i++) {\n        jQuery.find(selector, self[ i ], ret);\n      }\n\n      // Needed because $( selector, context ) becomes $( context ).find( selector )\n      ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);\n      ret.selector = this.selector ? this.selector + \" \" + selector : selector;\n      return ret;\n    },\n    filter: function (selector) {\n      return this.pushStack(winnow(this, selector || [], false));\n    },\n    not: function (selector) {\n      return this.pushStack(winnow(this, selector || [], true));\n    },\n    is: function (selector) {\n      return !!winnow(\n        this,\n\n        // If this is a positional/relative selector, check membership in the returned set\n        // so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n          typeof selector === \"string\" && rneedsContext.test(selector) ?\n          jQuery(selector) :\n          selector || [],\n        false\n      ).length;\n    }\n  });\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\n  var rootjQuery,\n\n  // A simple way to check for HTML strings\n  // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n  // Strict HTML recognition (#11290: must start with <)\n    rquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n    init = jQuery.fn.init = function (selector, context) {\n      var match, elem;\n\n      // HANDLE: $(\"\"), $(null), $(undefined), $(false)\n      if (!selector) {\n        return this;\n      }\n\n      // Handle HTML strings\n      if (typeof selector === \"string\") {\n        if (selector[0] === \"<\" && selector[ selector.length - 1 ] === \">\" && selector.length >= 3) {\n          // Assume that strings that start and end with <> are HTML and skip the regex check\n          match = [ null, selector, null ];\n\n        } else {\n          match = rquickExpr.exec(selector);\n        }\n\n        // Match html or make sure no context is specified for #id\n        if (match && (match[1] || !context)) {\n\n          // HANDLE: $(html) -> $(array)\n          if (match[1]) {\n            context = context instanceof jQuery ? context[0] : context;\n\n            // scripts is true for back-compat\n            // Intentionally let the error be thrown if parseHTML is not present\n            jQuery.merge(this, jQuery.parseHTML(\n              match[1],\n                context && context.nodeType ? context.ownerDocument || context : document,\n              true\n            ));\n\n            // HANDLE: $(html, props)\n            if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {\n              for (match in context) {\n                // Properties of context are called as methods if possible\n                if (jQuery.isFunction(this[ match ])) {\n                  this[ match ](context[ match ]);\n\n                  // ...and otherwise set as attributes\n                } else {\n                  this.attr(match, context[ match ]);\n                }\n              }\n            }\n\n            return this;\n\n            // HANDLE: $(#id)\n          } else {\n            elem = document.getElementById(match[2]);\n\n            // Check parentNode to catch when Blackberry 4.6 returns\n            // nodes that are no longer in the document #6963\n            if (elem && elem.parentNode) {\n              // Inject the element directly into the jQuery object\n              this.length = 1;\n              this[0] = elem;\n            }\n\n            this.context = document;\n            this.selector = selector;\n            return this;\n          }\n\n          // HANDLE: $(expr, $(...))\n        } else if (!context || context.jquery) {\n          return ( context || rootjQuery ).find(selector);\n\n          // HANDLE: $(expr, context)\n          // (which is just equivalent to: $(context).find(expr)\n        } else {\n          return this.constructor(context).find(selector);\n        }\n\n        // HANDLE: $(DOMElement)\n      } else if (selector.nodeType) {\n        this.context = this[0] = selector;\n        this.length = 1;\n        return this;\n\n        // HANDLE: $(function)\n        // Shortcut for document ready\n      } else if (jQuery.isFunction(selector)) {\n        return typeof rootjQuery.ready !== \"undefined\" ?\n          rootjQuery.ready(selector) :\n          // Execute immediately if ready is not present\n          selector(jQuery);\n      }\n\n      if (selector.selector !== undefined) {\n        this.selector = selector.selector;\n        this.context = selector.context;\n      }\n\n      return jQuery.makeArray(selector, this);\n    };\n\n// Give the init function the jQuery prototype for later instantiation\n  init.prototype = jQuery.fn;\n\n// Initialize central reference\n  rootjQuery = jQuery(document);\n\n\n  var rparentsprev = /^(?:parents|prev(?:Until|All))/,\n  // methods guaranteed to produce a unique set when starting from a unique set\n    guaranteedUnique = {\n      children: true,\n      contents: true,\n      next: true,\n      prev: true\n    };\n\n  jQuery.extend({\n    dir: function (elem, dir, until) {\n      var matched = [],\n        truncate = until !== undefined;\n\n      while ((elem = elem[ dir ]) && elem.nodeType !== 9) {\n        if (elem.nodeType === 1) {\n          if (truncate && jQuery(elem).is(until)) {\n            break;\n          }\n          matched.push(elem);\n        }\n      }\n      return matched;\n    },\n\n    sibling: function (n, elem) {\n      var matched = [];\n\n      for (; n; n = n.nextSibling) {\n        if (n.nodeType === 1 && n !== elem) {\n          matched.push(n);\n        }\n      }\n\n      return matched;\n    }\n  });\n\n  jQuery.fn.extend({\n    has: function (target) {\n      var targets = jQuery(target, this),\n        l = targets.length;\n\n      return this.filter(function () {\n        var i = 0;\n        for (; i < l; i++) {\n          if (jQuery.contains(this, targets[i])) {\n            return true;\n          }\n        }\n      });\n    },\n\n    closest: function (selectors, context) {\n      var cur,\n        i = 0,\n        l = this.length,\n        matched = [],\n        pos = rneedsContext.test(selectors) || typeof selectors !== \"string\" ?\n          jQuery(selectors, context || this.context) :\n          0;\n\n      for (; i < l; i++) {\n        for (cur = this[i]; cur && cur !== context; cur = cur.parentNode) {\n          // Always skip document fragments\n          if (cur.nodeType < 11 && (pos ?\n            pos.index(cur) > -1 :\n\n            // Don't pass non-elements to Sizzle\n            cur.nodeType === 1 &&\n            jQuery.find.matchesSelector(cur, selectors))) {\n\n            matched.push(cur);\n            break;\n          }\n        }\n      }\n\n      return this.pushStack(matched.length > 1 ? jQuery.unique(matched) : matched);\n    },\n\n    // Determine the position of an element within\n    // the matched set of elements\n    index: function (elem) {\n\n      // No argument, return index in parent\n      if (!elem) {\n        return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n      }\n\n      // index in selector\n      if (typeof elem === \"string\") {\n        return indexOf.call(jQuery(elem), this[ 0 ]);\n      }\n\n      // Locate the position of the desired element\n      return indexOf.call(this,\n\n        // If it receives a jQuery object, the first element is used\n        elem.jquery ? elem[ 0 ] : elem\n      );\n    },\n\n    add: function (selector, context) {\n      return this.pushStack(\n        jQuery.unique(\n          jQuery.merge(this.get(), jQuery(selector, context))\n        )\n      );\n    },\n\n    addBack: function (selector) {\n      return this.add(selector == null ?\n          this.prevObject : this.prevObject.filter(selector)\n      );\n    }\n  });\n\n  function sibling(cur, dir) {\n    while ((cur = cur[dir]) && cur.nodeType !== 1) {\n    }\n    return cur;\n  }\n\n  jQuery.each({\n    parent: function (elem) {\n      var parent = elem.parentNode;\n      return parent && parent.nodeType !== 11 ? parent : null;\n    },\n    parents: function (elem) {\n      return jQuery.dir(elem, \"parentNode\");\n    },\n    parentsUntil: function (elem, i, until) {\n      return jQuery.dir(elem, \"parentNode\", until);\n    },\n    next: function (elem) {\n      return sibling(elem, \"nextSibling\");\n    },\n    prev: function (elem) {\n      return sibling(elem, \"previousSibling\");\n    },\n    nextAll: function (elem) {\n      return jQuery.dir(elem, \"nextSibling\");\n    },\n    prevAll: function (elem) {\n      return jQuery.dir(elem, \"previousSibling\");\n    },\n    nextUntil: function (elem, i, until) {\n      return jQuery.dir(elem, \"nextSibling\", until);\n    },\n    prevUntil: function (elem, i, until) {\n      return jQuery.dir(elem, \"previousSibling\", until);\n    },\n    siblings: function (elem) {\n      return jQuery.sibling(( elem.parentNode || {} ).firstChild, elem);\n    },\n    children: function (elem) {\n      return jQuery.sibling(elem.firstChild);\n    },\n    contents: function (elem) {\n      return elem.contentDocument || jQuery.merge([], elem.childNodes);\n    }\n  }, function (name, fn) {\n    jQuery.fn[ name ] = function (until, selector) {\n      var matched = jQuery.map(this, fn, until);\n\n      if (name.slice(-5) !== \"Until\") {\n        selector = until;\n      }\n\n      if (selector && typeof selector === \"string\") {\n        matched = jQuery.filter(selector, matched);\n      }\n\n      if (this.length > 1) {\n        // Remove duplicates\n        if (!guaranteedUnique[ name ]) {\n          jQuery.unique(matched);\n        }\n\n        // Reverse order for parents* and prev-derivatives\n        if (rparentsprev.test(name)) {\n          matched.reverse();\n        }\n      }\n\n      return this.pushStack(matched);\n    };\n  });\n  var rnotwhite = (/\\S+/g);\n\n\n// String to Object options format cache\n  var optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\n  function createOptions(options) {\n    var object = optionsCache[ options ] = {};\n    jQuery.each(options.match(rnotwhite) || [], function (_, flag) {\n      object[ flag ] = true;\n    });\n    return object;\n  }\n\n  /*\n   * Create a callback list using the following parameters:\n   *\n   *\toptions: an optional list of space-separated options that will change how\n   *\t\t\tthe callback list behaves or a more traditional option object\n   *\n   * By default a callback list will act like an event callback list and can be\n   * \"fired\" multiple times.\n   *\n   * Possible options:\n   *\n   *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n   *\n   *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n   *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n   *\t\t\t\t\tvalues (like a Deferred)\n   *\n   *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n   *\n   *\tstopOnFalse:\tinterrupt callings when a callback returns false\n   *\n   */\n  jQuery.Callbacks = function (options) {\n\n    // Convert options from String-formatted to Object-formatted if needed\n    // (we check in cache first)\n    options = typeof options === \"string\" ?\n      ( optionsCache[ options ] || createOptions(options) ) :\n      jQuery.extend({}, options);\n\n    var // Last fire value (for non-forgettable lists)\n      memory,\n    // Flag to know if list was already fired\n      fired,\n    // Flag to know if list is currently firing\n      firing,\n    // First callback to fire (used internally by add and fireWith)\n      firingStart,\n    // End of the loop when firing\n      firingLength,\n    // Index of currently firing callback (modified by remove if needed)\n      firingIndex,\n    // Actual callback list\n      list = [],\n    // Stack of fire calls for repeatable lists\n      stack = !options.once && [],\n    // Fire callbacks\n      fire = function (data) {\n        memory = options.memory && data;\n        fired = true;\n        firingIndex = firingStart || 0;\n        firingStart = 0;\n        firingLength = list.length;\n        firing = true;\n        for (; list && firingIndex < firingLength; firingIndex++) {\n          if (list[ firingIndex ].apply(data[ 0 ], data[ 1 ]) === false && options.stopOnFalse) {\n            memory = false; // To prevent further calls using add\n            break;\n          }\n        }\n        firing = false;\n        if (list) {\n          if (stack) {\n            if (stack.length) {\n              fire(stack.shift());\n            }\n          } else if (memory) {\n            list = [];\n          } else {\n            self.disable();\n          }\n        }\n      },\n    // Actual Callbacks object\n      self = {\n        // Add a callback or a collection of callbacks to the list\n        add: function () {\n          if (list) {\n            // First, we save the current length\n            var start = list.length;\n            (function add(args) {\n              jQuery.each(args, function (_, arg) {\n                var type = jQuery.type(arg);\n                if (type === \"function\") {\n                  if (!options.unique || !self.has(arg)) {\n                    list.push(arg);\n                  }\n                } else if (arg && arg.length && type !== \"string\") {\n                  // Inspect recursively\n                  add(arg);\n                }\n              });\n            })(arguments);\n            // Do we need to add the callbacks to the\n            // current firing batch?\n            if (firing) {\n              firingLength = list.length;\n              // With memory, if we're not firing then\n              // we should call right away\n            } else if (memory) {\n              firingStart = start;\n              fire(memory);\n            }\n          }\n          return this;\n        },\n        // Remove a callback from the list\n        remove: function () {\n          if (list) {\n            jQuery.each(arguments, function (_, arg) {\n              var index;\n              while (( index = jQuery.inArray(arg, list, index) ) > -1) {\n                list.splice(index, 1);\n                // Handle firing indexes\n                if (firing) {\n                  if (index <= firingLength) {\n                    firingLength--;\n                  }\n                  if (index <= firingIndex) {\n                    firingIndex--;\n                  }\n                }\n              }\n            });\n          }\n          return this;\n        },\n        // Check if a given callback is in the list.\n        // If no argument is given, return whether or not list has callbacks attached.\n        has: function (fn) {\n          return fn ? jQuery.inArray(fn, list) > -1 : !!( list && list.length );\n        },\n        // Remove all callbacks from the list\n        empty: function () {\n          list = [];\n          firingLength = 0;\n          return this;\n        },\n        // Have the list do nothing anymore\n        disable: function () {\n          list = stack = memory = undefined;\n          return this;\n        },\n        // Is it disabled?\n        disabled: function () {\n          return !list;\n        },\n        // Lock the list in its current state\n        lock: function () {\n          stack = undefined;\n          if (!memory) {\n            self.disable();\n          }\n          return this;\n        },\n        // Is it locked?\n        locked: function () {\n          return !stack;\n        },\n        // Call all callbacks with the given context and arguments\n        fireWith: function (context, args) {\n          if (list && ( !fired || stack )) {\n            args = args || [];\n            args = [ context, args.slice ? args.slice() : args ];\n            if (firing) {\n              stack.push(args);\n            } else {\n              fire(args);\n            }\n          }\n          return this;\n        },\n        // Call all the callbacks with the given arguments\n        fire: function () {\n          self.fireWith(this, arguments);\n          return this;\n        },\n        // To know if the callbacks have already been called at least once\n        fired: function () {\n          return !!fired;\n        }\n      };\n\n    return self;\n  };\n\n\n  jQuery.extend({\n\n    Deferred: function (func) {\n      var tuples = [\n          // action, add listener, listener list, final state\n          [ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n          [ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n          [ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n        ],\n        state = \"pending\",\n        promise = {\n          state: function () {\n            return state;\n          },\n          always: function () {\n            deferred.done(arguments).fail(arguments);\n            return this;\n          },\n          then: function (/* fnDone, fnFail, fnProgress */) {\n            var fns = arguments;\n            return jQuery.Deferred(function (newDefer) {\n              jQuery.each(tuples, function (i, tuple) {\n                var fn = jQuery.isFunction(fns[ i ]) && fns[ i ];\n                // deferred[ done | fail | progress ] for forwarding actions to newDefer\n                deferred[ tuple[1] ](function () {\n                  var returned = fn && fn.apply(this, arguments);\n                  if (returned && jQuery.isFunction(returned.promise)) {\n                    returned.promise()\n                      .done(newDefer.resolve)\n                      .fail(newDefer.reject)\n                      .progress(newDefer.notify);\n                  } else {\n                    newDefer[ tuple[ 0 ] + \"With\" ](this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments);\n                  }\n                });\n              });\n              fns = null;\n            }).promise();\n          },\n          // Get a promise for this deferred\n          // If obj is provided, the promise aspect is added to the object\n          promise: function (obj) {\n            return obj != null ? jQuery.extend(obj, promise) : promise;\n          }\n        },\n        deferred = {};\n\n      // Keep pipe for back-compat\n      promise.pipe = promise.then;\n\n      // Add list-specific methods\n      jQuery.each(tuples, function (i, tuple) {\n        var list = tuple[ 2 ],\n          stateString = tuple[ 3 ];\n\n        // promise[ done | fail | progress ] = list.add\n        promise[ tuple[1] ] = list.add;\n\n        // Handle state\n        if (stateString) {\n          list.add(function () {\n            // state = [ resolved | rejected ]\n            state = stateString;\n\n            // [ reject_list | resolve_list ].disable; progress_list.lock\n          }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock);\n        }\n\n        // deferred[ resolve | reject | notify ]\n        deferred[ tuple[0] ] = function () {\n          deferred[ tuple[0] + \"With\" ](this === deferred ? promise : this, arguments);\n          return this;\n        };\n        deferred[ tuple[0] + \"With\" ] = list.fireWith;\n      });\n\n      // Make the deferred a promise\n      promise.promise(deferred);\n\n      // Call given func if any\n      if (func) {\n        func.call(deferred, deferred);\n      }\n\n      // All done!\n      return deferred;\n    },\n\n    // Deferred helper\n    when: function (subordinate /* , ..., subordinateN */) {\n      var i = 0,\n        resolveValues = slice.call(arguments),\n        length = resolveValues.length,\n\n      // the count of uncompleted subordinates\n        remaining = length !== 1 || ( subordinate && jQuery.isFunction(subordinate.promise) ) ? length : 0,\n\n      // the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n        deferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n      // Update function for both resolve and progress values\n        updateFunc = function (i, contexts, values) {\n          return function (value) {\n            contexts[ i ] = this;\n            values[ i ] = arguments.length > 1 ? slice.call(arguments) : value;\n            if (values === progressValues) {\n              deferred.notifyWith(contexts, values);\n            } else if (!( --remaining )) {\n              deferred.resolveWith(contexts, values);\n            }\n          };\n        },\n\n        progressValues, progressContexts, resolveContexts;\n\n      // add listeners to Deferred subordinates; treat others as resolved\n      if (length > 1) {\n        progressValues = new Array(length);\n        progressContexts = new Array(length);\n        resolveContexts = new Array(length);\n        for (; i < length; i++) {\n          if (resolveValues[ i ] && jQuery.isFunction(resolveValues[ i ].promise)) {\n            resolveValues[ i ].promise()\n              .done(updateFunc(i, resolveContexts, resolveValues))\n              .fail(deferred.reject)\n              .progress(updateFunc(i, progressContexts, progressValues));\n          } else {\n            --remaining;\n          }\n        }\n      }\n\n      // if we're not waiting on anything, resolve the master\n      if (!remaining) {\n        deferred.resolveWith(resolveContexts, resolveValues);\n      }\n\n      return deferred.promise();\n    }\n  });\n\n\n// The deferred used on DOM ready\n  var readyList;\n\n  jQuery.fn.ready = function (fn) {\n    // Add the callback\n    jQuery.ready.promise().done(fn);\n\n    return this;\n  };\n\n  jQuery.extend({\n    // Is the DOM ready to be used? Set to true once it occurs.\n    isReady: false,\n\n    // A counter to track how many items to wait for before\n    // the ready event fires. See #6781\n    readyWait: 1,\n\n    // Hold (or release) the ready event\n    holdReady: function (hold) {\n      if (hold) {\n        jQuery.readyWait++;\n      } else {\n        jQuery.ready(true);\n      }\n    },\n\n    // Handle when the DOM is ready\n    ready: function (wait) {\n\n      // Abort if there are pending holds or we're already ready\n      if (wait === true ? --jQuery.readyWait : jQuery.isReady) {\n        return;\n      }\n\n      // Remember that the DOM is ready\n      jQuery.isReady = true;\n\n      // If a normal DOM Ready event fired, decrement, and wait if need be\n      if (wait !== true && --jQuery.readyWait > 0) {\n        return;\n      }\n\n      // If there are functions bound, to execute\n      readyList.resolveWith(document, [ jQuery ]);\n\n      // Trigger any bound ready events\n      if (jQuery.fn.trigger) {\n        jQuery(document).trigger(\"ready\").off(\"ready\");\n      }\n    }\n  });\n\n  /**\n   * The ready event handler and self cleanup method\n   */\n  function completed() {\n    document.removeEventListener(\"DOMContentLoaded\", completed, false);\n    window.removeEventListener(\"load\", completed, false);\n    jQuery.ready();\n  }\n\n  jQuery.ready.promise = function (obj) {\n    if (!readyList) {\n\n      readyList = jQuery.Deferred();\n\n      // Catch cases where $(document).ready() is called after the browser event has already occurred.\n      // we once tried to use readyState \"interactive\" here, but it caused issues like the one\n      // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n      if (document.readyState === \"complete\") {\n        // Handle it asynchronously to allow scripts the opportunity to delay ready\n        setTimeout(jQuery.ready);\n\n      } else {\n\n        // Use the handy event callback\n        document.addEventListener(\"DOMContentLoaded\", completed, false);\n\n        // A fallback to window.onload, that will always work\n        window.addEventListener(\"load\", completed, false);\n      }\n    }\n    return readyList.promise(obj);\n  };\n\n// Kick off the DOM ready check even if the user does not\n  jQuery.ready.promise();\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\n  var access = jQuery.access = function (elems, fn, key, value, chainable, emptyGet, raw) {\n    var i = 0,\n      len = elems.length,\n      bulk = key == null;\n\n    // Sets many values\n    if (jQuery.type(key) === \"object\") {\n      chainable = true;\n      for (i in key) {\n        jQuery.access(elems, fn, i, key[i], true, emptyGet, raw);\n      }\n\n      // Sets one value\n    } else if (value !== undefined) {\n      chainable = true;\n\n      if (!jQuery.isFunction(value)) {\n        raw = true;\n      }\n\n      if (bulk) {\n        // Bulk operations run against the entire set\n        if (raw) {\n          fn.call(elems, value);\n          fn = null;\n\n          // ...except when executing function values\n        } else {\n          bulk = fn;\n          fn = function (elem, key, value) {\n            return bulk.call(jQuery(elem), value);\n          };\n        }\n      }\n\n      if (fn) {\n        for (; i < len; i++) {\n          fn(elems[i], key, raw ? value : value.call(elems[i], i, fn(elems[i], key)));\n        }\n      }\n    }\n\n    return chainable ?\n      elems :\n\n      // Gets\n      bulk ?\n        fn.call(elems) :\n        len ? fn(elems[0], key) : emptyGet;\n  };\n\n\n  /**\n   * Determines whether an object can have data\n   */\n  jQuery.acceptData = function (owner) {\n    // Accepts only:\n    //  - Node\n    //    - Node.ELEMENT_NODE\n    //    - Node.DOCUMENT_NODE\n    //  - Object\n    //    - Any\n    /* jshint -W018 */\n    return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n  };\n\n\n  function Data() {\n    // Support: Android < 4,\n    // Old WebKit does not have Object.preventExtensions/freeze method,\n    // return new empty object instead with no [[set]] accessor\n    Object.defineProperty(this.cache = {}, 0, {\n      get: function () {\n        return {};\n      }\n    });\n\n    this.expando = jQuery.expando + Math.random();\n  }\n\n  Data.uid = 1;\n  Data.accepts = jQuery.acceptData;\n\n  Data.prototype = {\n    key: function (owner) {\n      // We can accept data for non-element nodes in modern browsers,\n      // but we should not, see #8335.\n      // Always return the key for a frozen object.\n      if (!Data.accepts(owner)) {\n        return 0;\n      }\n\n      var descriptor = {},\n      // Check if the owner object already has a cache key\n        unlock = owner[ this.expando ];\n\n      // If not, create one\n      if (!unlock) {\n        unlock = Data.uid++;\n\n        // Secure it in a non-enumerable, non-writable property\n        try {\n          descriptor[ this.expando ] = { value: unlock };\n          Object.defineProperties(owner, descriptor);\n\n          // Support: Android < 4\n          // Fallback to a less secure definition\n        } catch (e) {\n          descriptor[ this.expando ] = unlock;\n          jQuery.extend(owner, descriptor);\n        }\n      }\n\n      // Ensure the cache object\n      if (!this.cache[ unlock ]) {\n        this.cache[ unlock ] = {};\n      }\n\n      return unlock;\n    },\n    set: function (owner, data, value) {\n      var prop,\n      // There may be an unlock assigned to this node,\n      // if there is no entry for this \"owner\", create one inline\n      // and set the unlock as though an owner entry had always existed\n        unlock = this.key(owner),\n        cache = this.cache[ unlock ];\n\n      // Handle: [ owner, key, value ] args\n      if (typeof data === \"string\") {\n        cache[ data ] = value;\n\n        // Handle: [ owner, { properties } ] args\n      } else {\n        // Fresh assignments by object are shallow copied\n        if (jQuery.isEmptyObject(cache)) {\n          jQuery.extend(this.cache[ unlock ], data);\n          // Otherwise, copy the properties one-by-one to the cache object\n        } else {\n          for (prop in data) {\n            cache[ prop ] = data[ prop ];\n          }\n        }\n      }\n      return cache;\n    },\n    get: function (owner, key) {\n      // Either a valid cache is found, or will be created.\n      // New caches will be created and the unlock returned,\n      // allowing direct access to the newly created\n      // empty data object. A valid owner object must be provided.\n      var cache = this.cache[ this.key(owner) ];\n\n      return key === undefined ?\n        cache : cache[ key ];\n    },\n    access: function (owner, key, value) {\n      var stored;\n      // In cases where either:\n      //\n      //   1. No key was specified\n      //   2. A string key was specified, but no value provided\n      //\n      // Take the \"read\" path and allow the get method to determine\n      // which value to return, respectively either:\n      //\n      //   1. The entire cache object\n      //   2. The data stored at the key\n      //\n      if (key === undefined ||\n        ((key && typeof key === \"string\") && value === undefined)) {\n\n        stored = this.get(owner, key);\n\n        return stored !== undefined ?\n          stored : this.get(owner, jQuery.camelCase(key));\n      }\n\n      // [*]When the key is not a string, or both a key and value\n      // are specified, set or extend (existing objects) with either:\n      //\n      //   1. An object of properties\n      //   2. A key and value\n      //\n      this.set(owner, key, value);\n\n      // Since the \"set\" path can have two possible entry points\n      // return the expected data based on which path was taken[*]\n      return value !== undefined ? value : key;\n    },\n    remove: function (owner, key) {\n      var i, name, camel,\n        unlock = this.key(owner),\n        cache = this.cache[ unlock ];\n\n      if (key === undefined) {\n        this.cache[ unlock ] = {};\n\n      } else {\n        // Support array or space separated string of keys\n        if (jQuery.isArray(key)) {\n          // If \"name\" is an array of keys...\n          // When data is initially created, via (\"key\", \"val\") signature,\n          // keys will be converted to camelCase.\n          // Since there is no way to tell _how_ a key was added, remove\n          // both plain key and camelCase key. #12786\n          // This will only penalize the array argument path.\n          name = key.concat(key.map(jQuery.camelCase));\n        } else {\n          camel = jQuery.camelCase(key);\n          // Try the string as a key before any manipulation\n          if (key in cache) {\n            name = [ key, camel ];\n          } else {\n            // If a key with the spaces exists, use it.\n            // Otherwise, create an array by matching non-whitespace\n            name = camel;\n            name = name in cache ?\n              [ name ] : ( name.match(rnotwhite) || [] );\n          }\n        }\n\n        i = name.length;\n        while (i--) {\n          delete cache[ name[ i ] ];\n        }\n      }\n    },\n    hasData: function (owner) {\n      return !jQuery.isEmptyObject(\n          this.cache[ owner[ this.expando ] ] || {}\n      );\n    },\n    discard: function (owner) {\n      if (owner[ this.expando ]) {\n        delete this.cache[ owner[ this.expando ] ];\n      }\n    }\n  };\n  var data_priv = new Data();\n\n  var data_user = new Data();\n\n\n  /*\n   Implementation Summary\n\n   1. Enforce API surface and semantic compatibility with 1.9.x branch\n   2. Improve the module's maintainability by reducing the storage\n   paths to a single mechanism.\n   3. Use the same single mechanism to support \"private\" and \"user\" data.\n   4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n   5. Avoid exposing implementation details on user objects (eg. expando properties)\n   6. Provide a clear path for implementation upgrade to WeakMap in 2014\n   */\n  var rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n    rmultiDash = /([A-Z])/g;\n\n  function dataAttr(elem, key, data) {\n    var name;\n\n    // If nothing was found internally, try to fetch any\n    // data from the HTML5 data-* attribute\n    if (data === undefined && elem.nodeType === 1) {\n      name = \"data-\" + key.replace(rmultiDash, \"-$1\").toLowerCase();\n      data = elem.getAttribute(name);\n\n      if (typeof data === \"string\") {\n        try {\n          data = data === \"true\" ? true :\n              data === \"false\" ? false :\n              data === \"null\" ? null :\n            // Only convert to a number if it doesn't change the string\n              +data + \"\" === data ? +data :\n            rbrace.test(data) ? jQuery.parseJSON(data) :\n              data;\n        } catch (e) {\n        }\n\n        // Make sure we set the data so it isn't changed later\n        data_user.set(elem, key, data);\n      } else {\n        data = undefined;\n      }\n    }\n    return data;\n  }\n\n  jQuery.extend({\n    hasData: function (elem) {\n      return data_user.hasData(elem) || data_priv.hasData(elem);\n    },\n\n    data: function (elem, name, data) {\n      return data_user.access(elem, name, data);\n    },\n\n    removeData: function (elem, name) {\n      data_user.remove(elem, name);\n    },\n\n    // TODO: Now that all calls to _data and _removeData have been replaced\n    // with direct calls to data_priv methods, these can be deprecated.\n    _data: function (elem, name, data) {\n      return data_priv.access(elem, name, data);\n    },\n\n    _removeData: function (elem, name) {\n      data_priv.remove(elem, name);\n    }\n  });\n\n  jQuery.fn.extend({\n    data: function (key, value) {\n      var i, name, data,\n        elem = this[ 0 ],\n        attrs = elem && elem.attributes;\n\n      // Gets all values\n      if (key === undefined) {\n        if (this.length) {\n          data = data_user.get(elem);\n\n          if (elem.nodeType === 1 && !data_priv.get(elem, \"hasDataAttrs\")) {\n            i = attrs.length;\n            while (i--) {\n              name = attrs[ i ].name;\n\n              if (name.indexOf(\"data-\") === 0) {\n                name = jQuery.camelCase(name.slice(5));\n                dataAttr(elem, name, data[ name ]);\n              }\n            }\n            data_priv.set(elem, \"hasDataAttrs\", true);\n          }\n        }\n\n        return data;\n      }\n\n      // Sets multiple values\n      if (typeof key === \"object\") {\n        return this.each(function () {\n          data_user.set(this, key);\n        });\n      }\n\n      return access(this, function (value) {\n        var data,\n          camelKey = jQuery.camelCase(key);\n\n        // The calling jQuery object (element matches) is not empty\n        // (and therefore has an element appears at this[ 0 ]) and the\n        // `value` parameter was not undefined. An empty jQuery object\n        // will result in `undefined` for elem = this[ 0 ] which will\n        // throw an exception if an attempt to read a data cache is made.\n        if (elem && value === undefined) {\n          // Attempt to get data from the cache\n          // with the key as-is\n          data = data_user.get(elem, key);\n          if (data !== undefined) {\n            return data;\n          }\n\n          // Attempt to get data from the cache\n          // with the key camelized\n          data = data_user.get(elem, camelKey);\n          if (data !== undefined) {\n            return data;\n          }\n\n          // Attempt to \"discover\" the data in\n          // HTML5 custom data-* attrs\n          data = dataAttr(elem, camelKey, undefined);\n          if (data !== undefined) {\n            return data;\n          }\n\n          // We tried really hard, but the data doesn't exist.\n          return;\n        }\n\n        // Set the data...\n        this.each(function () {\n          // First, attempt to store a copy or reference of any\n          // data that might've been store with a camelCased key.\n          var data = data_user.get(this, camelKey);\n\n          // For HTML5 data-* attribute interop, we have to\n          // store property names with dashes in a camelCase form.\n          // This might not apply to all properties...*\n          data_user.set(this, camelKey, value);\n\n          // *... In the case of properties that might _actually_\n          // have dashes, we need to also store a copy of that\n          // unchanged property.\n          if (key.indexOf(\"-\") !== -1 && data !== undefined) {\n            data_user.set(this, key, value);\n          }\n        });\n      }, null, value, arguments.length > 1, null, true);\n    },\n\n    removeData: function (key) {\n      return this.each(function () {\n        data_user.remove(this, key);\n      });\n    }\n  });\n\n\n  jQuery.extend({\n    queue: function (elem, type, data) {\n      var queue;\n\n      if (elem) {\n        type = ( type || \"fx\" ) + \"queue\";\n        queue = data_priv.get(elem, type);\n\n        // Speed up dequeue by getting out quickly if this is just a lookup\n        if (data) {\n          if (!queue || jQuery.isArray(data)) {\n            queue = data_priv.access(elem, type, jQuery.makeArray(data));\n          } else {\n            queue.push(data);\n          }\n        }\n        return queue || [];\n      }\n    },\n\n    dequeue: function (elem, type) {\n      type = type || \"fx\";\n\n      var queue = jQuery.queue(elem, type),\n        startLength = queue.length,\n        fn = queue.shift(),\n        hooks = jQuery._queueHooks(elem, type),\n        next = function () {\n          jQuery.dequeue(elem, type);\n        };\n\n      // If the fx queue is dequeued, always remove the progress sentinel\n      if (fn === \"inprogress\") {\n        fn = queue.shift();\n        startLength--;\n      }\n\n      if (fn) {\n\n        // Add a progress sentinel to prevent the fx queue from being\n        // automatically dequeued\n        if (type === \"fx\") {\n          queue.unshift(\"inprogress\");\n        }\n\n        // clear up the last queue stop function\n        delete hooks.stop;\n        fn.call(elem, next, hooks);\n      }\n\n      if (!startLength && hooks) {\n        hooks.empty.fire();\n      }\n    },\n\n    // not intended for public consumption - generates a queueHooks object, or returns the current one\n    _queueHooks: function (elem, type) {\n      var key = type + \"queueHooks\";\n      return data_priv.get(elem, key) || data_priv.access(elem, key, {\n        empty: jQuery.Callbacks(\"once memory\").add(function () {\n          data_priv.remove(elem, [ type + \"queue\", key ]);\n        })\n      });\n    }\n  });\n\n  jQuery.fn.extend({\n    queue: function (type, data) {\n      var setter = 2;\n\n      if (typeof type !== \"string\") {\n        data = type;\n        type = \"fx\";\n        setter--;\n      }\n\n      if (arguments.length < setter) {\n        return jQuery.queue(this[0], type);\n      }\n\n      return data === undefined ?\n        this :\n        this.each(function () {\n          var queue = jQuery.queue(this, type, data);\n\n          // ensure a hooks for this queue\n          jQuery._queueHooks(this, type);\n\n          if (type === \"fx\" && queue[0] !== \"inprogress\") {\n            jQuery.dequeue(this, type);\n          }\n        });\n    },\n    dequeue: function (type) {\n      return this.each(function () {\n        jQuery.dequeue(this, type);\n      });\n    },\n    clearQueue: function (type) {\n      return this.queue(type || \"fx\", []);\n    },\n    // Get a promise resolved when queues of a certain type\n    // are emptied (fx is the type by default)\n    promise: function (type, obj) {\n      var tmp,\n        count = 1,\n        defer = jQuery.Deferred(),\n        elements = this,\n        i = this.length,\n        resolve = function () {\n          if (!( --count )) {\n            defer.resolveWith(elements, [ elements ]);\n          }\n        };\n\n      if (typeof type !== \"string\") {\n        obj = type;\n        type = undefined;\n      }\n      type = type || \"fx\";\n\n      while (i--) {\n        tmp = data_priv.get(elements[ i ], type + \"queueHooks\");\n        if (tmp && tmp.empty) {\n          count++;\n          tmp.empty.add(resolve);\n        }\n      }\n      resolve();\n      return defer.promise(obj);\n    }\n  });\n  var pnum = (/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\n  var cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\n  var isHidden = function (elem, el) {\n    // isHidden might be called from jQuery#filter function;\n    // in that case, element will be second argument\n    elem = el || elem;\n    return jQuery.css(elem, \"display\") === \"none\" || !jQuery.contains(elem.ownerDocument, elem);\n  };\n\n  var rcheckableType = (/^(?:checkbox|radio)$/i);\n\n\n  (function () {\n    var fragment = document.createDocumentFragment(),\n      div = fragment.appendChild(document.createElement(\"div\"));\n\n    // #11217 - WebKit loses check when the name is after the checked attribute\n    div.innerHTML = \"<input type='radio' checked='checked' name='t'/>\";\n\n    // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3\n    // old WebKit doesn't clone checked state correctly in fragments\n    support.checkClone = div.cloneNode(true).cloneNode(true).lastChild.checked;\n\n    // Make sure textarea (and checkbox) defaultValue is properly cloned\n    // Support: IE9-IE11+\n    div.innerHTML = \"<textarea>x</textarea>\";\n    support.noCloneChecked = !!div.cloneNode(true).lastChild.defaultValue;\n  })();\n  var strundefined = typeof undefined;\n\n\n  support.focusinBubbles = \"onfocusin\" in window;\n\n\n  var\n    rkeyEvent = /^key/,\n    rmouseEvent = /^(?:mouse|contextmenu)|click/,\n    rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n    rtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\n  function returnTrue() {\n    return true;\n  }\n\n  function returnFalse() {\n    return false;\n  }\n\n  function safeActiveElement() {\n    try {\n      return document.activeElement;\n    } catch (err) {\n    }\n  }\n\n  /*\n   * Helper functions for managing events -- not part of the public interface.\n   * Props to Dean Edwards' addEvent library for many of the ideas.\n   */\n  jQuery.event = {\n\n    global: {},\n\n    add: function (elem, types, handler, data, selector) {\n\n      var handleObjIn, eventHandle, tmp,\n        events, t, handleObj,\n        special, handlers, type, namespaces, origType,\n        elemData = data_priv.get(elem);\n\n      // Don't attach events to noData or text/comment nodes (but allow plain objects)\n      if (!elemData) {\n        return;\n      }\n\n      // Caller can pass in an object of custom data in lieu of the handler\n      if (handler.handler) {\n        handleObjIn = handler;\n        handler = handleObjIn.handler;\n        selector = handleObjIn.selector;\n      }\n\n      // Make sure that the handler has a unique ID, used to find/remove it later\n      if (!handler.guid) {\n        handler.guid = jQuery.guid++;\n      }\n\n      // Init the element's event structure and main handler, if this is the first\n      if (!(events = elemData.events)) {\n        events = elemData.events = {};\n      }\n      if (!(eventHandle = elemData.handle)) {\n        eventHandle = elemData.handle = function (e) {\n          // Discard the second event of a jQuery.event.trigger() and\n          // when an event is called after a page has unloaded\n          return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n            jQuery.event.dispatch.apply(elem, arguments) : undefined;\n        };\n      }\n\n      // Handle multiple events separated by a space\n      types = ( types || \"\" ).match(rnotwhite) || [ \"\" ];\n      t = types.length;\n      while (t--) {\n        tmp = rtypenamespace.exec(types[t]) || [];\n        type = origType = tmp[1];\n        namespaces = ( tmp[2] || \"\" ).split(\".\").sort();\n\n        // There *must* be a type, no attaching namespace-only handlers\n        if (!type) {\n          continue;\n        }\n\n        // If event changes its type, use the special event handlers for the changed type\n        special = jQuery.event.special[ type ] || {};\n\n        // If selector defined, determine special event api type, otherwise given type\n        type = ( selector ? special.delegateType : special.bindType ) || type;\n\n        // Update special based on newly reset type\n        special = jQuery.event.special[ type ] || {};\n\n        // handleObj is passed to all event handlers\n        handleObj = jQuery.extend({\n          type: type,\n          origType: origType,\n          data: data,\n          handler: handler,\n          guid: handler.guid,\n          selector: selector,\n          needsContext: selector && jQuery.expr.match.needsContext.test(selector),\n          namespace: namespaces.join(\".\")\n        }, handleObjIn);\n\n        // Init the event handler queue if we're the first\n        if (!(handlers = events[ type ])) {\n          handlers = events[ type ] = [];\n          handlers.delegateCount = 0;\n\n          // Only use addEventListener if the special events handler returns false\n          if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {\n            if (elem.addEventListener) {\n              elem.addEventListener(type, eventHandle, false);\n            }\n          }\n        }\n\n        if (special.add) {\n          special.add.call(elem, handleObj);\n\n          if (!handleObj.handler.guid) {\n            handleObj.handler.guid = handler.guid;\n          }\n        }\n\n        // Add to the element's handler list, delegates in front\n        if (selector) {\n          handlers.splice(handlers.delegateCount++, 0, handleObj);\n        } else {\n          handlers.push(handleObj);\n        }\n\n        // Keep track of which events have ever been used, for event optimization\n        jQuery.event.global[ type ] = true;\n      }\n\n    },\n\n    // Detach an event or set of events from an element\n    remove: function (elem, types, handler, selector, mappedTypes) {\n\n      var j, origCount, tmp,\n        events, t, handleObj,\n        special, handlers, type, namespaces, origType,\n        elemData = data_priv.hasData(elem) && data_priv.get(elem);\n\n      if (!elemData || !(events = elemData.events)) {\n        return;\n      }\n\n      // Once for each type.namespace in types; type may be omitted\n      types = ( types || \"\" ).match(rnotwhite) || [ \"\" ];\n      t = types.length;\n      while (t--) {\n        tmp = rtypenamespace.exec(types[t]) || [];\n        type = origType = tmp[1];\n        namespaces = ( tmp[2] || \"\" ).split(\".\").sort();\n\n        // Unbind all events (on this namespace, if provided) for the element\n        if (!type) {\n          for (type in events) {\n            jQuery.event.remove(elem, type + types[ t ], handler, selector, true);\n          }\n          continue;\n        }\n\n        special = jQuery.event.special[ type ] || {};\n        type = ( selector ? special.delegateType : special.bindType ) || type;\n        handlers = events[ type ] || [];\n        tmp = tmp[2] && new RegExp(\"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\");\n\n        // Remove matching events\n        origCount = j = handlers.length;\n        while (j--) {\n          handleObj = handlers[ j ];\n\n          if (( mappedTypes || origType === handleObj.origType ) &&\n            ( !handler || handler.guid === handleObj.guid ) &&\n            ( !tmp || tmp.test(handleObj.namespace) ) &&\n            ( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector )) {\n            handlers.splice(j, 1);\n\n            if (handleObj.selector) {\n              handlers.delegateCount--;\n            }\n            if (special.remove) {\n              special.remove.call(elem, handleObj);\n            }\n          }\n        }\n\n        // Remove generic event handler if we removed something and no more handlers exist\n        // (avoids potential for endless recursion during removal of special event handlers)\n        if (origCount && !handlers.length) {\n          if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) {\n            jQuery.removeEvent(elem, type, elemData.handle);\n          }\n\n          delete events[ type ];\n        }\n      }\n\n      // Remove the expando if it's no longer used\n      if (jQuery.isEmptyObject(events)) {\n        delete elemData.handle;\n        data_priv.remove(elem, \"events\");\n      }\n    },\n\n    trigger: function (event, data, elem, onlyHandlers) {\n\n      var i, cur, tmp, bubbleType, ontype, handle, special,\n        eventPath = [ elem || document ],\n        type = hasOwn.call(event, \"type\") ? event.type : event,\n        namespaces = hasOwn.call(event, \"namespace\") ? event.namespace.split(\".\") : [];\n\n      cur = tmp = elem = elem || document;\n\n      // Don't do events on text and comment nodes\n      if (elem.nodeType === 3 || elem.nodeType === 8) {\n        return;\n      }\n\n      // focus/blur morphs to focusin/out; ensure we're not firing them right now\n      if (rfocusMorph.test(type + jQuery.event.triggered)) {\n        return;\n      }\n\n      if (type.indexOf(\".\") >= 0) {\n        // Namespaced trigger; create a regexp to match event type in handle()\n        namespaces = type.split(\".\");\n        type = namespaces.shift();\n        namespaces.sort();\n      }\n      ontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n      // Caller can pass in a jQuery.Event object, Object, or just an event type string\n      event = event[ jQuery.expando ] ?\n        event :\n        new jQuery.Event(type, typeof event === \"object\" && event);\n\n      // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n      event.isTrigger = onlyHandlers ? 2 : 3;\n      event.namespace = namespaces.join(\".\");\n      event.namespace_re = event.namespace ?\n        new RegExp(\"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\") :\n        null;\n\n      // Clean up the event in case it is being reused\n      event.result = undefined;\n      if (!event.target) {\n        event.target = elem;\n      }\n\n      // Clone any incoming data and prepend the event, creating the handler arg list\n      data = data == null ?\n        [ event ] :\n        jQuery.makeArray(data, [ event ]);\n\n      // Allow special events to draw outside the lines\n      special = jQuery.event.special[ type ] || {};\n      if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) {\n        return;\n      }\n\n      // Determine event propagation path in advance, per W3C events spec (#9951)\n      // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n      if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) {\n\n        bubbleType = special.delegateType || type;\n        if (!rfocusMorph.test(bubbleType + type)) {\n          cur = cur.parentNode;\n        }\n        for (; cur; cur = cur.parentNode) {\n          eventPath.push(cur);\n          tmp = cur;\n        }\n\n        // Only add window if we got to document (e.g., not plain obj or detached DOM)\n        if (tmp === (elem.ownerDocument || document)) {\n          eventPath.push(tmp.defaultView || tmp.parentWindow || window);\n        }\n      }\n\n      // Fire handlers on the event path\n      i = 0;\n      while ((cur = eventPath[i++]) && !event.isPropagationStopped()) {\n\n        event.type = i > 1 ?\n          bubbleType :\n          special.bindType || type;\n\n        // jQuery handler\n        handle = ( data_priv.get(cur, \"events\") || {} )[ event.type ] && data_priv.get(cur, \"handle\");\n        if (handle) {\n          handle.apply(cur, data);\n        }\n\n        // Native handler\n        handle = ontype && cur[ ontype ];\n        if (handle && handle.apply && jQuery.acceptData(cur)) {\n          event.result = handle.apply(cur, data);\n          if (event.result === false) {\n            event.preventDefault();\n          }\n        }\n      }\n      event.type = type;\n\n      // If nobody prevented the default action, do it now\n      if (!onlyHandlers && !event.isDefaultPrevented()) {\n\n        if ((!special._default || special._default.apply(eventPath.pop(), data) === false) &&\n          jQuery.acceptData(elem)) {\n\n          // Call a native DOM method on the target with the same name name as the event.\n          // Don't do default actions on window, that's where global variables be (#6170)\n          if (ontype && jQuery.isFunction(elem[ type ]) && !jQuery.isWindow(elem)) {\n\n            // Don't re-trigger an onFOO event when we call its FOO() method\n            tmp = elem[ ontype ];\n\n            if (tmp) {\n              elem[ ontype ] = null;\n            }\n\n            // Prevent re-triggering of the same event, since we already bubbled it above\n            jQuery.event.triggered = type;\n            elem[ type ]();\n            jQuery.event.triggered = undefined;\n\n            if (tmp) {\n              elem[ ontype ] = tmp;\n            }\n          }\n        }\n      }\n\n      return event.result;\n    },\n\n    dispatch: function (event) {\n\n      // Make a writable jQuery.Event from the native event object\n      event = jQuery.event.fix(event);\n\n      var i, j, ret, matched, handleObj,\n        handlerQueue = [],\n        args = slice.call(arguments),\n        handlers = ( data_priv.get(this, \"events\") || {} )[ event.type ] || [],\n        special = jQuery.event.special[ event.type ] || {};\n\n      // Use the fix-ed jQuery.Event rather than the (read-only) native event\n      args[0] = event;\n      event.delegateTarget = this;\n\n      // Call the preDispatch hook for the mapped type, and let it bail if desired\n      if (special.preDispatch && special.preDispatch.call(this, event) === false) {\n        return;\n      }\n\n      // Determine handlers\n      handlerQueue = jQuery.event.handlers.call(this, event, handlers);\n\n      // Run delegates first; they may want to stop propagation beneath us\n      i = 0;\n      while ((matched = handlerQueue[ i++ ]) && !event.isPropagationStopped()) {\n        event.currentTarget = matched.elem;\n\n        j = 0;\n        while ((handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped()) {\n\n          // Triggered event must either 1) have no namespace, or\n          // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n          if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) {\n\n            event.handleObj = handleObj;\n            event.data = handleObj.data;\n\n            ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n              .apply(matched.elem, args);\n\n            if (ret !== undefined) {\n              if ((event.result = ret) === false) {\n                event.preventDefault();\n                event.stopPropagation();\n              }\n            }\n          }\n        }\n      }\n\n      // Call the postDispatch hook for the mapped type\n      if (special.postDispatch) {\n        special.postDispatch.call(this, event);\n      }\n\n      return event.result;\n    },\n\n    handlers: function (event, handlers) {\n      var i, matches, sel, handleObj,\n        handlerQueue = [],\n        delegateCount = handlers.delegateCount,\n        cur = event.target;\n\n      // Find delegate handlers\n      // Black-hole SVG <use> instance trees (#13180)\n      // Avoid non-left-click bubbling in Firefox (#3861)\n      if (delegateCount && cur.nodeType && (!event.button || event.type !== \"click\")) {\n\n        for (; cur !== this; cur = cur.parentNode || this) {\n\n          // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n          if (cur.disabled !== true || event.type !== \"click\") {\n            matches = [];\n            for (i = 0; i < delegateCount; i++) {\n              handleObj = handlers[ i ];\n\n              // Don't conflict with Object.prototype properties (#13203)\n              sel = handleObj.selector + \" \";\n\n              if (matches[ sel ] === undefined) {\n                matches[ sel ] = handleObj.needsContext ?\n                  jQuery(sel, this).index(cur) >= 0 :\n                  jQuery.find(sel, this, null, [ cur ]).length;\n              }\n              if (matches[ sel ]) {\n                matches.push(handleObj);\n              }\n            }\n            if (matches.length) {\n              handlerQueue.push({ elem: cur, handlers: matches });\n            }\n          }\n        }\n      }\n\n      // Add the remaining (directly-bound) handlers\n      if (delegateCount < handlers.length) {\n        handlerQueue.push({ elem: this, handlers: handlers.slice(delegateCount) });\n      }\n\n      return handlerQueue;\n    },\n\n    // Includes some event props shared by KeyEvent and MouseEvent\n    props: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n    fixHooks: {},\n\n    keyHooks: {\n      props: \"char charCode key keyCode\".split(\" \"),\n      filter: function (event, original) {\n\n        // Add which for key events\n        if (event.which == null) {\n          event.which = original.charCode != null ? original.charCode : original.keyCode;\n        }\n\n        return event;\n      }\n    },\n\n    mouseHooks: {\n      props: \"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n      filter: function (event, original) {\n        var eventDoc, doc, body,\n          button = original.button;\n\n        // Calculate pageX/Y if missing and clientX/Y available\n        if (event.pageX == null && original.clientX != null) {\n          eventDoc = event.target.ownerDocument || document;\n          doc = eventDoc.documentElement;\n          body = eventDoc.body;\n\n          event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n          event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );\n        }\n\n        // Add which for click: 1 === left; 2 === middle; 3 === right\n        // Note: button is not normalized, so don't use it\n        if (!event.which && button !== undefined) {\n          event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n        }\n\n        return event;\n      }\n    },\n\n    fix: function (event) {\n      if (event[ jQuery.expando ]) {\n        return event;\n      }\n\n      // Create a writable copy of the event object and normalize some properties\n      var i, prop, copy,\n        type = event.type,\n        originalEvent = event,\n        fixHook = this.fixHooks[ type ];\n\n      if (!fixHook) {\n        this.fixHooks[ type ] = fixHook =\n          rmouseEvent.test(type) ? this.mouseHooks :\n            rkeyEvent.test(type) ? this.keyHooks :\n            {};\n      }\n      copy = fixHook.props ? this.props.concat(fixHook.props) : this.props;\n\n      event = new jQuery.Event(originalEvent);\n\n      i = copy.length;\n      while (i--) {\n        prop = copy[ i ];\n        event[ prop ] = originalEvent[ prop ];\n      }\n\n      // Support: Cordova 2.5 (WebKit) (#13255)\n      // All events should have a target; Cordova deviceready doesn't\n      if (!event.target) {\n        event.target = document;\n      }\n\n      // Support: Safari 6.0+, Chrome < 28\n      // Target should not be a text node (#504, #13143)\n      if (event.target.nodeType === 3) {\n        event.target = event.target.parentNode;\n      }\n\n      return fixHook.filter ? fixHook.filter(event, originalEvent) : event;\n    },\n\n    special: {\n      load: {\n        // Prevent triggered image.load events from bubbling to window.load\n        noBubble: true\n      },\n      focus: {\n        // Fire native event if possible so blur/focus sequence is correct\n        trigger: function () {\n          if (this !== safeActiveElement() && this.focus) {\n            this.focus();\n            return false;\n          }\n        },\n        delegateType: \"focusin\"\n      },\n      blur: {\n        trigger: function () {\n          if (this === safeActiveElement() && this.blur) {\n            this.blur();\n            return false;\n          }\n        },\n        delegateType: \"focusout\"\n      },\n      click: {\n        // For checkbox, fire native event so checked state will be right\n        trigger: function () {\n          if (this.type === \"checkbox\" && this.click && jQuery.nodeName(this, \"input\")) {\n            this.click();\n            return false;\n          }\n        },\n\n        // For cross-browser consistency, don't fire native .click() on links\n        _default: function (event) {\n          return jQuery.nodeName(event.target, \"a\");\n        }\n      },\n\n      beforeunload: {\n        postDispatch: function (event) {\n\n          // Support: Firefox 20+\n          // Firefox doesn't alert if the returnValue field is not set.\n          if (event.result !== undefined) {\n            event.originalEvent.returnValue = event.result;\n          }\n        }\n      }\n    },\n\n    simulate: function (type, elem, event, bubble) {\n      // Piggyback on a donor event to simulate a different one.\n      // Fake originalEvent to avoid donor's stopPropagation, but if the\n      // simulated event prevents default then we do the same on the donor.\n      var e = jQuery.extend(\n        new jQuery.Event(),\n        event,\n        {\n          type: type,\n          isSimulated: true,\n          originalEvent: {}\n        }\n      );\n      if (bubble) {\n        jQuery.event.trigger(e, null, elem);\n      } else {\n        jQuery.event.dispatch.call(elem, e);\n      }\n      if (e.isDefaultPrevented()) {\n        event.preventDefault();\n      }\n    }\n  };\n\n  jQuery.removeEvent = function (elem, type, handle) {\n    if (elem.removeEventListener) {\n      elem.removeEventListener(type, handle, false);\n    }\n  };\n\n  jQuery.Event = function (src, props) {\n    // Allow instantiation without the 'new' keyword\n    if (!(this instanceof jQuery.Event)) {\n      return new jQuery.Event(src, props);\n    }\n\n    // Event object\n    if (src && src.type) {\n      this.originalEvent = src;\n      this.type = src.type;\n\n      // Events bubbling up the document may have been marked as prevented\n      // by a handler lower down the tree; reflect the correct value.\n      this.isDefaultPrevented = src.defaultPrevented ||\n        // Support: Android < 4.0\n        src.defaultPrevented === undefined &&\n        src.getPreventDefault && src.getPreventDefault() ?\n        returnTrue :\n        returnFalse;\n\n      // Event type\n    } else {\n      this.type = src;\n    }\n\n    // Put explicitly provided properties onto the event object\n    if (props) {\n      jQuery.extend(this, props);\n    }\n\n    // Create a timestamp if incoming event doesn't have one\n    this.timeStamp = src && src.timeStamp || jQuery.now();\n\n    // Mark it as fixed\n    this[ jQuery.expando ] = true;\n  };\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\n  jQuery.Event.prototype = {\n    isDefaultPrevented: returnFalse,\n    isPropagationStopped: returnFalse,\n    isImmediatePropagationStopped: returnFalse,\n\n    preventDefault: function () {\n      var e = this.originalEvent;\n\n      this.isDefaultPrevented = returnTrue;\n\n      if (e && e.preventDefault) {\n        e.preventDefault();\n      }\n    },\n    stopPropagation: function () {\n      var e = this.originalEvent;\n\n      this.isPropagationStopped = returnTrue;\n\n      if (e && e.stopPropagation) {\n        e.stopPropagation();\n      }\n    },\n    stopImmediatePropagation: function () {\n      this.isImmediatePropagationStopped = returnTrue;\n      this.stopPropagation();\n    }\n  };\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// Support: Chrome 15+\n  jQuery.each({\n    mouseenter: \"mouseover\",\n    mouseleave: \"mouseout\"\n  }, function (orig, fix) {\n    jQuery.event.special[ orig ] = {\n      delegateType: fix,\n      bindType: fix,\n\n      handle: function (event) {\n        var ret,\n          target = this,\n          related = event.relatedTarget,\n          handleObj = event.handleObj;\n\n        // For mousenter/leave call the handler if related is outside the target.\n        // NB: No relatedTarget if the mouse left/entered the browser window\n        if (!related || (related !== target && !jQuery.contains(target, related))) {\n          event.type = handleObj.origType;\n          ret = handleObj.handler.apply(this, arguments);\n          event.type = fix;\n        }\n        return ret;\n      }\n    };\n  });\n\n// Create \"bubbling\" focus and blur events\n// Support: Firefox, Chrome, Safari\n  if (!support.focusinBubbles) {\n    jQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function (orig, fix) {\n\n      // Attach a single capturing handler on the document while someone wants focusin/focusout\n      var handler = function (event) {\n        jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true);\n      };\n\n      jQuery.event.special[ fix ] = {\n        setup: function () {\n          var doc = this.ownerDocument || this,\n            attaches = data_priv.access(doc, fix);\n\n          if (!attaches) {\n            doc.addEventListener(orig, handler, true);\n          }\n          data_priv.access(doc, fix, ( attaches || 0 ) + 1);\n        },\n        teardown: function () {\n          var doc = this.ownerDocument || this,\n            attaches = data_priv.access(doc, fix) - 1;\n\n          if (!attaches) {\n            doc.removeEventListener(orig, handler, true);\n            data_priv.remove(doc, fix);\n\n          } else {\n            data_priv.access(doc, fix, attaches);\n          }\n        }\n      };\n    });\n  }\n\n  jQuery.fn.extend({\n\n    on: function (types, selector, data, fn, /*INTERNAL*/ one) {\n      var origFn, type;\n\n      // Types can be a map of types/handlers\n      if (typeof types === \"object\") {\n        // ( types-Object, selector, data )\n        if (typeof selector !== \"string\") {\n          // ( types-Object, data )\n          data = data || selector;\n          selector = undefined;\n        }\n        for (type in types) {\n          this.on(type, selector, data, types[ type ], one);\n        }\n        return this;\n      }\n\n      if (data == null && fn == null) {\n        // ( types, fn )\n        fn = selector;\n        data = selector = undefined;\n      } else if (fn == null) {\n        if (typeof selector === \"string\") {\n          // ( types, selector, fn )\n          fn = data;\n          data = undefined;\n        } else {\n          // ( types, data, fn )\n          fn = data;\n          data = selector;\n          selector = undefined;\n        }\n      }\n      if (fn === false) {\n        fn = returnFalse;\n      } else if (!fn) {\n        return this;\n      }\n\n      if (one === 1) {\n        origFn = fn;\n        fn = function (event) {\n          // Can use an empty set, since event contains the info\n          jQuery().off(event);\n          return origFn.apply(this, arguments);\n        };\n        // Use same guid so caller can remove using origFn\n        fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n      }\n      return this.each(function () {\n        jQuery.event.add(this, types, fn, data, selector);\n      });\n    },\n    one: function (types, selector, data, fn) {\n      return this.on(types, selector, data, fn, 1);\n    },\n    off: function (types, selector, fn) {\n      var handleObj, type;\n      if (types && types.preventDefault && types.handleObj) {\n        // ( event )  dispatched jQuery.Event\n        handleObj = types.handleObj;\n        jQuery(types.delegateTarget).off(\n          handleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n          handleObj.selector,\n          handleObj.handler\n        );\n        return this;\n      }\n      if (typeof types === \"object\") {\n        // ( types-object [, selector] )\n        for (type in types) {\n          this.off(type, selector, types[ type ]);\n        }\n        return this;\n      }\n      if (selector === false || typeof selector === \"function\") {\n        // ( types [, fn] )\n        fn = selector;\n        selector = undefined;\n      }\n      if (fn === false) {\n        fn = returnFalse;\n      }\n      return this.each(function () {\n        jQuery.event.remove(this, types, fn, selector);\n      });\n    },\n\n    trigger: function (type, data) {\n      return this.each(function () {\n        jQuery.event.trigger(type, data, this);\n      });\n    },\n    triggerHandler: function (type, data) {\n      var elem = this[0];\n      if (elem) {\n        return jQuery.event.trigger(type, data, elem, true);\n      }\n    }\n  });\n\n\n  var\n    rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n    rtagName = /<([\\w:]+)/,\n    rhtml = /<|&#?\\w+;/,\n    rnoInnerhtml = /<(?:script|style|link)/i,\n  // checked=\"checked\" or checked\n    rchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n    rscriptType = /^$|\\/(?:java|ecma)script/i,\n    rscriptTypeMasked = /^true\\/(.*)/,\n    rcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n  // We have to close these tags to support XHTML (#13200)\n    wrapMap = {\n\n      // Support: IE 9\n      option: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\n      thead: [ 1, \"<table>\", \"</table>\" ],\n      col: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n      tr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n      td: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n      _default: [ 0, \"\", \"\" ]\n    };\n\n// Support: IE 9\n  wrapMap.optgroup = wrapMap.option;\n\n  wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\n  wrapMap.th = wrapMap.td;\n\n// Support: 1.x compatibility\n// Manipulating tables requires a tbody\n  function manipulationTarget(elem, content) {\n    return jQuery.nodeName(elem, \"table\") &&\n      jQuery.nodeName(content.nodeType !== 11 ? content : content.firstChild, \"tr\") ?\n\n      elem.getElementsByTagName(\"tbody\")[0] ||\n      elem.appendChild(elem.ownerDocument.createElement(\"tbody\")) :\n      elem;\n  }\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\n  function disableScript(elem) {\n    elem.type = (elem.getAttribute(\"type\") !== null) + \"/\" + elem.type;\n    return elem;\n  }\n\n  function restoreScript(elem) {\n    var match = rscriptTypeMasked.exec(elem.type);\n\n    if (match) {\n      elem.type = match[ 1 ];\n    } else {\n      elem.removeAttribute(\"type\");\n    }\n\n    return elem;\n  }\n\n// Mark scripts as having already been evaluated\n  function setGlobalEval(elems, refElements) {\n    var i = 0,\n      l = elems.length;\n\n    for (; i < l; i++) {\n      data_priv.set(\n        elems[ i ], \"globalEval\", !refElements || data_priv.get(refElements[ i ], \"globalEval\")\n      );\n    }\n  }\n\n  function cloneCopyEvent(src, dest) {\n    var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;\n\n    if (dest.nodeType !== 1) {\n      return;\n    }\n\n    // 1. Copy private data: events, handlers, etc.\n    if (data_priv.hasData(src)) {\n      pdataOld = data_priv.access(src);\n      pdataCur = data_priv.set(dest, pdataOld);\n      events = pdataOld.events;\n\n      if (events) {\n        delete pdataCur.handle;\n        pdataCur.events = {};\n\n        for (type in events) {\n          for (i = 0, l = events[ type ].length; i < l; i++) {\n            jQuery.event.add(dest, type, events[ type ][ i ]);\n          }\n        }\n      }\n    }\n\n    // 2. Copy user data\n    if (data_user.hasData(src)) {\n      udataOld = data_user.access(src);\n      udataCur = jQuery.extend({}, udataOld);\n\n      data_user.set(dest, udataCur);\n    }\n  }\n\n  function getAll(context, tag) {\n    var ret = context.getElementsByTagName ? context.getElementsByTagName(tag || \"*\") :\n      context.querySelectorAll ? context.querySelectorAll(tag || \"*\") :\n        [];\n\n    return tag === undefined || tag && jQuery.nodeName(context, tag) ?\n      jQuery.merge([ context ], ret) :\n      ret;\n  }\n\n// Support: IE >= 9\n  function fixInput(src, dest) {\n    var nodeName = dest.nodeName.toLowerCase();\n\n    // Fails to persist the checked state of a cloned checkbox or radio button.\n    if (nodeName === \"input\" && rcheckableType.test(src.type)) {\n      dest.checked = src.checked;\n\n      // Fails to return the selected option to the default selected state when cloning options\n    } else if (nodeName === \"input\" || nodeName === \"textarea\") {\n      dest.defaultValue = src.defaultValue;\n    }\n  }\n\n  jQuery.extend({\n    clone: function (elem, dataAndEvents, deepDataAndEvents) {\n      var i, l, srcElements, destElements,\n        clone = elem.cloneNode(true),\n        inPage = jQuery.contains(elem.ownerDocument, elem);\n\n      // Support: IE >= 9\n      // Fix Cloning issues\n      if (!support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc(elem)) {\n\n        // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n        destElements = getAll(clone);\n        srcElements = getAll(elem);\n\n        for (i = 0, l = srcElements.length; i < l; i++) {\n          fixInput(srcElements[ i ], destElements[ i ]);\n        }\n      }\n\n      // Copy the events from the original to the clone\n      if (dataAndEvents) {\n        if (deepDataAndEvents) {\n          srcElements = srcElements || getAll(elem);\n          destElements = destElements || getAll(clone);\n\n          for (i = 0, l = srcElements.length; i < l; i++) {\n            cloneCopyEvent(srcElements[ i ], destElements[ i ]);\n          }\n        } else {\n          cloneCopyEvent(elem, clone);\n        }\n      }\n\n      // Preserve script evaluation history\n      destElements = getAll(clone, \"script\");\n      if (destElements.length > 0) {\n        setGlobalEval(destElements, !inPage && getAll(elem, \"script\"));\n      }\n\n      // Return the cloned set\n      return clone;\n    },\n\n    buildFragment: function (elems, context, scripts, selection) {\n      var elem, tmp, tag, wrap, contains, j,\n        fragment = context.createDocumentFragment(),\n        nodes = [],\n        i = 0,\n        l = elems.length;\n\n      for (; i < l; i++) {\n        elem = elems[ i ];\n\n        if (elem || elem === 0) {\n\n          // Add nodes directly\n          if (jQuery.type(elem) === \"object\") {\n            // Support: QtWebKit\n            // jQuery.merge because push.apply(_, arraylike) throws\n            jQuery.merge(nodes, elem.nodeType ? [ elem ] : elem);\n\n            // Convert non-html into a text node\n          } else if (!rhtml.test(elem)) {\n            nodes.push(context.createTextNode(elem));\n\n            // Convert html into DOM nodes\n          } else {\n            tmp = tmp || fragment.appendChild(context.createElement(\"div\"));\n\n            // Deserialize a standard representation\n            tag = ( rtagName.exec(elem) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n            wrap = wrapMap[ tag ] || wrapMap._default;\n            tmp.innerHTML = wrap[ 1 ] + elem.replace(rxhtmlTag, \"<$1></$2>\") + wrap[ 2 ];\n\n            // Descend through wrappers to the right content\n            j = wrap[ 0 ];\n            while (j--) {\n              tmp = tmp.lastChild;\n            }\n\n            // Support: QtWebKit\n            // jQuery.merge because push.apply(_, arraylike) throws\n            jQuery.merge(nodes, tmp.childNodes);\n\n            // Remember the top-level container\n            tmp = fragment.firstChild;\n\n            // Fixes #12346\n            // Support: Webkit, IE\n            tmp.textContent = \"\";\n          }\n        }\n      }\n\n      // Remove wrapper from fragment\n      fragment.textContent = \"\";\n\n      i = 0;\n      while ((elem = nodes[ i++ ])) {\n\n        // #4087 - If origin and destination elements are the same, and this is\n        // that element, do not do anything\n        if (selection && jQuery.inArray(elem, selection) !== -1) {\n          continue;\n        }\n\n        contains = jQuery.contains(elem.ownerDocument, elem);\n\n        // Append to fragment\n        tmp = getAll(fragment.appendChild(elem), \"script\");\n\n        // Preserve script evaluation history\n        if (contains) {\n          setGlobalEval(tmp);\n        }\n\n        // Capture executables\n        if (scripts) {\n          j = 0;\n          while ((elem = tmp[ j++ ])) {\n            if (rscriptType.test(elem.type || \"\")) {\n              scripts.push(elem);\n            }\n          }\n        }\n      }\n\n      return fragment;\n    },\n\n    cleanData: function (elems) {\n      var data, elem, events, type, key, j,\n        special = jQuery.event.special,\n        i = 0;\n\n      for (; (elem = elems[ i ]) !== undefined; i++) {\n        if (jQuery.acceptData(elem)) {\n          key = elem[ data_priv.expando ];\n\n          if (key && (data = data_priv.cache[ key ])) {\n            events = Object.keys(data.events || {});\n            if (events.length) {\n              for (j = 0; (type = events[j]) !== undefined; j++) {\n                if (special[ type ]) {\n                  jQuery.event.remove(elem, type);\n\n                  // This is a shortcut to avoid jQuery.event.remove's overhead\n                } else {\n                  jQuery.removeEvent(elem, type, data.handle);\n                }\n              }\n            }\n            if (data_priv.cache[ key ]) {\n              // Discard any remaining `private` data\n              delete data_priv.cache[ key ];\n            }\n          }\n        }\n        // Discard any remaining `user` data\n        delete data_user.cache[ elem[ data_user.expando ] ];\n      }\n    }\n  });\n\n  jQuery.fn.extend({\n    text: function (value) {\n      return access(this, function (value) {\n        return value === undefined ?\n          jQuery.text(this) :\n          this.empty().each(function () {\n            if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) {\n              this.textContent = value;\n            }\n          });\n      }, null, value, arguments.length);\n    },\n\n    append: function () {\n      return this.domManip(arguments, function (elem) {\n        if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) {\n          var target = manipulationTarget(this, elem);\n          target.appendChild(elem);\n        }\n      });\n    },\n\n    prepend: function () {\n      return this.domManip(arguments, function (elem) {\n        if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) {\n          var target = manipulationTarget(this, elem);\n          target.insertBefore(elem, target.firstChild);\n        }\n      });\n    },\n\n    before: function () {\n      return this.domManip(arguments, function (elem) {\n        if (this.parentNode) {\n          this.parentNode.insertBefore(elem, this);\n        }\n      });\n    },\n\n    after: function () {\n      return this.domManip(arguments, function (elem) {\n        if (this.parentNode) {\n          this.parentNode.insertBefore(elem, this.nextSibling);\n        }\n      });\n    },\n\n    remove: function (selector, keepData /* Internal Use Only */) {\n      var elem,\n        elems = selector ? jQuery.filter(selector, this) : this,\n        i = 0;\n\n      for (; (elem = elems[i]) != null; i++) {\n        if (!keepData && elem.nodeType === 1) {\n          jQuery.cleanData(getAll(elem));\n        }\n\n        if (elem.parentNode) {\n          if (keepData && jQuery.contains(elem.ownerDocument, elem)) {\n            setGlobalEval(getAll(elem, \"script\"));\n          }\n          elem.parentNode.removeChild(elem);\n        }\n      }\n\n      return this;\n    },\n\n    empty: function () {\n      var elem,\n        i = 0;\n\n      for (; (elem = this[i]) != null; i++) {\n        if (elem.nodeType === 1) {\n\n          // Prevent memory leaks\n          jQuery.cleanData(getAll(elem, false));\n\n          // Remove any remaining nodes\n          elem.textContent = \"\";\n        }\n      }\n\n      return this;\n    },\n\n    clone: function (dataAndEvents, deepDataAndEvents) {\n      dataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n      deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n      return this.map(function () {\n        return jQuery.clone(this, dataAndEvents, deepDataAndEvents);\n      });\n    },\n\n    html: function (value) {\n      return access(this, function (value) {\n        var elem = this[ 0 ] || {},\n          i = 0,\n          l = this.length;\n\n        if (value === undefined && elem.nodeType === 1) {\n          return elem.innerHTML;\n        }\n\n        // See if we can take a shortcut and just use innerHTML\n        if (typeof value === \"string\" && !rnoInnerhtml.test(value) && !wrapMap[ ( rtagName.exec(value) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ]) {\n\n          value = value.replace(rxhtmlTag, \"<$1></$2>\");\n\n          try {\n            for (; i < l; i++) {\n              elem = this[ i ] || {};\n\n              // Remove element nodes and prevent memory leaks\n              if (elem.nodeType === 1) {\n                jQuery.cleanData(getAll(elem, false));\n                elem.innerHTML = value;\n              }\n            }\n\n            elem = 0;\n\n            // If using innerHTML throws an exception, use the fallback method\n          } catch (e) {\n          }\n        }\n\n        if (elem) {\n          this.empty().append(value);\n        }\n      }, null, value, arguments.length);\n    },\n\n    replaceWith: function () {\n      var arg = arguments[ 0 ];\n\n      // Make the changes, replacing each context element with the new content\n      this.domManip(arguments, function (elem) {\n        arg = this.parentNode;\n\n        jQuery.cleanData(getAll(this));\n\n        if (arg) {\n          arg.replaceChild(elem, this);\n        }\n      });\n\n      // Force removal if there was no new content (e.g., from empty arguments)\n      return arg && (arg.length || arg.nodeType) ? this : this.remove();\n    },\n\n    detach: function (selector) {\n      return this.remove(selector, true);\n    },\n\n    domManip: function (args, callback) {\n\n      // Flatten any nested arrays\n      args = concat.apply([], args);\n\n      var fragment, first, scripts, hasScripts, node, doc,\n        i = 0,\n        l = this.length,\n        set = this,\n        iNoClone = l - 1,\n        value = args[ 0 ],\n        isFunction = jQuery.isFunction(value);\n\n      // We can't cloneNode fragments that contain checked, in WebKit\n      if (isFunction ||\n        ( l > 1 && typeof value === \"string\" && !support.checkClone && rchecked.test(value) )) {\n        return this.each(function (index) {\n          var self = set.eq(index);\n          if (isFunction) {\n            args[ 0 ] = value.call(this, index, self.html());\n          }\n          self.domManip(args, callback);\n        });\n      }\n\n      if (l) {\n        fragment = jQuery.buildFragment(args, this[ 0 ].ownerDocument, false, this);\n        first = fragment.firstChild;\n\n        if (fragment.childNodes.length === 1) {\n          fragment = first;\n        }\n\n        if (first) {\n          scripts = jQuery.map(getAll(fragment, \"script\"), disableScript);\n          hasScripts = scripts.length;\n\n          // Use the original fragment for the last item instead of the first because it can end up\n          // being emptied incorrectly in certain situations (#8070).\n          for (; i < l; i++) {\n            node = fragment;\n\n            if (i !== iNoClone) {\n              node = jQuery.clone(node, true, true);\n\n              // Keep references to cloned scripts for later restoration\n              if (hasScripts) {\n                // Support: QtWebKit\n                // jQuery.merge because push.apply(_, arraylike) throws\n                jQuery.merge(scripts, getAll(node, \"script\"));\n              }\n            }\n\n            callback.call(this[ i ], node, i);\n          }\n\n          if (hasScripts) {\n            doc = scripts[ scripts.length - 1 ].ownerDocument;\n\n            // Reenable scripts\n            jQuery.map(scripts, restoreScript);\n\n            // Evaluate executable scripts on first document insertion\n            for (i = 0; i < hasScripts; i++) {\n              node = scripts[ i ];\n              if (rscriptType.test(node.type || \"\") && !data_priv.access(node, \"globalEval\") && jQuery.contains(doc, node)) {\n\n                if (node.src) {\n                  // Optional AJAX dependency, but won't run scripts if not present\n                  if (jQuery._evalUrl) {\n                    jQuery._evalUrl(node.src);\n                  }\n                } else {\n                  jQuery.globalEval(node.textContent.replace(rcleanScript, \"\"));\n                }\n              }\n            }\n          }\n        }\n      }\n\n      return this;\n    }\n  });\n\n  jQuery.each({\n    appendTo: \"append\",\n    prependTo: \"prepend\",\n    insertBefore: \"before\",\n    insertAfter: \"after\",\n    replaceAll: \"replaceWith\"\n  }, function (name, original) {\n    jQuery.fn[ name ] = function (selector) {\n      var elems,\n        ret = [],\n        insert = jQuery(selector),\n        last = insert.length - 1,\n        i = 0;\n\n      for (; i <= last; i++) {\n        elems = i === last ? this : this.clone(true);\n        jQuery(insert[ i ])[ original ](elems);\n\n        // Support: QtWebKit\n        // .get() because push.apply(_, arraylike) throws\n        push.apply(ret, elems.get());\n      }\n\n      return this.pushStack(ret);\n    };\n  });\n\n\n  var iframe,\n    elemdisplay = {};\n\n  /**\n   * Retrieve the actual display of a element\n   * @param {String} name nodeName of the element\n   * @param {Object} doc Document object\n   */\n// Called only from within defaultDisplay\n  function actualDisplay(name, doc) {\n    var elem = jQuery(doc.createElement(name)).appendTo(doc.body),\n\n    // getDefaultComputedStyle might be reliably used only on attached element\n      display = window.getDefaultComputedStyle ?\n\n        // Use of this method is a temporary fix (more like optmization) until something better comes along,\n        // since it was removed from specification and supported only in FF\n        window.getDefaultComputedStyle(elem[ 0 ]).display : jQuery.css(elem[ 0 ], \"display\");\n\n    // We don't have any data stored on the element,\n    // so use \"detach\" method as fast way to get rid of the element\n    elem.detach();\n\n    return display;\n  }\n\n  /**\n   * Try to determine the default display value of an element\n   * @param {String} nodeName\n   */\n  function defaultDisplay(nodeName) {\n    var doc = document,\n      display = elemdisplay[ nodeName ];\n\n    if (!display) {\n      display = actualDisplay(nodeName, doc);\n\n      // If the simple way fails, read from inside an iframe\n      if (display === \"none\" || !display) {\n\n        // Use the already-created iframe if possible\n        iframe = (iframe || jQuery(\"<iframe frameborder='0' width='0' height='0'/>\")).appendTo(doc.documentElement);\n\n        // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n        doc = iframe[ 0 ].contentDocument;\n\n        // Support: IE\n        doc.write();\n        doc.close();\n\n        display = actualDisplay(nodeName, doc);\n        iframe.detach();\n      }\n\n      // Store the correct default display\n      elemdisplay[ nodeName ] = display;\n    }\n\n    return display;\n  }\n\n  var rmargin = (/^margin/);\n\n  var rnumnonpx = new RegExp(\"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\");\n\n  var getStyles = function (elem) {\n    return elem.ownerDocument.defaultView.getComputedStyle(elem, null);\n  };\n\n\n  function curCSS(elem, name, computed) {\n    var width, minWidth, maxWidth, ret,\n      style = elem.style;\n\n    computed = computed || getStyles(elem);\n\n    // Support: IE9\n    // getPropertyValue is only needed for .css('filter') in IE9, see #12537\n    if (computed) {\n      ret = computed.getPropertyValue(name) || computed[ name ];\n    }\n\n    if (computed) {\n\n      if (ret === \"\" && !jQuery.contains(elem.ownerDocument, elem)) {\n        ret = jQuery.style(elem, name);\n      }\n\n      // Support: iOS < 6\n      // A tribute to the \"awesome hack by Dean Edwards\"\n      // iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n      // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n      if (rnumnonpx.test(ret) && rmargin.test(name)) {\n\n        // Remember the original values\n        width = style.width;\n        minWidth = style.minWidth;\n        maxWidth = style.maxWidth;\n\n        // Put in the new values to get a computed value out\n        style.minWidth = style.maxWidth = style.width = ret;\n        ret = computed.width;\n\n        // Revert the changed values\n        style.width = width;\n        style.minWidth = minWidth;\n        style.maxWidth = maxWidth;\n      }\n    }\n\n    return ret !== undefined ?\n      // Support: IE\n      // IE returns zIndex value as an integer.\n      ret + \"\" :\n      ret;\n  }\n\n\n  function addGetHookIf(conditionFn, hookFn) {\n    // Define the hook, we'll check on the first run if it's really needed.\n    return {\n      get: function () {\n        if (conditionFn()) {\n          // Hook not needed (or it's not possible to use it due to missing dependency),\n          // remove it.\n          // Since there are no other hooks for marginRight, remove the whole object.\n          delete this.get;\n          return;\n        }\n\n        // Hook needed; redefine it so that the support test is not executed again.\n\n        return (this.get = hookFn).apply(this, arguments);\n      }\n    };\n  }\n\n\n  (function () {\n    var pixelPositionVal, boxSizingReliableVal,\n    // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n      divReset = \"padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;\" +\n        \"-moz-box-sizing:content-box;box-sizing:content-box\",\n      docElem = document.documentElement,\n      container = document.createElement(\"div\"),\n      div = document.createElement(\"div\");\n\n    div.style.backgroundClip = \"content-box\";\n    div.cloneNode(true).style.backgroundClip = \"\";\n    support.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n    container.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;\" +\n      \"margin-top:1px\";\n    container.appendChild(div);\n\n    // Executing both pixelPosition & boxSizingReliable tests require only one layout\n    // so they're executed at the same time to save the second computation.\n    function computePixelPositionAndBoxSizingReliable() {\n      // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n      div.style.cssText = \"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;\" +\n        \"box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;\" +\n        \"position:absolute;top:1%\";\n      docElem.appendChild(container);\n\n      var divStyle = window.getComputedStyle(div, null);\n      pixelPositionVal = divStyle.top !== \"1%\";\n      boxSizingReliableVal = divStyle.width === \"4px\";\n\n      docElem.removeChild(container);\n    }\n\n    // Use window.getComputedStyle because jsdom on node.js will break without it.\n    if (window.getComputedStyle) {\n      jQuery.extend(support, {\n        pixelPosition: function () {\n          // This test is executed only once but we still do memoizing\n          // since we can use the boxSizingReliable pre-computing.\n          // No need to check if the test was already performed, though.\n          computePixelPositionAndBoxSizingReliable();\n          return pixelPositionVal;\n        },\n        boxSizingReliable: function () {\n          if (boxSizingReliableVal == null) {\n            computePixelPositionAndBoxSizingReliable();\n          }\n          return boxSizingReliableVal;\n        },\n        reliableMarginRight: function () {\n          // Support: Android 2.3\n          // Check if div with explicit width and no margin-right incorrectly\n          // gets computed margin-right based on width of container. (#3333)\n          // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n          // This support function is only executed once so no memoizing is needed.\n          var ret,\n            marginDiv = div.appendChild(document.createElement(\"div\"));\n          marginDiv.style.cssText = div.style.cssText = divReset;\n          marginDiv.style.marginRight = marginDiv.style.width = \"0\";\n          div.style.width = \"1px\";\n          docElem.appendChild(container);\n\n          ret = !parseFloat(window.getComputedStyle(marginDiv, null).marginRight);\n\n          docElem.removeChild(container);\n\n          // Clean up the div for other support tests.\n          div.innerHTML = \"\";\n\n          return ret;\n        }\n      });\n    }\n  })();\n\n\n// A method for quickly swapping in/out CSS properties to get correct calculations.\n  jQuery.swap = function (elem, options, callback, args) {\n    var ret, name,\n      old = {};\n\n    // Remember the old values, and insert the new ones\n    for (name in options) {\n      old[ name ] = elem.style[ name ];\n      elem.style[ name ] = options[ name ];\n    }\n\n    ret = callback.apply(elem, args || []);\n\n    // Revert the old values\n    for (name in options) {\n      elem.style[ name ] = old[ name ];\n    }\n\n    return ret;\n  };\n\n\n  var\n  // swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n  // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n    rdisplayswap = /^(none|table(?!-c[ea]).+)/,\n    rnumsplit = new RegExp(\"^(\" + pnum + \")(.*)$\", \"i\"),\n    rrelNum = new RegExp(\"^([+-])=(\" + pnum + \")\", \"i\"),\n\n    cssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n    cssNormalTransform = {\n      letterSpacing: 0,\n      fontWeight: 400\n    },\n\n    cssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// return a css property mapped to a potentially vendor prefixed property\n  function vendorPropName(style, name) {\n\n    // shortcut for names that are not vendor prefixed\n    if (name in style) {\n      return name;\n    }\n\n    // check for vendor prefixed names\n    var capName = name[0].toUpperCase() + name.slice(1),\n      origName = name,\n      i = cssPrefixes.length;\n\n    while (i--) {\n      name = cssPrefixes[ i ] + capName;\n      if (name in style) {\n        return name;\n      }\n    }\n\n    return origName;\n  }\n\n  function setPositiveNumber(elem, value, subtract) {\n    var matches = rnumsplit.exec(value);\n    return matches ?\n      // Guard against undefined \"subtract\", e.g., when used as in cssHooks\n      Math.max(0, matches[ 1 ] - ( subtract || 0 )) + ( matches[ 2 ] || \"px\" ) :\n      value;\n  }\n\n  function augmentWidthOrHeight(elem, name, extra, isBorderBox, styles) {\n    var i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n        // If we already have the right measurement, avoid augmentation\n        4 :\n        // Otherwise initialize for horizontal or vertical properties\n          name === \"width\" ? 1 : 0,\n\n      val = 0;\n\n    for (; i < 4; i += 2) {\n      // both box models exclude margin, so add it if we want it\n      if (extra === \"margin\") {\n        val += jQuery.css(elem, extra + cssExpand[ i ], true, styles);\n      }\n\n      if (isBorderBox) {\n        // border-box includes padding, so remove it if we want content\n        if (extra === \"content\") {\n          val -= jQuery.css(elem, \"padding\" + cssExpand[ i ], true, styles);\n        }\n\n        // at this point, extra isn't border nor margin, so remove border\n        if (extra !== \"margin\") {\n          val -= jQuery.css(elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles);\n        }\n      } else {\n        // at this point, extra isn't content, so add padding\n        val += jQuery.css(elem, \"padding\" + cssExpand[ i ], true, styles);\n\n        // at this point, extra isn't content nor padding, so add border\n        if (extra !== \"padding\") {\n          val += jQuery.css(elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles);\n        }\n      }\n    }\n\n    return val;\n  }\n\n  function getWidthOrHeight(elem, name, extra) {\n\n    // Start with offset property, which is equivalent to the border-box value\n    var valueIsBorderBox = true,\n      val = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n      styles = getStyles(elem),\n      isBorderBox = jQuery.css(elem, \"boxSizing\", false, styles) === \"border-box\";\n\n    // some non-html elements return undefined for offsetWidth, so check for null/undefined\n    // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n    // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n    if (val <= 0 || val == null) {\n      // Fall back to computed then uncomputed css if necessary\n      val = curCSS(elem, name, styles);\n      if (val < 0 || val == null) {\n        val = elem.style[ name ];\n      }\n\n      // Computed unit is not pixels. Stop here and return.\n      if (rnumnonpx.test(val)) {\n        return val;\n      }\n\n      // we need the check for style in case a browser which returns unreliable values\n      // for getComputedStyle silently falls back to the reliable elem.style\n      valueIsBorderBox = isBorderBox &&\n        ( support.boxSizingReliable() || val === elem.style[ name ] );\n\n      // Normalize \"\", auto, and prepare for extra\n      val = parseFloat(val) || 0;\n    }\n\n    // use the active box-sizing model to add/subtract irrelevant styles\n    return ( val +\n      augmentWidthOrHeight(\n        elem,\n        name,\n          extra || ( isBorderBox ? \"border\" : \"content\" ),\n        valueIsBorderBox,\n        styles\n      )\n      ) + \"px\";\n  }\n\n  function showHide(elements, show) {\n    var display, elem, hidden,\n      values = [],\n      index = 0,\n      length = elements.length;\n\n    for (; index < length; index++) {\n      elem = elements[ index ];\n      if (!elem.style) {\n        continue;\n      }\n\n      values[ index ] = data_priv.get(elem, \"olddisplay\");\n      display = elem.style.display;\n      if (show) {\n        // Reset the inline display of this element to learn if it is\n        // being hidden by cascaded rules or not\n        if (!values[ index ] && display === \"none\") {\n          elem.style.display = \"\";\n        }\n\n        // Set elements which have been overridden with display: none\n        // in a stylesheet to whatever the default browser style is\n        // for such an element\n        if (elem.style.display === \"\" && isHidden(elem)) {\n          values[ index ] = data_priv.access(elem, \"olddisplay\", defaultDisplay(elem.nodeName));\n        }\n      } else {\n\n        if (!values[ index ]) {\n          hidden = isHidden(elem);\n\n          if (display && display !== \"none\" || !hidden) {\n            data_priv.set(elem, \"olddisplay\", hidden ? display : jQuery.css(elem, \"display\"));\n          }\n        }\n      }\n    }\n\n    // Set the display of most of the elements in a second loop\n    // to avoid the constant reflow\n    for (index = 0; index < length; index++) {\n      elem = elements[ index ];\n      if (!elem.style) {\n        continue;\n      }\n      if (!show || elem.style.display === \"none\" || elem.style.display === \"\") {\n        elem.style.display = show ? values[ index ] || \"\" : \"none\";\n      }\n    }\n\n    return elements;\n  }\n\n  jQuery.extend({\n    // Add in style property hooks for overriding the default\n    // behavior of getting and setting a style property\n    cssHooks: {\n      opacity: {\n        get: function (elem, computed) {\n          if (computed) {\n            // We should always get a number back from opacity\n            var ret = curCSS(elem, \"opacity\");\n            return ret === \"\" ? \"1\" : ret;\n          }\n        }\n      }\n    },\n\n    // Don't automatically add \"px\" to these possibly-unitless properties\n    cssNumber: {\n      \"columnCount\": true,\n      \"fillOpacity\": true,\n      \"fontWeight\": true,\n      \"lineHeight\": true,\n      \"opacity\": true,\n      \"order\": true,\n      \"orphans\": true,\n      \"widows\": true,\n      \"zIndex\": true,\n      \"zoom\": true\n    },\n\n    // Add in properties whose names you wish to fix before\n    // setting or getting the value\n    cssProps: {\n      // normalize float css property\n      \"float\": \"cssFloat\"\n    },\n\n    // Get and set the style property on a DOM Node\n    style: function (elem, name, value, extra) {\n      // Don't set styles on text and comment nodes\n      if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) {\n        return;\n      }\n\n      // Make sure that we're working with the right name\n      var ret, type, hooks,\n        origName = jQuery.camelCase(name),\n        style = elem.style;\n\n      name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName(style, origName) );\n\n      // gets hook for the prefixed version\n      // followed by the unprefixed version\n      hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n      // Check if we're setting a value\n      if (value !== undefined) {\n        type = typeof value;\n\n        // convert relative number strings (+= or -=) to relative numbers. #7345\n        if (type === \"string\" && (ret = rrelNum.exec(value))) {\n          value = ( ret[1] + 1 ) * ret[2] + parseFloat(jQuery.css(elem, name));\n          // Fixes bug #9237\n          type = \"number\";\n        }\n\n        // Make sure that null and NaN values aren't set. See: #7116\n        if (value == null || value !== value) {\n          return;\n        }\n\n        // If a number was passed in, add 'px' to the (except for certain CSS properties)\n        if (type === \"number\" && !jQuery.cssNumber[ origName ]) {\n          value += \"px\";\n        }\n\n        // Fixes #8908, it can be done more correctly by specifying setters in cssHooks,\n        // but it would mean to define eight (for every problematic property) identical functions\n        if (!support.clearCloneStyle && value === \"\" && name.indexOf(\"background\") === 0) {\n          style[ name ] = \"inherit\";\n        }\n\n        // If a hook was provided, use that value, otherwise just set the specified value\n        if (!hooks || !(\"set\" in hooks) || (value = hooks.set(elem, value, extra)) !== undefined) {\n          // Support: Chrome, Safari\n          // Setting style to blank string required to delete \"style: x !important;\"\n          style[ name ] = \"\";\n          style[ name ] = value;\n        }\n\n      } else {\n        // If a hook was provided get the non-computed value from there\n        if (hooks && \"get\" in hooks && (ret = hooks.get(elem, false, extra)) !== undefined) {\n          return ret;\n        }\n\n        // Otherwise just get the value from the style object\n        return style[ name ];\n      }\n    },\n\n    css: function (elem, name, extra, styles) {\n      var val, num, hooks,\n        origName = jQuery.camelCase(name);\n\n      // Make sure that we're working with the right name\n      name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName(elem.style, origName) );\n\n      // gets hook for the prefixed version\n      // followed by the unprefixed version\n      hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n      // If a hook was provided get the computed value from there\n      if (hooks && \"get\" in hooks) {\n        val = hooks.get(elem, true, extra);\n      }\n\n      // Otherwise, if a way to get the computed value exists, use that\n      if (val === undefined) {\n        val = curCSS(elem, name, styles);\n      }\n\n      //convert \"normal\" to computed value\n      if (val === \"normal\" && name in cssNormalTransform) {\n        val = cssNormalTransform[ name ];\n      }\n\n      // Return, converting to number if forced or a qualifier was provided and val looks numeric\n      if (extra === \"\" || extra) {\n        num = parseFloat(val);\n        return extra === true || jQuery.isNumeric(num) ? num || 0 : val;\n      }\n      return val;\n    }\n  });\n\n  jQuery.each([ \"height\", \"width\" ], function (i, name) {\n    jQuery.cssHooks[ name ] = {\n      get: function (elem, computed, extra) {\n        if (computed) {\n          // certain elements can have dimension info if we invisibly show them\n          // however, it must have a current display style that would benefit from this\n          return elem.offsetWidth === 0 && rdisplayswap.test(jQuery.css(elem, \"display\")) ?\n            jQuery.swap(elem, cssShow, function () {\n              return getWidthOrHeight(elem, name, extra);\n            }) :\n            getWidthOrHeight(elem, name, extra);\n        }\n      },\n\n      set: function (elem, value, extra) {\n        var styles = extra && getStyles(elem);\n        return setPositiveNumber(elem, value, extra ?\n            augmentWidthOrHeight(\n              elem,\n              name,\n              extra,\n                jQuery.css(elem, \"boxSizing\", false, styles) === \"border-box\",\n              styles\n            ) : 0\n        );\n      }\n    };\n  });\n\n// Support: Android 2.3\n  jQuery.cssHooks.marginRight = addGetHookIf(support.reliableMarginRight,\n    function (elem, computed) {\n      if (computed) {\n        // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n        // Work around by temporarily setting element display to inline-block\n        return jQuery.swap(elem, { \"display\": \"inline-block\" },\n          curCSS, [ elem, \"marginRight\" ]);\n      }\n    }\n  );\n\n// These hooks are used by animate to expand properties\n  jQuery.each({\n    margin: \"\",\n    padding: \"\",\n    border: \"Width\"\n  }, function (prefix, suffix) {\n    jQuery.cssHooks[ prefix + suffix ] = {\n      expand: function (value) {\n        var i = 0,\n          expanded = {},\n\n        // assumes a single number if not a string\n          parts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n        for (; i < 4; i++) {\n          expanded[ prefix + cssExpand[ i ] + suffix ] =\n            parts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n        }\n\n        return expanded;\n      }\n    };\n\n    if (!rmargin.test(prefix)) {\n      jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n    }\n  });\n\n  jQuery.fn.extend({\n    css: function (name, value) {\n      return access(this, function (elem, name, value) {\n        var styles, len,\n          map = {},\n          i = 0;\n\n        if (jQuery.isArray(name)) {\n          styles = getStyles(elem);\n          len = name.length;\n\n          for (; i < len; i++) {\n            map[ name[ i ] ] = jQuery.css(elem, name[ i ], false, styles);\n          }\n\n          return map;\n        }\n\n        return value !== undefined ?\n          jQuery.style(elem, name, value) :\n          jQuery.css(elem, name);\n      }, name, value, arguments.length > 1);\n    },\n    show: function () {\n      return showHide(this, true);\n    },\n    hide: function () {\n      return showHide(this);\n    },\n    toggle: function (state) {\n      if (typeof state === \"boolean\") {\n        return state ? this.show() : this.hide();\n      }\n\n      return this.each(function () {\n        if (isHidden(this)) {\n          jQuery(this).show();\n        } else {\n          jQuery(this).hide();\n        }\n      });\n    }\n  });\n\n\n  function Tween(elem, options, prop, end, easing) {\n    return new Tween.prototype.init(elem, options, prop, end, easing);\n  }\n\n  jQuery.Tween = Tween;\n\n  Tween.prototype = {\n    constructor: Tween,\n    init: function (elem, options, prop, end, easing, unit) {\n      this.elem = elem;\n      this.prop = prop;\n      this.easing = easing || \"swing\";\n      this.options = options;\n      this.start = this.now = this.cur();\n      this.end = end;\n      this.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n    },\n    cur: function () {\n      var hooks = Tween.propHooks[ this.prop ];\n\n      return hooks && hooks.get ?\n        hooks.get(this) :\n        Tween.propHooks._default.get(this);\n    },\n    run: function (percent) {\n      var eased,\n        hooks = Tween.propHooks[ this.prop ];\n\n      if (this.options.duration) {\n        this.pos = eased = jQuery.easing[ this.easing ](\n          percent, this.options.duration * percent, 0, 1, this.options.duration\n        );\n      } else {\n        this.pos = eased = percent;\n      }\n      this.now = ( this.end - this.start ) * eased + this.start;\n\n      if (this.options.step) {\n        this.options.step.call(this.elem, this.now, this);\n      }\n\n      if (hooks && hooks.set) {\n        hooks.set(this);\n      } else {\n        Tween.propHooks._default.set(this);\n      }\n      return this;\n    }\n  };\n\n  Tween.prototype.init.prototype = Tween.prototype;\n\n  Tween.propHooks = {\n    _default: {\n      get: function (tween) {\n        var result;\n\n        if (tween.elem[ tween.prop ] != null &&\n          (!tween.elem.style || tween.elem.style[ tween.prop ] == null)) {\n          return tween.elem[ tween.prop ];\n        }\n\n        // passing an empty string as a 3rd parameter to .css will automatically\n        // attempt a parseFloat and fallback to a string if the parse fails\n        // so, simple values such as \"10px\" are parsed to Float.\n        // complex values such as \"rotate(1rad)\" are returned as is.\n        result = jQuery.css(tween.elem, tween.prop, \"\");\n        // Empty strings, null, undefined and \"auto\" are converted to 0.\n        return !result || result === \"auto\" ? 0 : result;\n      },\n      set: function (tween) {\n        // use step hook for back compat - use cssHook if its there - use .style if its\n        // available and use plain properties where available\n        if (jQuery.fx.step[ tween.prop ]) {\n          jQuery.fx.step[ tween.prop ](tween);\n        } else if (tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] )) {\n          jQuery.style(tween.elem, tween.prop, tween.now + tween.unit);\n        } else {\n          tween.elem[ tween.prop ] = tween.now;\n        }\n      }\n    }\n  };\n\n// Support: IE9\n// Panic based approach to setting things on disconnected nodes\n\n  Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n    set: function (tween) {\n      if (tween.elem.nodeType && tween.elem.parentNode) {\n        tween.elem[ tween.prop ] = tween.now;\n      }\n    }\n  };\n\n  jQuery.easing = {\n    linear: function (p) {\n      return p;\n    },\n    swing: function (p) {\n      return 0.5 - Math.cos(p * Math.PI) / 2;\n    }\n  };\n\n  jQuery.fx = Tween.prototype.init;\n\n// Back Compat <1.8 extension point\n  jQuery.fx.step = {};\n\n\n  var\n    fxNow, timerId,\n    rfxtypes = /^(?:toggle|show|hide)$/,\n    rfxnum = new RegExp(\"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\"),\n    rrun = /queueHooks$/,\n    animationPrefilters = [ defaultPrefilter ],\n    tweeners = {\n      \"*\": [ function (prop, value) {\n        var tween = this.createTween(prop, value),\n          target = tween.cur(),\n          parts = rfxnum.exec(value),\n          unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n        // Starting value computation is required for potential unit mismatches\n          start = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n            rfxnum.exec(jQuery.css(tween.elem, prop)),\n          scale = 1,\n          maxIterations = 20;\n\n        if (start && start[ 3 ] !== unit) {\n          // Trust units reported by jQuery.css\n          unit = unit || start[ 3 ];\n\n          // Make sure we update the tween properties later on\n          parts = parts || [];\n\n          // Iteratively approximate from a nonzero starting point\n          start = +target || 1;\n\n          do {\n            // If previous iteration zeroed out, double until we get *something*\n            // Use a string for doubling factor so we don't accidentally see scale as unchanged below\n            scale = scale || \".5\";\n\n            // Adjust and apply\n            start = start / scale;\n            jQuery.style(tween.elem, prop, start + unit);\n\n            // Update scale, tolerating zero or NaN from tween.cur()\n            // And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n          } while (scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations);\n        }\n\n        // Update tween properties\n        if (parts) {\n          start = tween.start = +start || +target || 0;\n          tween.unit = unit;\n          // If a +=/-= token was provided, we're doing a relative animation\n          tween.end = parts[ 1 ] ?\n            start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n            +parts[ 2 ];\n        }\n\n        return tween;\n      } ]\n    };\n\n// Animations created synchronously will run synchronously\n  function createFxNow() {\n    setTimeout(function () {\n      fxNow = undefined;\n    });\n    return ( fxNow = jQuery.now() );\n  }\n\n// Generate parameters to create a standard animation\n  function genFx(type, includeWidth) {\n    var which,\n      i = 0,\n      attrs = { height: type };\n\n    // if we include width, step value is 1 to do all cssExpand values,\n    // if we don't include width, step value is 2 to skip over Left and Right\n    includeWidth = includeWidth ? 1 : 0;\n    for (; i < 4; i += 2 - includeWidth) {\n      which = cssExpand[ i ];\n      attrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n    }\n\n    if (includeWidth) {\n      attrs.opacity = attrs.width = type;\n    }\n\n    return attrs;\n  }\n\n  function createTween(value, prop, animation) {\n    var tween,\n      collection = ( tweeners[ prop ] || [] ).concat(tweeners[ \"*\" ]),\n      index = 0,\n      length = collection.length;\n    for (; index < length; index++) {\n      if ((tween = collection[ index ].call(animation, prop, value))) {\n\n        // we're done with this property\n        return tween;\n      }\n    }\n  }\n\n  function defaultPrefilter(elem, props, opts) {\n    /* jshint validthis: true */\n    var prop, value, toggle, tween, hooks, oldfire, display,\n      anim = this,\n      orig = {},\n      style = elem.style,\n      hidden = elem.nodeType && isHidden(elem),\n      dataShow = data_priv.get(elem, \"fxshow\");\n\n    // handle queue: false promises\n    if (!opts.queue) {\n      hooks = jQuery._queueHooks(elem, \"fx\");\n      if (hooks.unqueued == null) {\n        hooks.unqueued = 0;\n        oldfire = hooks.empty.fire;\n        hooks.empty.fire = function () {\n          if (!hooks.unqueued) {\n            oldfire();\n          }\n        };\n      }\n      hooks.unqueued++;\n\n      anim.always(function () {\n        // doing this makes sure that the complete handler will be called\n        // before this completes\n        anim.always(function () {\n          hooks.unqueued--;\n          if (!jQuery.queue(elem, \"fx\").length) {\n            hooks.empty.fire();\n          }\n        });\n      });\n    }\n\n    // height/width overflow pass\n    if (elem.nodeType === 1 && ( \"height\" in props || \"width\" in props )) {\n      // Make sure that nothing sneaks out\n      // Record all 3 overflow attributes because IE9-10 do not\n      // change the overflow attribute when overflowX and\n      // overflowY are set to the same value\n      opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n      // Set display property to inline-block for height/width\n      // animations on inline elements that are having width/height animated\n      display = jQuery.css(elem, \"display\");\n      // Get default display if display is currently \"none\"\n      if (display === \"none\") {\n        display = defaultDisplay(elem.nodeName);\n      }\n      if (display === \"inline\" &&\n        jQuery.css(elem, \"float\") === \"none\") {\n\n        style.display = \"inline-block\";\n      }\n    }\n\n    if (opts.overflow) {\n      style.overflow = \"hidden\";\n      anim.always(function () {\n        style.overflow = opts.overflow[ 0 ];\n        style.overflowX = opts.overflow[ 1 ];\n        style.overflowY = opts.overflow[ 2 ];\n      });\n    }\n\n    // show/hide pass\n    for (prop in props) {\n      value = props[ prop ];\n      if (rfxtypes.exec(value)) {\n        delete props[ prop ];\n        toggle = toggle || value === \"toggle\";\n        if (value === ( hidden ? \"hide\" : \"show\" )) {\n\n          // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden\n          if (value === \"show\" && dataShow && dataShow[ prop ] !== undefined) {\n            hidden = true;\n          } else {\n            continue;\n          }\n        }\n        orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style(elem, prop);\n      }\n    }\n\n    if (!jQuery.isEmptyObject(orig)) {\n      if (dataShow) {\n        if (\"hidden\" in dataShow) {\n          hidden = dataShow.hidden;\n        }\n      } else {\n        dataShow = data_priv.access(elem, \"fxshow\", {});\n      }\n\n      // store state if its toggle - enables .stop().toggle() to \"reverse\"\n      if (toggle) {\n        dataShow.hidden = !hidden;\n      }\n      if (hidden) {\n        jQuery(elem).show();\n      } else {\n        anim.done(function () {\n          jQuery(elem).hide();\n        });\n      }\n      anim.done(function () {\n        var prop;\n\n        data_priv.remove(elem, \"fxshow\");\n        for (prop in orig) {\n          jQuery.style(elem, prop, orig[ prop ]);\n        }\n      });\n      for (prop in orig) {\n        tween = createTween(hidden ? dataShow[ prop ] : 0, prop, anim);\n\n        if (!( prop in dataShow )) {\n          dataShow[ prop ] = tween.start;\n          if (hidden) {\n            tween.end = tween.start;\n            tween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n          }\n        }\n      }\n    }\n  }\n\n  function propFilter(props, specialEasing) {\n    var index, name, easing, value, hooks;\n\n    // camelCase, specialEasing and expand cssHook pass\n    for (index in props) {\n      name = jQuery.camelCase(index);\n      easing = specialEasing[ name ];\n      value = props[ index ];\n      if (jQuery.isArray(value)) {\n        easing = value[ 1 ];\n        value = props[ index ] = value[ 0 ];\n      }\n\n      if (index !== name) {\n        props[ name ] = value;\n        delete props[ index ];\n      }\n\n      hooks = jQuery.cssHooks[ name ];\n      if (hooks && \"expand\" in hooks) {\n        value = hooks.expand(value);\n        delete props[ name ];\n\n        // not quite $.extend, this wont overwrite keys already present.\n        // also - reusing 'index' from above because we have the correct \"name\"\n        for (index in value) {\n          if (!( index in props )) {\n            props[ index ] = value[ index ];\n            specialEasing[ index ] = easing;\n          }\n        }\n      } else {\n        specialEasing[ name ] = easing;\n      }\n    }\n  }\n\n  function Animation(elem, properties, options) {\n    var result,\n      stopped,\n      index = 0,\n      length = animationPrefilters.length,\n      deferred = jQuery.Deferred().always(function () {\n        // don't match elem in the :animated selector\n        delete tick.elem;\n      }),\n      tick = function () {\n        if (stopped) {\n          return false;\n        }\n        var currentTime = fxNow || createFxNow(),\n          remaining = Math.max(0, animation.startTime + animation.duration - currentTime),\n        // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n          temp = remaining / animation.duration || 0,\n          percent = 1 - temp,\n          index = 0,\n          length = animation.tweens.length;\n\n        for (; index < length; index++) {\n          animation.tweens[ index ].run(percent);\n        }\n\n        deferred.notifyWith(elem, [ animation, percent, remaining ]);\n\n        if (percent < 1 && length) {\n          return remaining;\n        } else {\n          deferred.resolveWith(elem, [ animation ]);\n          return false;\n        }\n      },\n      animation = deferred.promise({\n        elem: elem,\n        props: jQuery.extend({}, properties),\n        opts: jQuery.extend(true, { specialEasing: {} }, options),\n        originalProperties: properties,\n        originalOptions: options,\n        startTime: fxNow || createFxNow(),\n        duration: options.duration,\n        tweens: [],\n        createTween: function (prop, end) {\n          var tween = jQuery.Tween(elem, animation.opts, prop, end,\n              animation.opts.specialEasing[ prop ] || animation.opts.easing);\n          animation.tweens.push(tween);\n          return tween;\n        },\n        stop: function (gotoEnd) {\n          var index = 0,\n          // if we are going to the end, we want to run all the tweens\n          // otherwise we skip this part\n            length = gotoEnd ? animation.tweens.length : 0;\n          if (stopped) {\n            return this;\n          }\n          stopped = true;\n          for (; index < length; index++) {\n            animation.tweens[ index ].run(1);\n          }\n\n          // resolve when we played the last frame\n          // otherwise, reject\n          if (gotoEnd) {\n            deferred.resolveWith(elem, [ animation, gotoEnd ]);\n          } else {\n            deferred.rejectWith(elem, [ animation, gotoEnd ]);\n          }\n          return this;\n        }\n      }),\n      props = animation.props;\n\n    propFilter(props, animation.opts.specialEasing);\n\n    for (; index < length; index++) {\n      result = animationPrefilters[ index ].call(animation, elem, props, animation.opts);\n      if (result) {\n        return result;\n      }\n    }\n\n    jQuery.map(props, createTween, animation);\n\n    if (jQuery.isFunction(animation.opts.start)) {\n      animation.opts.start.call(elem, animation);\n    }\n\n    jQuery.fx.timer(\n      jQuery.extend(tick, {\n        elem: elem,\n        anim: animation,\n        queue: animation.opts.queue\n      })\n    );\n\n    // attach callbacks from options\n    return animation.progress(animation.opts.progress)\n      .done(animation.opts.done, animation.opts.complete)\n      .fail(animation.opts.fail)\n      .always(animation.opts.always);\n  }\n\n  jQuery.Animation = jQuery.extend(Animation, {\n\n    tweener: function (props, callback) {\n      if (jQuery.isFunction(props)) {\n        callback = props;\n        props = [ \"*\" ];\n      } else {\n        props = props.split(\" \");\n      }\n\n      var prop,\n        index = 0,\n        length = props.length;\n\n      for (; index < length; index++) {\n        prop = props[ index ];\n        tweeners[ prop ] = tweeners[ prop ] || [];\n        tweeners[ prop ].unshift(callback);\n      }\n    },\n\n    prefilter: function (callback, prepend) {\n      if (prepend) {\n        animationPrefilters.unshift(callback);\n      } else {\n        animationPrefilters.push(callback);\n      }\n    }\n  });\n\n  jQuery.speed = function (speed, easing, fn) {\n    var opt = speed && typeof speed === \"object\" ? jQuery.extend({}, speed) : {\n      complete: fn || !fn && easing ||\n        jQuery.isFunction(speed) && speed,\n      duration: speed,\n      easing: fn && easing || easing && !jQuery.isFunction(easing) && easing\n    };\n\n    opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n        opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n    // normalize opt.queue - true/undefined/null -> \"fx\"\n    if (opt.queue == null || opt.queue === true) {\n      opt.queue = \"fx\";\n    }\n\n    // Queueing\n    opt.old = opt.complete;\n\n    opt.complete = function () {\n      if (jQuery.isFunction(opt.old)) {\n        opt.old.call(this);\n      }\n\n      if (opt.queue) {\n        jQuery.dequeue(this, opt.queue);\n      }\n    };\n\n    return opt;\n  };\n\n  jQuery.fn.extend({\n    fadeTo: function (speed, to, easing, callback) {\n\n      // show any hidden elements after setting opacity to 0\n      return this.filter(isHidden).css(\"opacity\", 0).show()\n\n        // animate to the value specified\n        .end().animate({ opacity: to }, speed, easing, callback);\n    },\n    animate: function (prop, speed, easing, callback) {\n      var empty = jQuery.isEmptyObject(prop),\n        optall = jQuery.speed(speed, easing, callback),\n        doAnimation = function () {\n          // Operate on a copy of prop so per-property easing won't be lost\n          var anim = Animation(this, jQuery.extend({}, prop), optall);\n\n          // Empty animations, or finishing resolves immediately\n          if (empty || data_priv.get(this, \"finish\")) {\n            anim.stop(true);\n          }\n        };\n      doAnimation.finish = doAnimation;\n\n      return empty || optall.queue === false ?\n        this.each(doAnimation) :\n        this.queue(optall.queue, doAnimation);\n    },\n    stop: function (type, clearQueue, gotoEnd) {\n      var stopQueue = function (hooks) {\n        var stop = hooks.stop;\n        delete hooks.stop;\n        stop(gotoEnd);\n      };\n\n      if (typeof type !== \"string\") {\n        gotoEnd = clearQueue;\n        clearQueue = type;\n        type = undefined;\n      }\n      if (clearQueue && type !== false) {\n        this.queue(type || \"fx\", []);\n      }\n\n      return this.each(function () {\n        var dequeue = true,\n          index = type != null && type + \"queueHooks\",\n          timers = jQuery.timers,\n          data = data_priv.get(this);\n\n        if (index) {\n          if (data[ index ] && data[ index ].stop) {\n            stopQueue(data[ index ]);\n          }\n        } else {\n          for (index in data) {\n            if (data[ index ] && data[ index ].stop && rrun.test(index)) {\n              stopQueue(data[ index ]);\n            }\n          }\n        }\n\n        for (index = timers.length; index--;) {\n          if (timers[ index ].elem === this && (type == null || timers[ index ].queue === type)) {\n            timers[ index ].anim.stop(gotoEnd);\n            dequeue = false;\n            timers.splice(index, 1);\n          }\n        }\n\n        // start the next in the queue if the last step wasn't forced\n        // timers currently will call their complete callbacks, which will dequeue\n        // but only if they were gotoEnd\n        if (dequeue || !gotoEnd) {\n          jQuery.dequeue(this, type);\n        }\n      });\n    },\n    finish: function (type) {\n      if (type !== false) {\n        type = type || \"fx\";\n      }\n      return this.each(function () {\n        var index,\n          data = data_priv.get(this),\n          queue = data[ type + \"queue\" ],\n          hooks = data[ type + \"queueHooks\" ],\n          timers = jQuery.timers,\n          length = queue ? queue.length : 0;\n\n        // enable finishing flag on private data\n        data.finish = true;\n\n        // empty the queue first\n        jQuery.queue(this, type, []);\n\n        if (hooks && hooks.stop) {\n          hooks.stop.call(this, true);\n        }\n\n        // look for any active animations, and finish them\n        for (index = timers.length; index--;) {\n          if (timers[ index ].elem === this && timers[ index ].queue === type) {\n            timers[ index ].anim.stop(true);\n            timers.splice(index, 1);\n          }\n        }\n\n        // look for any animations in the old queue and finish them\n        for (index = 0; index < length; index++) {\n          if (queue[ index ] && queue[ index ].finish) {\n            queue[ index ].finish.call(this);\n          }\n        }\n\n        // turn off finishing flag\n        delete data.finish;\n      });\n    }\n  });\n\n  jQuery.each([ \"toggle\", \"show\", \"hide\" ], function (i, name) {\n    var cssFn = jQuery.fn[ name ];\n    jQuery.fn[ name ] = function (speed, easing, callback) {\n      return speed == null || typeof speed === \"boolean\" ?\n        cssFn.apply(this, arguments) :\n        this.animate(genFx(name, true), speed, easing, callback);\n    };\n  });\n\n// Generate shortcuts for custom animations\n  jQuery.each({\n    slideDown: genFx(\"show\"),\n    slideUp: genFx(\"hide\"),\n    slideToggle: genFx(\"toggle\"),\n    fadeIn: { opacity: \"show\" },\n    fadeOut: { opacity: \"hide\" },\n    fadeToggle: { opacity: \"toggle\" }\n  }, function (name, props) {\n    jQuery.fn[ name ] = function (speed, easing, callback) {\n      return this.animate(props, speed, easing, callback);\n    };\n  });\n\n  jQuery.timers = [];\n  jQuery.fx.tick = function () {\n    var timer,\n      i = 0,\n      timers = jQuery.timers;\n\n    fxNow = jQuery.now();\n\n    for (; i < timers.length; i++) {\n      timer = timers[ i ];\n      // Checks the timer has not already been removed\n      if (!timer() && timers[ i ] === timer) {\n        timers.splice(i--, 1);\n      }\n    }\n\n    if (!timers.length) {\n      jQuery.fx.stop();\n    }\n    fxNow = undefined;\n  };\n\n  jQuery.fx.timer = function (timer) {\n    jQuery.timers.push(timer);\n    if (timer()) {\n      jQuery.fx.start();\n    } else {\n      jQuery.timers.pop();\n    }\n  };\n\n  jQuery.fx.interval = 13;\n\n  jQuery.fx.start = function () {\n    if (!timerId) {\n      timerId = setInterval(jQuery.fx.tick, jQuery.fx.interval);\n    }\n  };\n\n  jQuery.fx.stop = function () {\n    clearInterval(timerId);\n    timerId = null;\n  };\n\n  jQuery.fx.speeds = {\n    slow: 600,\n    fast: 200,\n    // Default speed\n    _default: 400\n  };\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// http://blindsignals.com/index.php/2009/07/jquery-delay/\n  jQuery.fn.delay = function (time, type) {\n    time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n    type = type || \"fx\";\n\n    return this.queue(type, function (next, hooks) {\n      var timeout = setTimeout(next, time);\n      hooks.stop = function () {\n        clearTimeout(timeout);\n      };\n    });\n  };\n\n\n  (function () {\n    var input = document.createElement(\"input\"),\n      select = document.createElement(\"select\"),\n      opt = select.appendChild(document.createElement(\"option\"));\n\n    input.type = \"checkbox\";\n\n    // Support: iOS 5.1, Android 4.x, Android 2.3\n    // Check the default checkbox/radio value (\"\" on old WebKit; \"on\" elsewhere)\n    support.checkOn = input.value !== \"\";\n\n    // Must access the parent to make an option select properly\n    // Support: IE9, IE10\n    support.optSelected = opt.selected;\n\n    // Make sure that the options inside disabled selects aren't marked as disabled\n    // (WebKit marks them as disabled)\n    select.disabled = true;\n    support.optDisabled = !opt.disabled;\n\n    // Check if an input maintains its value after becoming a radio\n    // Support: IE9, IE10\n    input = document.createElement(\"input\");\n    input.value = \"t\";\n    input.type = \"radio\";\n    support.radioValue = input.value === \"t\";\n  })();\n\n\n  var nodeHook, boolHook,\n    attrHandle = jQuery.expr.attrHandle;\n\n  jQuery.fn.extend({\n    attr: function (name, value) {\n      return access(this, jQuery.attr, name, value, arguments.length > 1);\n    },\n\n    removeAttr: function (name) {\n      return this.each(function () {\n        jQuery.removeAttr(this, name);\n      });\n    }\n  });\n\n  jQuery.extend({\n    attr: function (elem, name, value) {\n      var hooks, ret,\n        nType = elem.nodeType;\n\n      // don't get/set attributes on text, comment and attribute nodes\n      if (!elem || nType === 3 || nType === 8 || nType === 2) {\n        return;\n      }\n\n      // Fallback to prop when attributes are not supported\n      if (typeof elem.getAttribute === strundefined) {\n        return jQuery.prop(elem, name, value);\n      }\n\n      // All attributes are lowercase\n      // Grab necessary hook if one is defined\n      if (nType !== 1 || !jQuery.isXMLDoc(elem)) {\n        name = name.toLowerCase();\n        hooks = jQuery.attrHooks[ name ] ||\n          ( jQuery.expr.match.bool.test(name) ? boolHook : nodeHook );\n      }\n\n      if (value !== undefined) {\n\n        if (value === null) {\n          jQuery.removeAttr(elem, name);\n\n        } else if (hooks && \"set\" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {\n          return ret;\n\n        } else {\n          elem.setAttribute(name, value + \"\");\n          return value;\n        }\n\n      } else if (hooks && \"get\" in hooks && (ret = hooks.get(elem, name)) !== null) {\n        return ret;\n\n      } else {\n        ret = jQuery.find.attr(elem, name);\n\n        // Non-existent attributes return null, we normalize to undefined\n        return ret == null ?\n          undefined :\n          ret;\n      }\n    },\n\n    removeAttr: function (elem, value) {\n      var name, propName,\n        i = 0,\n        attrNames = value && value.match(rnotwhite);\n\n      if (attrNames && elem.nodeType === 1) {\n        while ((name = attrNames[i++])) {\n          propName = jQuery.propFix[ name ] || name;\n\n          // Boolean attributes get special treatment (#10870)\n          if (jQuery.expr.match.bool.test(name)) {\n            // Set corresponding property to false\n            elem[ propName ] = false;\n          }\n\n          elem.removeAttribute(name);\n        }\n      }\n    },\n\n    attrHooks: {\n      type: {\n        set: function (elem, value) {\n          if (!support.radioValue && value === \"radio\" &&\n            jQuery.nodeName(elem, \"input\")) {\n            // Setting the type on a radio button after the value resets the value in IE6-9\n            // Reset value to default in case type is set after value during creation\n            var val = elem.value;\n            elem.setAttribute(\"type\", value);\n            if (val) {\n              elem.value = val;\n            }\n            return value;\n          }\n        }\n      }\n    }\n  });\n\n// Hooks for boolean attributes\n  boolHook = {\n    set: function (elem, value, name) {\n      if (value === false) {\n        // Remove boolean attributes when set to false\n        jQuery.removeAttr(elem, name);\n      } else {\n        elem.setAttribute(name, name);\n      }\n      return name;\n    }\n  };\n  jQuery.each(jQuery.expr.match.bool.source.match(/\\w+/g), function (i, name) {\n    var getter = attrHandle[ name ] || jQuery.find.attr;\n\n    attrHandle[ name ] = function (elem, name, isXML) {\n      var ret, handle;\n      if (!isXML) {\n        // Avoid an infinite loop by temporarily removing this function from the getter\n        handle = attrHandle[ name ];\n        attrHandle[ name ] = ret;\n        ret = getter(elem, name, isXML) != null ?\n          name.toLowerCase() :\n          null;\n        attrHandle[ name ] = handle;\n      }\n      return ret;\n    };\n  });\n\n\n  var rfocusable = /^(?:input|select|textarea|button)$/i;\n\n  jQuery.fn.extend({\n    prop: function (name, value) {\n      return access(this, jQuery.prop, name, value, arguments.length > 1);\n    },\n\n    removeProp: function (name) {\n      return this.each(function () {\n        delete this[ jQuery.propFix[ name ] || name ];\n      });\n    }\n  });\n\n  jQuery.extend({\n    propFix: {\n      \"for\": \"htmlFor\",\n      \"class\": \"className\"\n    },\n\n    prop: function (elem, name, value) {\n      var ret, hooks, notxml,\n        nType = elem.nodeType;\n\n      // don't get/set properties on text, comment and attribute nodes\n      if (!elem || nType === 3 || nType === 8 || nType === 2) {\n        return;\n      }\n\n      notxml = nType !== 1 || !jQuery.isXMLDoc(elem);\n\n      if (notxml) {\n        // Fix name and attach hooks\n        name = jQuery.propFix[ name ] || name;\n        hooks = jQuery.propHooks[ name ];\n      }\n\n      if (value !== undefined) {\n        return hooks && \"set\" in hooks && (ret = hooks.set(elem, value, name)) !== undefined ?\n          ret :\n          ( elem[ name ] = value );\n\n      } else {\n        return hooks && \"get\" in hooks && (ret = hooks.get(elem, name)) !== null ?\n          ret :\n          elem[ name ];\n      }\n    },\n\n    propHooks: {\n      tabIndex: {\n        get: function (elem) {\n          return elem.hasAttribute(\"tabindex\") || rfocusable.test(elem.nodeName) || elem.href ?\n            elem.tabIndex :\n            -1;\n        }\n      }\n    }\n  });\n\n// Support: IE9+\n// Selectedness for an option in an optgroup can be inaccurate\n  if (!support.optSelected) {\n    jQuery.propHooks.selected = {\n      get: function (elem) {\n        var parent = elem.parentNode;\n        if (parent && parent.parentNode) {\n          parent.parentNode.selectedIndex;\n        }\n        return null;\n      }\n    };\n  }\n\n  jQuery.each([\n    \"tabIndex\",\n    \"readOnly\",\n    \"maxLength\",\n    \"cellSpacing\",\n    \"cellPadding\",\n    \"rowSpan\",\n    \"colSpan\",\n    \"useMap\",\n    \"frameBorder\",\n    \"contentEditable\"\n  ], function () {\n    jQuery.propFix[ this.toLowerCase() ] = this;\n  });\n\n\n  var rclass = /[\\t\\r\\n\\f]/g;\n\n  jQuery.fn.extend({\n    addClass: function (value) {\n      var classes, elem, cur, clazz, j, finalValue,\n        proceed = typeof value === \"string\" && value,\n        i = 0,\n        len = this.length;\n\n      if (jQuery.isFunction(value)) {\n        return this.each(function (j) {\n          jQuery(this).addClass(value.call(this, j, this.className));\n        });\n      }\n\n      if (proceed) {\n        // The disjunction here is for better compressibility (see removeClass)\n        classes = ( value || \"\" ).match(rnotwhite) || [];\n\n        for (; i < len; i++) {\n          elem = this[ i ];\n          cur = elem.nodeType === 1 && ( elem.className ?\n            ( \" \" + elem.className + \" \" ).replace(rclass, \" \") :\n            \" \"\n            );\n\n          if (cur) {\n            j = 0;\n            while ((clazz = classes[j++])) {\n              if (cur.indexOf(\" \" + clazz + \" \") < 0) {\n                cur += clazz + \" \";\n              }\n            }\n\n            // only assign if different to avoid unneeded rendering.\n            finalValue = jQuery.trim(cur);\n            if (elem.className !== finalValue) {\n              elem.className = finalValue;\n            }\n          }\n        }\n      }\n\n      return this;\n    },\n\n    removeClass: function (value) {\n      var classes, elem, cur, clazz, j, finalValue,\n        proceed = arguments.length === 0 || typeof value === \"string\" && value,\n        i = 0,\n        len = this.length;\n\n      if (jQuery.isFunction(value)) {\n        return this.each(function (j) {\n          jQuery(this).removeClass(value.call(this, j, this.className));\n        });\n      }\n      if (proceed) {\n        classes = ( value || \"\" ).match(rnotwhite) || [];\n\n        for (; i < len; i++) {\n          elem = this[ i ];\n          // This expression is here for better compressibility (see addClass)\n          cur = elem.nodeType === 1 && ( elem.className ?\n            ( \" \" + elem.className + \" \" ).replace(rclass, \" \") :\n            \"\"\n            );\n\n          if (cur) {\n            j = 0;\n            while ((clazz = classes[j++])) {\n              // Remove *all* instances\n              while (cur.indexOf(\" \" + clazz + \" \") >= 0) {\n                cur = cur.replace(\" \" + clazz + \" \", \" \");\n              }\n            }\n\n            // only assign if different to avoid unneeded rendering.\n            finalValue = value ? jQuery.trim(cur) : \"\";\n            if (elem.className !== finalValue) {\n              elem.className = finalValue;\n            }\n          }\n        }\n      }\n\n      return this;\n    },\n\n    toggleClass: function (value, stateVal) {\n      var type = typeof value;\n\n      if (typeof stateVal === \"boolean\" && type === \"string\") {\n        return stateVal ? this.addClass(value) : this.removeClass(value);\n      }\n\n      if (jQuery.isFunction(value)) {\n        return this.each(function (i) {\n          jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal);\n        });\n      }\n\n      return this.each(function () {\n        if (type === \"string\") {\n          // toggle individual class names\n          var className,\n            i = 0,\n            self = jQuery(this),\n            classNames = value.match(rnotwhite) || [];\n\n          while ((className = classNames[ i++ ])) {\n            // check each className given, space separated list\n            if (self.hasClass(className)) {\n              self.removeClass(className);\n            } else {\n              self.addClass(className);\n            }\n          }\n\n          // Toggle whole class name\n        } else if (type === strundefined || type === \"boolean\") {\n          if (this.className) {\n            // store className if set\n            data_priv.set(this, \"__className__\", this.className);\n          }\n\n          // If the element has a class name or if we're passed \"false\",\n          // then remove the whole classname (if there was one, the above saved it).\n          // Otherwise bring back whatever was previously saved (if anything),\n          // falling back to the empty string if nothing was stored.\n          this.className = this.className || value === false ? \"\" : data_priv.get(this, \"__className__\") || \"\";\n        }\n      });\n    },\n\n    hasClass: function (selector) {\n      var className = \" \" + selector + \" \",\n        i = 0,\n        l = this.length;\n      for (; i < l; i++) {\n        if (this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf(className) >= 0) {\n          return true;\n        }\n      }\n\n      return false;\n    }\n  });\n\n\n  var rreturn = /\\r/g;\n\n  jQuery.fn.extend({\n    val: function (value) {\n      var hooks, ret, isFunction,\n        elem = this[0];\n\n      if (!arguments.length) {\n        if (elem) {\n          hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n          if (hooks && \"get\" in hooks && (ret = hooks.get(elem, \"value\")) !== undefined) {\n            return ret;\n          }\n\n          ret = elem.value;\n\n          return typeof ret === \"string\" ?\n            // handle most common string cases\n            ret.replace(rreturn, \"\") :\n            // handle cases where value is null/undef or number\n              ret == null ? \"\" : ret;\n        }\n\n        return;\n      }\n\n      isFunction = jQuery.isFunction(value);\n\n      return this.each(function (i) {\n        var val;\n\n        if (this.nodeType !== 1) {\n          return;\n        }\n\n        if (isFunction) {\n          val = value.call(this, i, jQuery(this).val());\n        } else {\n          val = value;\n        }\n\n        // Treat null/undefined as \"\"; convert numbers to string\n        if (val == null) {\n          val = \"\";\n\n        } else if (typeof val === \"number\") {\n          val += \"\";\n\n        } else if (jQuery.isArray(val)) {\n          val = jQuery.map(val, function (value) {\n            return value == null ? \"\" : value + \"\";\n          });\n        }\n\n        hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n        // If set returns undefined, fall back to normal setting\n        if (!hooks || !(\"set\" in hooks) || hooks.set(this, val, \"value\") === undefined) {\n          this.value = val;\n        }\n      });\n    }\n  });\n\n  jQuery.extend({\n    valHooks: {\n      select: {\n        get: function (elem) {\n          var value, option,\n            options = elem.options,\n            index = elem.selectedIndex,\n            one = elem.type === \"select-one\" || index < 0,\n            values = one ? null : [],\n            max = one ? index + 1 : options.length,\n            i = index < 0 ?\n              max :\n              one ? index : 0;\n\n          // Loop through all the selected options\n          for (; i < max; i++) {\n            option = options[ i ];\n\n            // IE6-9 doesn't update selected after form reset (#2551)\n            if (( option.selected || i === index ) &&\n              // Don't return options that are disabled or in a disabled optgroup\n              ( support.optDisabled ? !option.disabled : option.getAttribute(\"disabled\") === null ) &&\n              ( !option.parentNode.disabled || !jQuery.nodeName(option.parentNode, \"optgroup\") )) {\n\n              // Get the specific value for the option\n              value = jQuery(option).val();\n\n              // We don't need an array for one selects\n              if (one) {\n                return value;\n              }\n\n              // Multi-Selects return an array\n              values.push(value);\n            }\n          }\n\n          return values;\n        },\n\n        set: function (elem, value) {\n          var optionSet, option,\n            options = elem.options,\n            values = jQuery.makeArray(value),\n            i = options.length;\n\n          while (i--) {\n            option = options[ i ];\n            if ((option.selected = jQuery.inArray(jQuery(option).val(), values) >= 0)) {\n              optionSet = true;\n            }\n          }\n\n          // force browsers to behave consistently when non-matching value is set\n          if (!optionSet) {\n            elem.selectedIndex = -1;\n          }\n          return values;\n        }\n      }\n    }\n  });\n\n// Radios and checkboxes getter/setter\n  jQuery.each([ \"radio\", \"checkbox\" ], function () {\n    jQuery.valHooks[ this ] = {\n      set: function (elem, value) {\n        if (jQuery.isArray(value)) {\n          return ( elem.checked = jQuery.inArray(jQuery(elem).val(), value) >= 0 );\n        }\n      }\n    };\n    if (!support.checkOn) {\n      jQuery.valHooks[ this ].get = function (elem) {\n        // Support: Webkit\n        // \"\" is returned instead of \"on\" if a value isn't specified\n        return elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n      };\n    }\n  });\n\n\n// Return jQuery for attributes-only inclusion\n\n\n  jQuery.each((\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n    \"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n    \"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function (i, name) {\n\n    // Handle event binding\n    jQuery.fn[ name ] = function (data, fn) {\n      return arguments.length > 0 ?\n        this.on(name, null, data, fn) :\n        this.trigger(name);\n    };\n  });\n\n  jQuery.fn.extend({\n    hover: function (fnOver, fnOut) {\n      return this.mouseenter(fnOver).mouseleave(fnOut || fnOver);\n    },\n\n    bind: function (types, data, fn) {\n      return this.on(types, null, data, fn);\n    },\n    unbind: function (types, fn) {\n      return this.off(types, null, fn);\n    },\n\n    delegate: function (selector, types, data, fn) {\n      return this.on(types, selector, data, fn);\n    },\n    undelegate: function (selector, types, fn) {\n      // ( namespace ) or ( selector, types [, fn] )\n      return arguments.length === 1 ? this.off(selector, \"**\") : this.off(types, selector || \"**\", fn);\n    }\n  });\n\n\n  var nonce = jQuery.now();\n\n  var rquery = (/\\?/);\n\n\n// Support: Android 2.3\n// Workaround failure to string-cast null input\n  jQuery.parseJSON = function (data) {\n    return JSON.parse(data + \"\");\n  };\n\n\n// Cross-browser xml parsing\n  jQuery.parseXML = function (data) {\n    var xml, tmp;\n    if (!data || typeof data !== \"string\") {\n      return null;\n    }\n\n    // Support: IE9\n    try {\n      tmp = new DOMParser();\n      xml = tmp.parseFromString(data, \"text/xml\");\n    } catch (e) {\n      xml = undefined;\n    }\n\n    if (!xml || xml.getElementsByTagName(\"parsererror\").length) {\n      jQuery.error(\"Invalid XML: \" + data);\n    }\n    return xml;\n  };\n\n\n  var\n  // Document location\n    ajaxLocParts,\n    ajaxLocation,\n\n    rhash = /#.*$/,\n    rts = /([?&])_=[^&]*/,\n    rheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n  // #7653, #8125, #8152: local protocol detection\n    rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n    rnoContent = /^(?:GET|HEAD)$/,\n    rprotocol = /^\\/\\//,\n    rurl = /^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n  /* Prefilters\n   * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n   * 2) These are called:\n   *    - BEFORE asking for a transport\n   *    - AFTER param serialization (s.data is a string if s.processData is true)\n   * 3) key is the dataType\n   * 4) the catchall symbol \"*\" can be used\n   * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n   */\n    prefilters = {},\n\n  /* Transports bindings\n   * 1) key is the dataType\n   * 2) the catchall symbol \"*\" can be used\n   * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n   */\n    transports = {},\n\n  // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n    allTypes = \"*/\".concat(\"*\");\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\n  try {\n    ajaxLocation = location.href;\n  } catch (e) {\n    // Use the href attribute of an A element\n    // since IE will modify it given document.location\n    ajaxLocation = document.createElement(\"a\");\n    ajaxLocation.href = \"\";\n    ajaxLocation = ajaxLocation.href;\n  }\n\n// Segment location into parts\n  ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\n  function addToPrefiltersOrTransports(structure) {\n\n    // dataTypeExpression is optional and defaults to \"*\"\n    return function (dataTypeExpression, func) {\n\n      if (typeof dataTypeExpression !== \"string\") {\n        func = dataTypeExpression;\n        dataTypeExpression = \"*\";\n      }\n\n      var dataType,\n        i = 0,\n        dataTypes = dataTypeExpression.toLowerCase().match(rnotwhite) || [];\n\n      if (jQuery.isFunction(func)) {\n        // For each dataType in the dataTypeExpression\n        while ((dataType = dataTypes[i++])) {\n          // Prepend if requested\n          if (dataType[0] === \"+\") {\n            dataType = dataType.slice(1) || \"*\";\n            (structure[ dataType ] = structure[ dataType ] || []).unshift(func);\n\n            // Otherwise append\n          } else {\n            (structure[ dataType ] = structure[ dataType ] || []).push(func);\n          }\n        }\n      }\n    };\n  }\n\n// Base inspection function for prefilters and transports\n  function inspectPrefiltersOrTransports(structure, options, originalOptions, jqXHR) {\n\n    var inspected = {},\n      seekingTransport = ( structure === transports );\n\n    function inspect(dataType) {\n      var selected;\n      inspected[ dataType ] = true;\n      jQuery.each(structure[ dataType ] || [], function (_, prefilterOrFactory) {\n        var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR);\n        if (typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ]) {\n          options.dataTypes.unshift(dataTypeOrTransport);\n          inspect(dataTypeOrTransport);\n          return false;\n        } else if (seekingTransport) {\n          return !( selected = dataTypeOrTransport );\n        }\n      });\n      return selected;\n    }\n\n    return inspect(options.dataTypes[ 0 ]) || !inspected[ \"*\" ] && inspect(\"*\");\n  }\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\n  function ajaxExtend(target, src) {\n    var key, deep,\n      flatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n    for (key in src) {\n      if (src[ key ] !== undefined) {\n        ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n      }\n    }\n    if (deep) {\n      jQuery.extend(true, target, deep);\n    }\n\n    return target;\n  }\n\n  /* Handles responses to an ajax request:\n   * - finds the right dataType (mediates between content-type and expected dataType)\n   * - returns the corresponding response\n   */\n  function ajaxHandleResponses(s, jqXHR, responses) {\n\n    var ct, type, finalDataType, firstDataType,\n      contents = s.contents,\n      dataTypes = s.dataTypes;\n\n    // Remove auto dataType and get content-type in the process\n    while (dataTypes[ 0 ] === \"*\") {\n      dataTypes.shift();\n      if (ct === undefined) {\n        ct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n      }\n    }\n\n    // Check if we're dealing with a known content-type\n    if (ct) {\n      for (type in contents) {\n        if (contents[ type ] && contents[ type ].test(ct)) {\n          dataTypes.unshift(type);\n          break;\n        }\n      }\n    }\n\n    // Check to see if we have a response for the expected dataType\n    if (dataTypes[ 0 ] in responses) {\n      finalDataType = dataTypes[ 0 ];\n    } else {\n      // Try convertible dataTypes\n      for (type in responses) {\n        if (!dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ]) {\n          finalDataType = type;\n          break;\n        }\n        if (!firstDataType) {\n          firstDataType = type;\n        }\n      }\n      // Or just use first one\n      finalDataType = finalDataType || firstDataType;\n    }\n\n    // If we found a dataType\n    // We add the dataType to the list if needed\n    // and return the corresponding response\n    if (finalDataType) {\n      if (finalDataType !== dataTypes[ 0 ]) {\n        dataTypes.unshift(finalDataType);\n      }\n      return responses[ finalDataType ];\n    }\n  }\n\n  /* Chain conversions given the request and the original response\n   * Also sets the responseXXX fields on the jqXHR instance\n   */\n  function ajaxConvert(s, response, jqXHR, isSuccess) {\n    var conv2, current, conv, tmp, prev,\n      converters = {},\n    // Work with a copy of dataTypes in case we need to modify it for conversion\n      dataTypes = s.dataTypes.slice();\n\n    // Create converters map with lowercased keys\n    if (dataTypes[ 1 ]) {\n      for (conv in s.converters) {\n        converters[ conv.toLowerCase() ] = s.converters[ conv ];\n      }\n    }\n\n    current = dataTypes.shift();\n\n    // Convert to each sequential dataType\n    while (current) {\n\n      if (s.responseFields[ current ]) {\n        jqXHR[ s.responseFields[ current ] ] = response;\n      }\n\n      // Apply the dataFilter if provided\n      if (!prev && isSuccess && s.dataFilter) {\n        response = s.dataFilter(response, s.dataType);\n      }\n\n      prev = current;\n      current = dataTypes.shift();\n\n      if (current) {\n\n        // There's only work to do if current dataType is non-auto\n        if (current === \"*\") {\n\n          current = prev;\n\n          // Convert response if prev dataType is non-auto and differs from current\n        } else if (prev !== \"*\" && prev !== current) {\n\n          // Seek a direct converter\n          conv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n          // If none found, seek a pair\n          if (!conv) {\n            for (conv2 in converters) {\n\n              // If conv2 outputs current\n              tmp = conv2.split(\" \");\n              if (tmp[ 1 ] === current) {\n\n                // If prev can be converted to accepted input\n                conv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n                  converters[ \"* \" + tmp[ 0 ] ];\n                if (conv) {\n                  // Condense equivalence converters\n                  if (conv === true) {\n                    conv = converters[ conv2 ];\n\n                    // Otherwise, insert the intermediate dataType\n                  } else if (converters[ conv2 ] !== true) {\n                    current = tmp[ 0 ];\n                    dataTypes.unshift(tmp[ 1 ]);\n                  }\n                  break;\n                }\n              }\n            }\n          }\n\n          // Apply converter (if not an equivalence)\n          if (conv !== true) {\n\n            // Unless errors are allowed to bubble, catch and return them\n            if (conv && s[ \"throws\" ]) {\n              response = conv(response);\n            } else {\n              try {\n                response = conv(response);\n              } catch (e) {\n                return { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n              }\n            }\n          }\n        }\n      }\n    }\n\n    return { state: \"success\", data: response };\n  }\n\n  jQuery.extend({\n\n    // Counter for holding the number of active queries\n    active: 0,\n\n    // Last-Modified header cache for next request\n    lastModified: {},\n    etag: {},\n\n    ajaxSettings: {\n      url: ajaxLocation,\n      type: \"GET\",\n      isLocal: rlocalProtocol.test(ajaxLocParts[ 1 ]),\n      global: true,\n      processData: true,\n      async: true,\n      contentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n      /*\n       timeout: 0,\n       data: null,\n       dataType: null,\n       username: null,\n       password: null,\n       cache: null,\n       throws: false,\n       traditional: false,\n       headers: {},\n       */\n\n      accepts: {\n        \"*\": allTypes,\n        text: \"text/plain\",\n        html: \"text/html\",\n        xml: \"application/xml, text/xml\",\n        json: \"application/json, text/javascript\"\n      },\n\n      contents: {\n        xml: /xml/,\n        html: /html/,\n        json: /json/\n      },\n\n      responseFields: {\n        xml: \"responseXML\",\n        text: \"responseText\",\n        json: \"responseJSON\"\n      },\n\n      // Data converters\n      // Keys separate source (or catchall \"*\") and destination types with a single space\n      converters: {\n\n        // Convert anything to text\n        \"* text\": String,\n\n        // Text to html (true = no transformation)\n        \"text html\": true,\n\n        // Evaluate text as a json expression\n        \"text json\": jQuery.parseJSON,\n\n        // Parse text as xml\n        \"text xml\": jQuery.parseXML\n      },\n\n      // For options that shouldn't be deep extended:\n      // you can add your own custom options here if\n      // and when you create one that shouldn't be\n      // deep extended (see ajaxExtend)\n      flatOptions: {\n        url: true,\n        context: true\n      }\n    },\n\n    // Creates a full fledged settings object into target\n    // with both ajaxSettings and settings fields.\n    // If target is omitted, writes into ajaxSettings.\n    ajaxSetup: function (target, settings) {\n      return settings ?\n\n        // Building a settings object\n        ajaxExtend(ajaxExtend(target, jQuery.ajaxSettings), settings) :\n\n        // Extending ajaxSettings\n        ajaxExtend(jQuery.ajaxSettings, target);\n    },\n\n    ajaxPrefilter: addToPrefiltersOrTransports(prefilters),\n    ajaxTransport: addToPrefiltersOrTransports(transports),\n\n    // Main method\n    ajax: function (url, options) {\n\n      // If url is an object, simulate pre-1.5 signature\n      if (typeof url === \"object\") {\n        options = url;\n        url = undefined;\n      }\n\n      // Force options to be an object\n      options = options || {};\n\n      var transport,\n      // URL without anti-cache param\n        cacheURL,\n      // Response headers\n        responseHeadersString,\n        responseHeaders,\n      // timeout handle\n        timeoutTimer,\n      // Cross-domain detection vars\n        parts,\n      // To know if global events are to be dispatched\n        fireGlobals,\n      // Loop variable\n        i,\n      // Create the final options object\n        s = jQuery.ajaxSetup({}, options),\n      // Callbacks context\n        callbackContext = s.context || s,\n      // Context for global events is callbackContext if it is a DOM node or jQuery collection\n        globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n          jQuery(callbackContext) :\n          jQuery.event,\n      // Deferreds\n        deferred = jQuery.Deferred(),\n        completeDeferred = jQuery.Callbacks(\"once memory\"),\n      // Status-dependent callbacks\n        statusCode = s.statusCode || {},\n      // Headers (they are sent all at once)\n        requestHeaders = {},\n        requestHeadersNames = {},\n      // The jqXHR state\n        state = 0,\n      // Default abort message\n        strAbort = \"canceled\",\n      // Fake xhr\n        jqXHR = {\n          readyState: 0,\n\n          // Builds headers hashtable if needed\n          getResponseHeader: function (key) {\n            var match;\n            if (state === 2) {\n              if (!responseHeaders) {\n                responseHeaders = {};\n                while ((match = rheaders.exec(responseHeadersString))) {\n                  responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n                }\n              }\n              match = responseHeaders[ key.toLowerCase() ];\n            }\n            return match == null ? null : match;\n          },\n\n          // Raw string\n          getAllResponseHeaders: function () {\n            return state === 2 ? responseHeadersString : null;\n          },\n\n          // Caches the header\n          setRequestHeader: function (name, value) {\n            var lname = name.toLowerCase();\n            if (!state) {\n              name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n              requestHeaders[ name ] = value;\n            }\n            return this;\n          },\n\n          // Overrides response content-type header\n          overrideMimeType: function (type) {\n            if (!state) {\n              s.mimeType = type;\n            }\n            return this;\n          },\n\n          // Status-dependent callbacks\n          statusCode: function (map) {\n            var code;\n            if (map) {\n              if (state < 2) {\n                for (code in map) {\n                  // Lazy-add the new callback in a way that preserves old ones\n                  statusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n                }\n              } else {\n                // Execute the appropriate callbacks\n                jqXHR.always(map[ jqXHR.status ]);\n              }\n            }\n            return this;\n          },\n\n          // Cancel the request\n          abort: function (statusText) {\n            var finalText = statusText || strAbort;\n            if (transport) {\n              transport.abort(finalText);\n            }\n            done(0, finalText);\n            return this;\n          }\n        };\n\n      // Attach deferreds\n      deferred.promise(jqXHR).complete = completeDeferred.add;\n      jqXHR.success = jqXHR.done;\n      jqXHR.error = jqXHR.fail;\n\n      // Remove hash character (#7531: and string promotion)\n      // Add protocol if not provided (prefilters might expect it)\n      // Handle falsy url in the settings object (#10093: consistency with old signature)\n      // We also use the url parameter if available\n      s.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace(rhash, \"\")\n        .replace(rprotocol, ajaxLocParts[ 1 ] + \"//\");\n\n      // Alias method option to type as per ticket #12004\n      s.type = options.method || options.type || s.method || s.type;\n\n      // Extract dataTypes list\n      s.dataTypes = jQuery.trim(s.dataType || \"*\").toLowerCase().match(rnotwhite) || [ \"\" ];\n\n      // A cross-domain request is in order when we have a protocol:host:port mismatch\n      if (s.crossDomain == null) {\n        parts = rurl.exec(s.url.toLowerCase());\n        s.crossDomain = !!( parts &&\n          ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n            ( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n            ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n          );\n      }\n\n      // Convert data if not already a string\n      if (s.data && s.processData && typeof s.data !== \"string\") {\n        s.data = jQuery.param(s.data, s.traditional);\n      }\n\n      // Apply prefilters\n      inspectPrefiltersOrTransports(prefilters, s, options, jqXHR);\n\n      // If request was aborted inside a prefilter, stop there\n      if (state === 2) {\n        return jqXHR;\n      }\n\n      // We can fire global events as of now if asked to\n      fireGlobals = s.global;\n\n      // Watch for a new set of requests\n      if (fireGlobals && jQuery.active++ === 0) {\n        jQuery.event.trigger(\"ajaxStart\");\n      }\n\n      // Uppercase the type\n      s.type = s.type.toUpperCase();\n\n      // Determine if request has content\n      s.hasContent = !rnoContent.test(s.type);\n\n      // Save the URL in case we're toying with the If-Modified-Since\n      // and/or If-None-Match header later on\n      cacheURL = s.url;\n\n      // More options handling for requests with no content\n      if (!s.hasContent) {\n\n        // If data is available, append data to url\n        if (s.data) {\n          cacheURL = ( s.url += ( rquery.test(cacheURL) ? \"&\" : \"?\" ) + s.data );\n          // #9682: remove data so that it's not used in an eventual retry\n          delete s.data;\n        }\n\n        // Add anti-cache in url if needed\n        if (s.cache === false) {\n          s.url = rts.test(cacheURL) ?\n\n            // If there is already a '_' parameter, set its value\n            cacheURL.replace(rts, \"$1_=\" + nonce++) :\n\n            // Otherwise add one to the end\n            cacheURL + ( rquery.test(cacheURL) ? \"&\" : \"?\" ) + \"_=\" + nonce++;\n        }\n      }\n\n      // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n      if (s.ifModified) {\n        if (jQuery.lastModified[ cacheURL ]) {\n          jqXHR.setRequestHeader(\"If-Modified-Since\", jQuery.lastModified[ cacheURL ]);\n        }\n        if (jQuery.etag[ cacheURL ]) {\n          jqXHR.setRequestHeader(\"If-None-Match\", jQuery.etag[ cacheURL ]);\n        }\n      }\n\n      // Set the correct header, if data is being sent\n      if (s.data && s.hasContent && s.contentType !== false || options.contentType) {\n        jqXHR.setRequestHeader(\"Content-Type\", s.contentType);\n      }\n\n      // Set the Accepts header for the server, depending on the dataType\n      jqXHR.setRequestHeader(\n        \"Accept\",\n          s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n          s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n          s.accepts[ \"*\" ]\n      );\n\n      // Check for headers option\n      for (i in s.headers) {\n        jqXHR.setRequestHeader(i, s.headers[ i ]);\n      }\n\n      // Allow custom headers/mimetypes and early abort\n      if (s.beforeSend && ( s.beforeSend.call(callbackContext, jqXHR, s) === false || state === 2 )) {\n        // Abort if not done already and return\n        return jqXHR.abort();\n      }\n\n      // aborting is no longer a cancellation\n      strAbort = \"abort\";\n\n      // Install callbacks on deferreds\n      for (i in { success: 1, error: 1, complete: 1 }) {\n        jqXHR[ i ](s[ i ]);\n      }\n\n      // Get transport\n      transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR);\n\n      // If no transport, we auto-abort\n      if (!transport) {\n        done(-1, \"No Transport\");\n      } else {\n        jqXHR.readyState = 1;\n\n        // Send global event\n        if (fireGlobals) {\n          globalEventContext.trigger(\"ajaxSend\", [ jqXHR, s ]);\n        }\n        // Timeout\n        if (s.async && s.timeout > 0) {\n          timeoutTimer = setTimeout(function () {\n            jqXHR.abort(\"timeout\");\n          }, s.timeout);\n        }\n\n        try {\n          state = 1;\n          transport.send(requestHeaders, done);\n        } catch (e) {\n          // Propagate exception as error if not done\n          if (state < 2) {\n            done(-1, e);\n            // Simply rethrow otherwise\n          } else {\n            throw e;\n          }\n        }\n      }\n\n      // Callback for when everything is done\n      function done(status, nativeStatusText, responses, headers) {\n        var isSuccess, success, error, response, modified,\n          statusText = nativeStatusText;\n\n        // Called once\n        if (state === 2) {\n          return;\n        }\n\n        // State is \"done\" now\n        state = 2;\n\n        // Clear timeout if it exists\n        if (timeoutTimer) {\n          clearTimeout(timeoutTimer);\n        }\n\n        // Dereference transport for early garbage collection\n        // (no matter how long the jqXHR object will be used)\n        transport = undefined;\n\n        // Cache response headers\n        responseHeadersString = headers || \"\";\n\n        // Set readyState\n        jqXHR.readyState = status > 0 ? 4 : 0;\n\n        // Determine if successful\n        isSuccess = status >= 200 && status < 300 || status === 304;\n\n        // Get response data\n        if (responses) {\n          response = ajaxHandleResponses(s, jqXHR, responses);\n        }\n\n        // Convert no matter what (that way responseXXX fields are always set)\n        response = ajaxConvert(s, response, jqXHR, isSuccess);\n\n        // If successful, handle type chaining\n        if (isSuccess) {\n\n          // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n          if (s.ifModified) {\n            modified = jqXHR.getResponseHeader(\"Last-Modified\");\n            if (modified) {\n              jQuery.lastModified[ cacheURL ] = modified;\n            }\n            modified = jqXHR.getResponseHeader(\"etag\");\n            if (modified) {\n              jQuery.etag[ cacheURL ] = modified;\n            }\n          }\n\n          // if no content\n          if (status === 204 || s.type === \"HEAD\") {\n            statusText = \"nocontent\";\n\n            // if not modified\n          } else if (status === 304) {\n            statusText = \"notmodified\";\n\n            // If we have data, let's convert it\n          } else {\n            statusText = response.state;\n            success = response.data;\n            error = response.error;\n            isSuccess = !error;\n          }\n        } else {\n          // We extract error from statusText\n          // then normalize statusText and status for non-aborts\n          error = statusText;\n          if (status || !statusText) {\n            statusText = \"error\";\n            if (status < 0) {\n              status = 0;\n            }\n          }\n        }\n\n        // Set data for the fake xhr object\n        jqXHR.status = status;\n        jqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n        // Success/Error\n        if (isSuccess) {\n          deferred.resolveWith(callbackContext, [ success, statusText, jqXHR ]);\n        } else {\n          deferred.rejectWith(callbackContext, [ jqXHR, statusText, error ]);\n        }\n\n        // Status-dependent callbacks\n        jqXHR.statusCode(statusCode);\n        statusCode = undefined;\n\n        if (fireGlobals) {\n          globalEventContext.trigger(isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n            [ jqXHR, s, isSuccess ? success : error ]);\n        }\n\n        // Complete\n        completeDeferred.fireWith(callbackContext, [ jqXHR, statusText ]);\n\n        if (fireGlobals) {\n          globalEventContext.trigger(\"ajaxComplete\", [ jqXHR, s ]);\n          // Handle the global AJAX counter\n          if (!( --jQuery.active )) {\n            jQuery.event.trigger(\"ajaxStop\");\n          }\n        }\n      }\n\n      return jqXHR;\n    },\n\n    getJSON: function (url, data, callback) {\n      return jQuery.get(url, data, callback, \"json\");\n    },\n\n    getScript: function (url, callback) {\n      return jQuery.get(url, undefined, callback, \"script\");\n    }\n  });\n\n  jQuery.each([ \"get\", \"post\" ], function (i, method) {\n    jQuery[ method ] = function (url, data, callback, type) {\n      // shift arguments if data argument was omitted\n      if (jQuery.isFunction(data)) {\n        type = type || callback;\n        callback = data;\n        data = undefined;\n      }\n\n      return jQuery.ajax({\n        url: url,\n        type: method,\n        dataType: type,\n        data: data,\n        success: callback\n      });\n    };\n  });\n\n// Attach a bunch of functions for handling common AJAX events\n  jQuery.each([ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function (i, type) {\n    jQuery.fn[ type ] = function (fn) {\n      return this.on(type, fn);\n    };\n  });\n\n\n  jQuery._evalUrl = function (url) {\n    return jQuery.ajax({\n      url: url,\n      type: \"GET\",\n      dataType: \"script\",\n      async: false,\n      global: false,\n      \"throws\": true\n    });\n  };\n\n\n  jQuery.fn.extend({\n    wrapAll: function (html) {\n      var wrap;\n\n      if (jQuery.isFunction(html)) {\n        return this.each(function (i) {\n          jQuery(this).wrapAll(html.call(this, i));\n        });\n      }\n\n      if (this[ 0 ]) {\n\n        // The elements to wrap the target around\n        wrap = jQuery(html, this[ 0 ].ownerDocument).eq(0).clone(true);\n\n        if (this[ 0 ].parentNode) {\n          wrap.insertBefore(this[ 0 ]);\n        }\n\n        wrap.map(function () {\n          var elem = this;\n\n          while (elem.firstElementChild) {\n            elem = elem.firstElementChild;\n          }\n\n          return elem;\n        }).append(this);\n      }\n\n      return this;\n    },\n\n    wrapInner: function (html) {\n      if (jQuery.isFunction(html)) {\n        return this.each(function (i) {\n          jQuery(this).wrapInner(html.call(this, i));\n        });\n      }\n\n      return this.each(function () {\n        var self = jQuery(this),\n          contents = self.contents();\n\n        if (contents.length) {\n          contents.wrapAll(html);\n\n        } else {\n          self.append(html);\n        }\n      });\n    },\n\n    wrap: function (html) {\n      var isFunction = jQuery.isFunction(html);\n\n      return this.each(function (i) {\n        jQuery(this).wrapAll(isFunction ? html.call(this, i) : html);\n      });\n    },\n\n    unwrap: function () {\n      return this.parent().each(function () {\n        if (!jQuery.nodeName(this, \"body\")) {\n          jQuery(this).replaceWith(this.childNodes);\n        }\n      }).end();\n    }\n  });\n\n\n  jQuery.expr.filters.hidden = function (elem) {\n    // Support: Opera <= 12.12\n    // Opera reports offsetWidths and offsetHeights less than zero on some elements\n    return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;\n  };\n  jQuery.expr.filters.visible = function (elem) {\n    return !jQuery.expr.filters.hidden(elem);\n  };\n\n\n  var r20 = /%20/g,\n    rbracket = /\\[\\]$/,\n    rCRLF = /\\r?\\n/g,\n    rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n    rsubmittable = /^(?:input|select|textarea|keygen)/i;\n\n  function buildParams(prefix, obj, traditional, add) {\n    var name;\n\n    if (jQuery.isArray(obj)) {\n      // Serialize array item.\n      jQuery.each(obj, function (i, v) {\n        if (traditional || rbracket.test(prefix)) {\n          // Treat each array item as a scalar.\n          add(prefix, v);\n\n        } else {\n          // Item is non-scalar (array or object), encode its numeric index.\n          buildParams(prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add);\n        }\n      });\n\n    } else if (!traditional && jQuery.type(obj) === \"object\") {\n      // Serialize object item.\n      for (name in obj) {\n        buildParams(prefix + \"[\" + name + \"]\", obj[ name ], traditional, add);\n      }\n\n    } else {\n      // Serialize scalar item.\n      add(prefix, obj);\n    }\n  }\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\n  jQuery.param = function (a, traditional) {\n    var prefix,\n      s = [],\n      add = function (key, value) {\n        // If value is a function, invoke it and return its value\n        value = jQuery.isFunction(value) ? value() : ( value == null ? \"\" : value );\n        s[ s.length ] = encodeURIComponent(key) + \"=\" + encodeURIComponent(value);\n      };\n\n    // Set traditional to true for jQuery <= 1.3.2 behavior.\n    if (traditional === undefined) {\n      traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n    }\n\n    // If an array was passed in, assume that it is an array of form elements.\n    if (jQuery.isArray(a) || ( a.jquery && !jQuery.isPlainObject(a) )) {\n      // Serialize the form elements\n      jQuery.each(a, function () {\n        add(this.name, this.value);\n      });\n\n    } else {\n      // If traditional, encode the \"old\" way (the way 1.3.2 or older\n      // did it), otherwise encode params recursively.\n      for (prefix in a) {\n        buildParams(prefix, a[ prefix ], traditional, add);\n      }\n    }\n\n    // Return the resulting serialization\n    return s.join(\"&\").replace(r20, \"+\");\n  };\n\n  jQuery.fn.extend({\n    serialize: function () {\n      return jQuery.param(this.serializeArray());\n    },\n    serializeArray: function () {\n      return this.map(function () {\n        // Can add propHook for \"elements\" to filter or add form elements\n        var elements = jQuery.prop(this, \"elements\");\n        return elements ? jQuery.makeArray(elements) : this;\n      })\n        .filter(function () {\n          var type = this.type;\n\n          // Use .is( \":disabled\" ) so that fieldset[disabled] works\n          return this.name && !jQuery(this).is(\":disabled\") &&\n            rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) &&\n            ( this.checked || !rcheckableType.test(type) );\n        })\n        .map(function (i, elem) {\n          var val = jQuery(this).val();\n\n          return val == null ?\n            null :\n            jQuery.isArray(val) ?\n              jQuery.map(val, function (val) {\n                return { name: elem.name, value: val.replace(rCRLF, \"\\r\\n\") };\n              }) :\n            { name: elem.name, value: val.replace(rCRLF, \"\\r\\n\") };\n        }).get();\n    }\n  });\n\n\n  jQuery.ajaxSettings.xhr = function () {\n    try {\n      return new XMLHttpRequest();\n    } catch (e) {\n    }\n  };\n\n  var xhrId = 0,\n    xhrCallbacks = {},\n    xhrSuccessStatus = {\n      // file protocol always yields status code 0, assume 200\n      0: 200,\n      // Support: IE9\n      // #1450: sometimes IE returns 1223 when it should be 204\n      1223: 204\n    },\n    xhrSupported = jQuery.ajaxSettings.xhr();\n\n// Support: IE9\n// Open requests must be manually aborted on unload (#5280)\n  if (window.ActiveXObject) {\n    jQuery(window).on(\"unload\", function () {\n      for (var key in xhrCallbacks) {\n        xhrCallbacks[ key ]();\n      }\n    });\n  }\n\n  support.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\n  support.ajax = xhrSupported = !!xhrSupported;\n\n  jQuery.ajaxTransport(function (options) {\n    var callback;\n\n    // Cross domain only allowed if supported through XMLHttpRequest\n    if (support.cors || xhrSupported && !options.crossDomain) {\n      return {\n        send: function (headers, complete) {\n          var i,\n            xhr = options.xhr(),\n            id = ++xhrId;\n\n          xhr.open(options.type, options.url, options.async, options.username, options.password);\n\n          // Apply custom fields if provided\n          if (options.xhrFields) {\n            for (i in options.xhrFields) {\n              xhr[ i ] = options.xhrFields[ i ];\n            }\n          }\n\n          // Override mime type if needed\n          if (options.mimeType && xhr.overrideMimeType) {\n            xhr.overrideMimeType(options.mimeType);\n          }\n\n          // X-Requested-With header\n          // For cross-domain requests, seeing as conditions for a preflight are\n          // akin to a jigsaw puzzle, we simply never set it to be sure.\n          // (it can always be set on a per-request basis or even using ajaxSetup)\n          // For same-domain requests, won't change header if already provided.\n          if (!options.crossDomain && !headers[\"X-Requested-With\"]) {\n            headers[\"X-Requested-With\"] = \"XMLHttpRequest\";\n          }\n\n          // Set headers\n          for (i in headers) {\n            xhr.setRequestHeader(i, headers[ i ]);\n          }\n\n          // Callback\n          callback = function (type) {\n            return function () {\n              if (callback) {\n                delete xhrCallbacks[ id ];\n                callback = xhr.onload = xhr.onerror = null;\n\n                if (type === \"abort\") {\n                  xhr.abort();\n                } else if (type === \"error\") {\n                  complete(\n                    // file: protocol always yields status 0; see #8605, #14207\n                    xhr.status,\n                    xhr.statusText\n                  );\n                } else {\n                  complete(\n                      xhrSuccessStatus[ xhr.status ] || xhr.status,\n                    xhr.statusText,\n                    // Support: IE9\n                    // Accessing binary-data responseText throws an exception\n                    // (#11426)\n                      typeof xhr.responseText === \"string\" ? {\n                      text: xhr.responseText\n                    } : undefined,\n                    xhr.getAllResponseHeaders()\n                  );\n                }\n              }\n            };\n          };\n\n          // Listen to events\n          xhr.onload = callback();\n          xhr.onerror = callback(\"error\");\n\n          // Create the abort callback\n          callback = xhrCallbacks[ id ] = callback(\"abort\");\n\n          // Do send the request\n          // This may raise an exception which is actually\n          // handled in jQuery.ajax (so no try/catch here)\n          xhr.send(options.hasContent && options.data || null);\n        },\n\n        abort: function () {\n          if (callback) {\n            callback();\n          }\n        }\n      };\n    }\n  });\n\n\n// Install script dataType\n  jQuery.ajaxSetup({\n    accepts: {\n      script: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n    },\n    contents: {\n      script: /(?:java|ecma)script/\n    },\n    converters: {\n      \"text script\": function (text) {\n        jQuery.globalEval(text);\n        return text;\n      }\n    }\n  });\n\n// Handle cache's special case and crossDomain\n  jQuery.ajaxPrefilter(\"script\", function (s) {\n    if (s.cache === undefined) {\n      s.cache = false;\n    }\n    if (s.crossDomain) {\n      s.type = \"GET\";\n    }\n  });\n\n// Bind script tag hack transport\n  jQuery.ajaxTransport(\"script\", function (s) {\n    // This transport only deals with cross domain requests\n    if (s.crossDomain) {\n      var script, callback;\n      return {\n        send: function (_, complete) {\n          script = jQuery(\"<script>\").prop({\n            async: true,\n            charset: s.scriptCharset,\n            src: s.url\n          }).on(\n            \"load error\",\n            callback = function (evt) {\n              script.remove();\n              callback = null;\n              if (evt) {\n                complete(evt.type === \"error\" ? 404 : 200, evt.type);\n              }\n            }\n          );\n          document.head.appendChild(script[ 0 ]);\n        },\n        abort: function () {\n          if (callback) {\n            callback();\n          }\n        }\n      };\n    }\n  });\n\n\n  var oldCallbacks = [],\n    rjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\n  jQuery.ajaxSetup({\n    jsonp: \"callback\",\n    jsonpCallback: function () {\n      var callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n      this[ callback ] = true;\n      return callback;\n    }\n  });\n\n// Detect, normalize options and install callbacks for jsonp requests\n  jQuery.ajaxPrefilter(\"json jsonp\", function (s, originalSettings, jqXHR) {\n\n    var callbackName, overwritten, responseContainer,\n      jsonProp = s.jsonp !== false && ( rjsonp.test(s.url) ?\n        \"url\" :\n        typeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test(s.data) && \"data\"\n        );\n\n    // Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n    if (jsonProp || s.dataTypes[ 0 ] === \"jsonp\") {\n\n      // Get callback name, remembering preexisting value associated with it\n      callbackName = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ?\n        s.jsonpCallback() :\n        s.jsonpCallback;\n\n      // Insert callback into url or form data\n      if (jsonProp) {\n        s[ jsonProp ] = s[ jsonProp ].replace(rjsonp, \"$1\" + callbackName);\n      } else if (s.jsonp !== false) {\n        s.url += ( rquery.test(s.url) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n      }\n\n      // Use data converter to retrieve json after script execution\n      s.converters[\"script json\"] = function () {\n        if (!responseContainer) {\n          jQuery.error(callbackName + \" was not called\");\n        }\n        return responseContainer[ 0 ];\n      };\n\n      // force json dataType\n      s.dataTypes[ 0 ] = \"json\";\n\n      // Install callback\n      overwritten = window[ callbackName ];\n      window[ callbackName ] = function () {\n        responseContainer = arguments;\n      };\n\n      // Clean-up function (fires after converters)\n      jqXHR.always(function () {\n        // Restore preexisting value\n        window[ callbackName ] = overwritten;\n\n        // Save back as free\n        if (s[ callbackName ]) {\n          // make sure that re-using the options doesn't screw things around\n          s.jsonpCallback = originalSettings.jsonpCallback;\n\n          // save the callback name for future use\n          oldCallbacks.push(callbackName);\n        }\n\n        // Call if it was a function and we have a response\n        if (responseContainer && jQuery.isFunction(overwritten)) {\n          overwritten(responseContainer[ 0 ]);\n        }\n\n        responseContainer = overwritten = undefined;\n      });\n\n      // Delegate to script\n      return \"script\";\n    }\n  });\n\n\n// data: string of html\n// context (optional): If specified, the fragment will be created in this context, defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\n  jQuery.parseHTML = function (data, context, keepScripts) {\n    if (!data || typeof data !== \"string\") {\n      return null;\n    }\n    if (typeof context === \"boolean\") {\n      keepScripts = context;\n      context = false;\n    }\n    context = context || document;\n\n    var parsed = rsingleTag.exec(data),\n      scripts = !keepScripts && [];\n\n    // Single tag\n    if (parsed) {\n      return [ context.createElement(parsed[1]) ];\n    }\n\n    parsed = jQuery.buildFragment([ data ], context, scripts);\n\n    if (scripts && scripts.length) {\n      jQuery(scripts).remove();\n    }\n\n    return jQuery.merge([], parsed.childNodes);\n  };\n\n\n// Keep a copy of the old load method\n  var _load = jQuery.fn.load;\n\n  /**\n   * Load a url into a page\n   */\n  jQuery.fn.load = function (url, params, callback) {\n    if (typeof url !== \"string\" && _load) {\n      return _load.apply(this, arguments);\n    }\n\n    var selector, type, response,\n      self = this,\n      off = url.indexOf(\" \");\n\n    if (off >= 0) {\n      selector = url.slice(off);\n      url = url.slice(0, off);\n    }\n\n    // If it's a function\n    if (jQuery.isFunction(params)) {\n\n      // We assume that it's the callback\n      callback = params;\n      params = undefined;\n\n      // Otherwise, build a param string\n    } else if (params && typeof params === \"object\") {\n      type = \"POST\";\n    }\n\n    // If we have elements to modify, make the request\n    if (self.length > 0) {\n      jQuery.ajax({\n        url: url,\n\n        // if \"type\" variable is undefined, then \"GET\" method will be used\n        type: type,\n        dataType: \"html\",\n        data: params\n      }).done(function (responseText) {\n\n        // Save response for use in complete callback\n        response = arguments;\n\n        self.html(selector ?\n\n          // If a selector was specified, locate the right elements in a dummy div\n          // Exclude scripts to avoid IE 'Permission Denied' errors\n          jQuery(\"<div>\").append(jQuery.parseHTML(responseText)).find(selector) :\n\n          // Otherwise use the full result\n          responseText);\n\n      }).complete(callback && function (jqXHR, status) {\n        self.each(callback, response || [ jqXHR.responseText, status, jqXHR ]);\n      });\n    }\n\n    return this;\n  };\n\n\n  jQuery.expr.filters.animated = function (elem) {\n    return jQuery.grep(jQuery.timers, function (fn) {\n      return elem === fn.elem;\n    }).length;\n  };\n\n\n  var docElem = window.document.documentElement;\n\n  /**\n   * Gets a window from an element\n   */\n  function getWindow(elem) {\n    return jQuery.isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView;\n  }\n\n  jQuery.offset = {\n    setOffset: function (elem, options, i) {\n      var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n        position = jQuery.css(elem, \"position\"),\n        curElem = jQuery(elem),\n        props = {};\n\n      // Set position first, in-case top/left are set even on static elem\n      if (position === \"static\") {\n        elem.style.position = \"relative\";\n      }\n\n      curOffset = curElem.offset();\n      curCSSTop = jQuery.css(elem, \"top\");\n      curCSSLeft = jQuery.css(elem, \"left\");\n      calculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n        ( curCSSTop + curCSSLeft ).indexOf(\"auto\") > -1;\n\n      // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n      if (calculatePosition) {\n        curPosition = curElem.position();\n        curTop = curPosition.top;\n        curLeft = curPosition.left;\n\n      } else {\n        curTop = parseFloat(curCSSTop) || 0;\n        curLeft = parseFloat(curCSSLeft) || 0;\n      }\n\n      if (jQuery.isFunction(options)) {\n        options = options.call(elem, i, curOffset);\n      }\n\n      if (options.top != null) {\n        props.top = ( options.top - curOffset.top ) + curTop;\n      }\n      if (options.left != null) {\n        props.left = ( options.left - curOffset.left ) + curLeft;\n      }\n\n      if (\"using\" in options) {\n        options.using.call(elem, props);\n\n      } else {\n        curElem.css(props);\n      }\n    }\n  };\n\n  jQuery.fn.extend({\n    offset: function (options) {\n      if (arguments.length) {\n        return options === undefined ?\n          this :\n          this.each(function (i) {\n            jQuery.offset.setOffset(this, options, i);\n          });\n      }\n\n      var docElem, win,\n        elem = this[ 0 ],\n        box = { top: 0, left: 0 },\n        doc = elem && elem.ownerDocument;\n\n      if (!doc) {\n        return;\n      }\n\n      docElem = doc.documentElement;\n\n      // Make sure it's not a disconnected DOM node\n      if (!jQuery.contains(docElem, elem)) {\n        return box;\n      }\n\n      // If we don't have gBCR, just use 0,0 rather than error\n      // BlackBerry 5, iOS 3 (original iPhone)\n      if (typeof elem.getBoundingClientRect !== strundefined) {\n        box = elem.getBoundingClientRect();\n      }\n      win = getWindow(doc);\n      return {\n        top: box.top + win.pageYOffset - docElem.clientTop,\n        left: box.left + win.pageXOffset - docElem.clientLeft\n      };\n    },\n\n    position: function () {\n      if (!this[ 0 ]) {\n        return;\n      }\n\n      var offsetParent, offset,\n        elem = this[ 0 ],\n        parentOffset = { top: 0, left: 0 };\n\n      // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent\n      if (jQuery.css(elem, \"position\") === \"fixed\") {\n        // We assume that getBoundingClientRect is available when computed position is fixed\n        offset = elem.getBoundingClientRect();\n\n      } else {\n        // Get *real* offsetParent\n        offsetParent = this.offsetParent();\n\n        // Get correct offsets\n        offset = this.offset();\n        if (!jQuery.nodeName(offsetParent[ 0 ], \"html\")) {\n          parentOffset = offsetParent.offset();\n        }\n\n        // Add offsetParent borders\n        parentOffset.top += jQuery.css(offsetParent[ 0 ], \"borderTopWidth\", true);\n        parentOffset.left += jQuery.css(offsetParent[ 0 ], \"borderLeftWidth\", true);\n      }\n\n      // Subtract parent offsets and element margins\n      return {\n        top: offset.top - parentOffset.top - jQuery.css(elem, \"marginTop\", true),\n        left: offset.left - parentOffset.left - jQuery.css(elem, \"marginLeft\", true)\n      };\n    },\n\n    offsetParent: function () {\n      return this.map(function () {\n        var offsetParent = this.offsetParent || docElem;\n\n        while (offsetParent && ( !jQuery.nodeName(offsetParent, \"html\") && jQuery.css(offsetParent, \"position\") === \"static\" )) {\n          offsetParent = offsetParent.offsetParent;\n        }\n\n        return offsetParent || docElem;\n      });\n    }\n  });\n\n// Create scrollLeft and scrollTop methods\n  jQuery.each({ scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function (method, prop) {\n    var top = \"pageYOffset\" === prop;\n\n    jQuery.fn[ method ] = function (val) {\n      return access(this, function (elem, method, val) {\n        var win = getWindow(elem);\n\n        if (val === undefined) {\n          return win ? win[ prop ] : elem[ method ];\n        }\n\n        if (win) {\n          win.scrollTo(\n            !top ? val : window.pageXOffset,\n            top ? val : window.pageYOffset\n          );\n\n        } else {\n          elem[ method ] = val;\n        }\n      }, method, val, arguments.length, null);\n    };\n  });\n\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// getComputedStyle returns percent when specified for top/left/bottom/right\n// rather than make the css module depend on the offset module, we just check for it here\n  jQuery.each([ \"top\", \"left\" ], function (i, prop) {\n    jQuery.cssHooks[ prop ] = addGetHookIf(support.pixelPosition,\n      function (elem, computed) {\n        if (computed) {\n          computed = curCSS(elem, prop);\n          // if curCSS returns percentage, fallback to offset\n          return rnumnonpx.test(computed) ?\n            jQuery(elem).position()[ prop ] + \"px\" :\n            computed;\n        }\n      }\n    );\n  });\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\n  jQuery.each({ Height: \"height\", Width: \"width\" }, function (name, type) {\n    jQuery.each({ padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function (defaultExtra, funcName) {\n      // margin is only for outerHeight, outerWidth\n      jQuery.fn[ funcName ] = function (margin, value) {\n        var chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n          extra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n        return access(this, function (elem, type, value) {\n          var doc;\n\n          if (jQuery.isWindow(elem)) {\n            // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n            // isn't a whole lot we can do. See pull request at this URL for discussion:\n            // https://github.com/jquery/jquery/pull/764\n            return elem.document.documentElement[ \"client\" + name ];\n          }\n\n          // Get document width or height\n          if (elem.nodeType === 9) {\n            doc = elem.documentElement;\n\n            // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n            // whichever is greatest\n            return Math.max(\n              elem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n              elem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n              doc[ \"client\" + name ]\n            );\n          }\n\n          return value === undefined ?\n            // Get width or height on the element, requesting but not forcing parseFloat\n            jQuery.css(elem, type, extra) :\n\n            // Set width or height on the element\n            jQuery.style(elem, type, value, extra);\n        }, type, chainable ? margin : undefined, chainable, null);\n      };\n    });\n  });\n\n\n// The number of elements contained in the matched element set\n  jQuery.fn.size = function () {\n    return this.length;\n  };\n\n  jQuery.fn.andSelf = jQuery.fn.addBack;\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n  if (typeof define === \"function\" && define.amd) {\n    define(\"jquery\", [], function () {\n      return jQuery;\n    });\n  }\n\n\n  var\n  // Map over jQuery in case of overwrite\n    _jQuery = window.jQuery,\n\n  // Map over the $ in case of overwrite\n    _$ = window.$;\n\n  jQuery.noConflict = function (deep) {\n    if (window.$ === jQuery) {\n      window.$ = _$;\n    }\n\n    if (deep && window.jQuery === jQuery) {\n      window.jQuery = _jQuery;\n    }\n\n    return jQuery;\n  };\n\n// Expose jQuery and $ identifiers, even in\n// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\n  if (typeof noGlobal === strundefined) {\n    window.jQuery = window.$ = jQuery;\n  }\n\n\n  return jQuery;\n\n}));\n"
  },
  {
    "path": "public/libs/jquery-ujs.js",
    "content": "(function ($, undefined) {\n\n  /**\n   * Unobtrusive scripting adapter for jQuery\n   * https://github.com/rails/jquery-ujs\n   *\n   * Requires jQuery 1.7.0 or later.\n   *\n   * Released under the MIT license\n   *\n   */\n\n  // Cut down on the number of issues from people inadvertently including jquery_ujs twice\n  // by detecting and raising an error when it happens.\n  if ($.rails !== undefined) {\n    $.error('jquery-ujs has already been loaded!');\n  }\n\n  // Shorthand to make it a little easier to call public rails functions from within rails.js\n  var rails;\n  var $document = $(document);\n\n  $.rails = rails = {\n    // Link elements bound by jquery-ujs\n    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',\n\n    // Button elements bound by jquery-ujs\n    buttonClickSelector: 'button[data-remote]',\n\n    // Select elements bound by jquery-ujs\n    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',\n\n    // Form elements bound by jquery-ujs\n    formSubmitSelector: 'form',\n\n    // Form input elements bound by jquery-ujs\n    formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',\n\n    // Form input elements disabled during form submission\n    disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',\n\n    // Form input elements re-enabled after form submission\n    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',\n\n    // Form required input elements\n    requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',\n\n    // Form file input elements\n    fileInputSelector: 'input[type=file]',\n\n    // Link onClick disable selector with possible reenable after remote submission\n    linkDisableSelector: 'a[data-disable-with]',\n\n    // Make sure that every Ajax request sends the CSRF token\n    CSRFProtection: function (xhr) {\n      var token = $('meta[name=\"csrf-token\"]').attr('content');\n      if (token) xhr.setRequestHeader('X-CSRF-Token', token);\n    },\n\n    // making sure that all forms have actual up-to-date token(cached forms contain old one)\n    refreshCSRFTokens: function () {\n      var csrfToken = $('meta[name=csrf-token]').attr('content');\n      var csrfParam = $('meta[name=csrf-param]').attr('content');\n      $('form input[name=\"' + csrfParam + '\"]').val(csrfToken);\n    },\n\n    // Triggers an event on an element and returns false if the event result is false\n    fire: function (obj, name, data) {\n      var event = $.Event(name);\n      obj.trigger(event, data);\n      return event.result !== false;\n    },\n\n    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm\n    confirm: function (message) {\n      return confirm(message);\n    },\n\n    // Default ajax function, may be overridden with custom function in $.rails.ajax\n    ajax: function (options) {\n      return $.ajax(options);\n    },\n\n    // Default way to get an element's href. May be overridden at $.rails.href.\n    href: function (element) {\n      return element.attr('href');\n    },\n\n    // Submits \"remote\" forms and links with ajax\n    handleRemote: function (element) {\n      var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;\n\n      if (rails.fire(element, 'ajax:before')) {\n        elCrossDomain = element.data('cross-domain');\n        crossDomain = elCrossDomain === undefined ? null : elCrossDomain;\n        withCredentials = element.data('with-credentials') || null;\n        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);\n\n        if (element.is('form')) {\n          method = element.attr('method');\n          url = element.attr('action');\n          data = element.serializeArray();\n          // memoized value from clicked submit button\n          var button = element.data('ujs:submit-button');\n          if (button) {\n            data.push(button);\n            element.data('ujs:submit-button', null);\n          }\n        } else if (element.is(rails.inputChangeSelector)) {\n          method = element.data('method');\n          url = element.data('url');\n          data = element.serialize();\n          if (element.data('params')) data = data + \"&\" + element.data('params');\n        } else if (element.is(rails.buttonClickSelector)) {\n          method = element.data('method') || 'get';\n          url = element.data('url');\n          data = element.serialize();\n          if (element.data('params')) data = data + \"&\" + element.data('params');\n        } else {\n          method = element.data('method');\n          url = rails.href(element);\n          data = element.data('params') || null;\n        }\n\n        options = {\n          type: method || 'GET', data: data, dataType: dataType,\n          // stopping the \"ajax:beforeSend\" event will cancel the ajax request\n          beforeSend: function (xhr, settings) {\n            if (settings.dataType === undefined) {\n              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);\n            }\n            return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);\n          },\n          success: function (data, status, xhr) {\n            element.trigger('ajax:success', [data, status, xhr]);\n          },\n          complete: function (xhr, status) {\n            element.trigger('ajax:complete', [xhr, status]);\n          },\n          error: function (xhr, status, error) {\n            element.trigger('ajax:error', [xhr, status, error]);\n          },\n          crossDomain: crossDomain\n        };\n\n        // There is no withCredentials for IE6-8 when\n        // \"Enable native XMLHTTP support\" is disabled\n        if (withCredentials) {\n          options.xhrFields = {\n            withCredentials: withCredentials\n          };\n        }\n\n        // Only pass url to `ajax` options if not blank\n        if (url) {\n          options.url = url;\n        }\n\n        var jqxhr = rails.ajax(options);\n        element.trigger('ajax:send', jqxhr);\n        return jqxhr;\n      } else {\n        return false;\n      }\n    },\n\n    // Handles \"data-method\" on links such as:\n    // <a href=\"/users/5\" data-method=\"delete\" rel=\"nofollow\" data-confirm=\"Are you sure?\">Delete</a>\n    handleMethod: function (link) {\n      var href = rails.href(link),\n        method = link.data('method'),\n        target = link.attr('target'),\n        csrfToken = $('meta[name=csrf-token]').attr('content'),\n        csrfParam = $('meta[name=csrf-param]').attr('content'),\n        form = $('<form method=\"post\" action=\"' + href + '\"></form>'),\n        metadataInput = '<input name=\"_method\" value=\"' + method + '\" type=\"hidden\" />';\n\n      if (csrfParam !== undefined && csrfToken !== undefined) {\n        metadataInput += '<input name=\"' + csrfParam + '\" value=\"' + csrfToken + '\" type=\"hidden\" />';\n      }\n\n      if (target) {\n        form.attr('target', target);\n      }\n\n      form.hide().append(metadataInput).appendTo('body');\n      form.submit();\n    },\n\n    /* Disables form elements:\n     - Caches element value in 'ujs:enable-with' data store\n     - Replaces element text with value of 'data-disable-with' attribute\n     - Sets disabled property to true\n     */\n    disableFormElements: function (form) {\n      form.find(rails.disableSelector).each(function () {\n        var element = $(this), method = element.is('button') ? 'html' : 'val';\n        element.data('ujs:enable-with', element[method]());\n        element[method](element.data('disable-with'));\n        element.prop('disabled', true);\n      });\n    },\n\n    /* Re-enables disabled form elements:\n     - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)\n     - Sets disabled property to false\n     */\n    enableFormElements: function (form) {\n      form.find(rails.enableSelector).each(function () {\n        var element = $(this), method = element.is('button') ? 'html' : 'val';\n        if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));\n        element.prop('disabled', false);\n      });\n    },\n\n    /* For 'data-confirm' attribute:\n     - Fires `confirm` event\n     - Shows the confirmation dialog\n     - Fires the `confirm:complete` event\n\n     Returns `true` if no function stops the chain and user chose yes; `false` otherwise.\n     Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.\n     Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function\n     return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.\n     */\n    allowAction: function (element) {\n      var message = element.data('confirm'),\n        answer = false, callback;\n      if (!message) {\n        return true;\n      }\n\n      if (rails.fire(element, 'confirm')) {\n        answer = rails.confirm(message);\n        callback = rails.fire(element, 'confirm:complete', [answer]);\n      }\n      return answer && callback;\n    },\n\n    // Helper function which checks for blank inputs in a form that match the specified CSS selector\n    blankInputs: function (form, specifiedSelector, nonBlank) {\n      var inputs = $(), input, valueToCheck,\n        selector = specifiedSelector || 'input,textarea',\n        allInputs = form.find(selector);\n\n      allInputs.each(function () {\n        input = $(this);\n        valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val();\n        // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey\n        if (!valueToCheck === !nonBlank) {\n\n          // Don't count unchecked required radio if other radio with same name is checked\n          if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name=\"' + input.attr('name') + '\"]').length) {\n            return true; // Skip to next input\n          }\n\n          inputs = inputs.add(input);\n        }\n      });\n      return inputs.length ? inputs : false;\n    },\n\n    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector\n    nonBlankInputs: function (form, specifiedSelector) {\n      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank\n    },\n\n    // Helper function, needed to provide consistent behavior in IE\n    stopEverything: function (e) {\n      $(e.target).trigger('ujs:everythingStopped');\n      e.stopImmediatePropagation();\n      return false;\n    },\n\n    //  replace element's html with the 'data-disable-with' after storing original html\n    //  and prevent clicking on it\n    disableElement: function (element) {\n      element.data('ujs:enable-with', element.html()); // store enabled state\n      element.html(element.data('disable-with')); // set to disabled state\n      element.bind('click.railsDisable', function (e) { // prevent further clicking\n        return rails.stopEverything(e);\n      });\n    },\n\n    // restore element to its original state which was disabled by 'disableElement' above\n    enableElement: function (element) {\n      if (element.data('ujs:enable-with') !== undefined) {\n        element.html(element.data('ujs:enable-with')); // set to old enabled state\n        element.removeData('ujs:enable-with'); // clean up cache\n      }\n      element.unbind('click.railsDisable'); // enable element\n    }\n\n  };\n\n  if (rails.fire($document, 'rails:attachBindings')) {\n\n    $.ajaxPrefilter(function (options, originalOptions, xhr) {\n      if (!options.crossDomain) {\n        rails.CSRFProtection(xhr);\n      }\n    });\n\n    $document.delegate(rails.linkDisableSelector, 'ajax:complete', function () {\n      rails.enableElement($(this));\n    });\n\n    $document.delegate(rails.linkClickSelector, 'click.rails', function (e) {\n      var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;\n      if (!rails.allowAction(link)) return rails.stopEverything(e);\n\n      if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);\n\n      if (link.data('remote') !== undefined) {\n        if (metaClick && (!method || method === 'GET') && !data) {\n          return true;\n        }\n\n        var handleRemote = rails.handleRemote(link);\n        // response from rails.handleRemote() will either be false or a deferred object promise.\n        if (handleRemote === false) {\n          rails.enableElement(link);\n        } else {\n          handleRemote.error(function () {\n            rails.enableElement(link);\n          });\n        }\n        return false;\n\n      } else if (link.data('method')) {\n        rails.handleMethod(link);\n        return false;\n      }\n    });\n\n    $document.delegate(rails.buttonClickSelector, 'click.rails', function (e) {\n      var button = $(this);\n      if (!rails.allowAction(button)) return rails.stopEverything(e);\n\n      rails.handleRemote(button);\n      return false;\n    });\n\n    $document.delegate(rails.inputChangeSelector, 'change.rails', function (e) {\n      var link = $(this);\n      if (!rails.allowAction(link)) return rails.stopEverything(e);\n\n      rails.handleRemote(link);\n      return false;\n    });\n\n    $document.delegate(rails.formSubmitSelector, 'submit.rails', function (e) {\n      var form = $(this),\n        remote = form.data('remote') !== undefined,\n        blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),\n        nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);\n\n      if (!rails.allowAction(form)) return rails.stopEverything(e);\n\n      // skip other logic when required values are missing or file upload is present\n      if (blankRequiredInputs && form.attr(\"novalidate\") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {\n        return rails.stopEverything(e);\n      }\n\n      if (remote) {\n        if (nonBlankFileInputs) {\n          // slight timeout so that the submit button gets properly serialized\n          // (make it easy for event handler to serialize form without disabled values)\n          setTimeout(function () {\n            rails.disableFormElements(form);\n          }, 13);\n          var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);\n\n          // re-enable form elements if event bindings return false (canceling normal form submission)\n          if (!aborted) {\n            setTimeout(function () {\n              rails.enableFormElements(form);\n            }, 13);\n          }\n\n          return aborted;\n        }\n\n        rails.handleRemote(form);\n        return false;\n\n      } else {\n        // slight timeout so that the submit button gets properly serialized\n        setTimeout(function () {\n          rails.disableFormElements(form);\n        }, 13);\n      }\n    });\n\n    $document.delegate(rails.formInputClickSelector, 'click.rails', function (event) {\n      var button = $(this);\n\n      if (!rails.allowAction(button)) return rails.stopEverything(event);\n\n      // register the pressed submit button\n      var name = button.attr('name'),\n        data = name ? {name: name, value: button.val()} : null;\n\n      button.closest('form').data('ujs:submit-button', data);\n    });\n\n    $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function (event) {\n      if (this == event.target) rails.disableFormElements($(this));\n    });\n\n    $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function (event) {\n      if (this == event.target) rails.enableFormElements($(this));\n    });\n\n    $(function () {\n      rails.refreshCSRFTokens();\n    });\n  }\n\n})(jQuery);\n"
  },
  {
    "path": "public/libs/jquery.atwho.js",
    "content": "/*! jquery.atwho - v0.5.1 - 2014-10-16\n* Copyright (c) 2014 chord.luo <chord.luo@gmail.com>;\n* homepage: http://ichord.github.com/At.js\n* Licensed MIT\n*/\n\n(function() {\n  (function(factory) {\n    if (typeof define === 'function' && define.amd) {\n      return define(['jquery'], factory);\n    } else {\n      return factory(window.jQuery);\n    }\n  })(function($) {\n\nvar Api, App, Controller, DEFAULT_CALLBACKS, KEY_CODE, Model, View,\n  __slice = [].slice;\n\nApp = (function() {\n  function App(inputor) {\n    this.current_flag = null;\n    this.controllers = {};\n    this.alias_maps = {};\n    this.$inputor = $(inputor);\n    this.setIframe();\n    this.listen();\n  }\n\n  App.prototype.createContainer = function(doc) {\n    if ((this.$el = $(\"#atwho-container\", doc)).length === 0) {\n      return $(doc.body).append(this.$el = $(\"<div id='atwho-container'></div>\"));\n    }\n  };\n\n  App.prototype.setIframe = function(iframe, standalone) {\n    var _ref;\n    if (standalone == null) {\n      standalone = false;\n    }\n    if (iframe) {\n      this.window = iframe.contentWindow;\n      this.document = iframe.contentDocument || this.window.document;\n      this.iframe = iframe;\n    } else {\n      this.document = document;\n      this.window = window;\n      this.iframe = null;\n    }\n    if (this.iframeStandalone = standalone) {\n      if ((_ref = this.$el) != null) {\n        _ref.remove();\n      }\n      return this.createContainer(this.document);\n    } else {\n      return this.createContainer(document);\n    }\n  };\n\n  App.prototype.controller = function(at) {\n    var c, current, current_flag, _ref;\n    if (this.alias_maps[at]) {\n      current = this.controllers[this.alias_maps[at]];\n    } else {\n      _ref = this.controllers;\n      for (current_flag in _ref) {\n        c = _ref[current_flag];\n        if (current_flag === at) {\n          current = c;\n          break;\n        }\n      }\n    }\n    if (current) {\n      return current;\n    } else {\n      return this.controllers[this.current_flag];\n    }\n  };\n\n  App.prototype.set_context_for = function(at) {\n    this.current_flag = at;\n    return this;\n  };\n\n  App.prototype.reg = function(flag, setting) {\n    var controller, _base;\n    controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));\n    if (setting.alias) {\n      this.alias_maps[setting.alias] = flag;\n    }\n    controller.init(setting);\n    return this;\n  };\n\n  App.prototype.listen = function() {\n    return this.$inputor.on('keyup.atwhoInner', (function(_this) {\n      return function(e) {\n        return _this.on_keyup(e);\n      };\n    })(this)).on('keydown.atwhoInner', (function(_this) {\n      return function(e) {\n        return _this.on_keydown(e);\n      };\n    })(this)).on('scroll.atwhoInner', (function(_this) {\n      return function(e) {\n        var _ref;\n        return (_ref = _this.controller()) != null ? _ref.view.hide(e) : void 0;\n      };\n    })(this)).on('blur.atwhoInner', (function(_this) {\n      return function(e) {\n        var c;\n        if (c = _this.controller()) {\n          return c.view.hide(e, c.get_opt(\"display_timeout\"));\n        }\n      };\n    })(this)).on('click.atwhoInner', (function(_this) {\n      return function(e) {\n        var _ref;\n        return (_ref = _this.controller()) != null ? _ref.view.hide(e) : void 0;\n      };\n    })(this));\n  };\n\n  App.prototype.shutdown = function() {\n    var c, _, _ref;\n    _ref = this.controllers;\n    for (_ in _ref) {\n      c = _ref[_];\n      c.destroy();\n      delete this.controllers[_];\n    }\n    this.$inputor.off('.atwhoInner');\n    return this.$el.remove();\n  };\n\n  App.prototype.dispatch = function() {\n    return $.map(this.controllers, (function(_this) {\n      return function(c) {\n        var delay;\n        if (delay = c.get_opt('delay')) {\n          clearTimeout(_this.delayedCallback);\n          return _this.delayedCallback = setTimeout(function() {\n            if (c.look_up()) {\n              return _this.set_context_for(c.at);\n            }\n          }, delay);\n        } else {\n          if (c.look_up()) {\n            return _this.set_context_for(c.at);\n          }\n        }\n      };\n    })(this));\n  };\n\n  App.prototype.on_keyup = function(e) {\n    var _ref;\n    switch (e.keyCode) {\n      case KEY_CODE.ESC:\n        e.preventDefault();\n        if ((_ref = this.controller()) != null) {\n          _ref.view.hide();\n        }\n        break;\n      case KEY_CODE.DOWN:\n      case KEY_CODE.UP:\n      case KEY_CODE.CTRL:\n        $.noop();\n        break;\n      case KEY_CODE.P:\n      case KEY_CODE.N:\n        if (!e.ctrlKey) {\n          this.dispatch();\n        }\n        break;\n      default:\n        this.dispatch();\n    }\n  };\n\n  App.prototype.on_keydown = function(e) {\n    var view, _ref;\n    view = (_ref = this.controller()) != null ? _ref.view : void 0;\n    if (!(view && view.visible())) {\n      return;\n    }\n    switch (e.keyCode) {\n      case KEY_CODE.ESC:\n        e.preventDefault();\n        view.hide(e);\n        break;\n      case KEY_CODE.UP:\n        e.preventDefault();\n        view.prev();\n        break;\n      case KEY_CODE.DOWN:\n        e.preventDefault();\n        view.next();\n        break;\n      case KEY_CODE.P:\n        if (!e.ctrlKey) {\n          return;\n        }\n        e.preventDefault();\n        view.prev();\n        break;\n      case KEY_CODE.N:\n        if (!e.ctrlKey) {\n          return;\n        }\n        e.preventDefault();\n        view.next();\n        break;\n      case KEY_CODE.TAB:\n      case KEY_CODE.ENTER:\n        if (!view.visible()) {\n          return;\n        }\n        e.preventDefault();\n        view.choose(e);\n        break;\n      default:\n        $.noop();\n    }\n  };\n\n  return App;\n\n})();\n\nController = (function() {\n  Controller.prototype.uid = function() {\n    return (Math.random().toString(16) + \"000000000\").substr(2, 8) + (new Date().getTime());\n  };\n\n  function Controller(app, at) {\n    this.app = app;\n    this.at = at;\n    this.$inputor = this.app.$inputor;\n    this.id = this.$inputor[0].id || this.uid();\n    this.setting = null;\n    this.query = null;\n    this.pos = 0;\n    this.cur_rect = null;\n    this.range = null;\n    if ((this.$el = $(\"#atwho-ground-\" + this.id, this.app.$el)).length === 0) {\n      this.app.$el.append(this.$el = $(\"<div id='atwho-ground-\" + this.id + \"'></div>\"));\n    }\n    this.model = new Model(this);\n    this.view = new View(this);\n  }\n\n  Controller.prototype.init = function(setting) {\n    this.setting = $.extend({}, this.setting || $.fn.atwho[\"default\"], setting);\n    this.view.init();\n    return this.model.reload(this.setting.data);\n  };\n\n  Controller.prototype.destroy = function() {\n    this.trigger('beforeDestroy');\n    this.model.destroy();\n    this.view.destroy();\n    return this.$el.remove();\n  };\n\n  Controller.prototype.call_default = function() {\n    var args, error, func_name;\n    func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n    try {\n      return DEFAULT_CALLBACKS[func_name].apply(this, args);\n    } catch (_error) {\n      error = _error;\n      return $.error(\"\" + error + \" Or maybe At.js doesn't have function \" + func_name);\n    }\n  };\n\n  Controller.prototype.trigger = function(name, data) {\n    var alias, event_name;\n    if (data == null) {\n      data = [];\n    }\n    data.push(this);\n    alias = this.get_opt('alias');\n    event_name = alias ? \"\" + name + \"-\" + alias + \".atwho\" : \"\" + name + \".atwho\";\n    return this.$inputor.trigger(event_name, data);\n  };\n\n  Controller.prototype.callbacks = function(func_name) {\n    return this.get_opt(\"callbacks\")[func_name] || DEFAULT_CALLBACKS[func_name];\n  };\n\n  Controller.prototype.get_opt = function(at, default_value) {\n    var e;\n    try {\n      return this.setting[at];\n    } catch (_error) {\n      e = _error;\n      return null;\n    }\n  };\n\n  Controller.prototype.content = function() {\n    if (this.$inputor.is('textarea, input')) {\n      return this.$inputor.val();\n    } else {\n      return this.$inputor.text();\n    }\n  };\n\n  Controller.prototype.catch_query = function() {\n    var caret_pos, content, end, query, start, subtext;\n    content = this.content();\n    caret_pos = this.$inputor.caret('pos', {\n      iframe: this.app.iframe\n    });\n    subtext = content.slice(0, caret_pos);\n    query = this.callbacks(\"matcher\").call(this, this.at, subtext, this.get_opt('start_with_space'));\n    if (typeof query === \"string\" && query.length <= this.get_opt('max_len', 20)) {\n      start = caret_pos - query.length;\n      end = start + query.length;\n      this.pos = start;\n      query = {\n        'text': query,\n        'head_pos': start,\n        'end_pos': end\n      };\n      this.trigger(\"matched\", [this.at, query.text]);\n    } else {\n      query = null;\n      this.view.hide();\n    }\n    return this.query = query;\n  };\n\n  Controller.prototype.rect = function() {\n    var c, iframe_offset, scale_bottom;\n    if (!(c = this.$inputor.caret('offset', this.pos - 1, {\n      iframe: this.app.iframe\n    }))) {\n      return;\n    }\n    if (this.app.iframe && !this.app.iframeStandalone) {\n      iframe_offset = $(this.app.iframe).offset();\n      c.left += iframe_offset.left;\n      c.top += iframe_offset.top;\n    }\n    if (this.$inputor.is('[contentEditable]')) {\n      c = this.cur_rect || (this.cur_rect = c);\n    }\n    scale_bottom = this.app.document.selection ? 0 : 2;\n    return {\n      left: c.left,\n      top: c.top,\n      bottom: c.top + c.height + scale_bottom\n    };\n  };\n\n  Controller.prototype.reset_rect = function() {\n    if (this.$inputor.is('[contentEditable]')) {\n      return this.cur_rect = null;\n    }\n  };\n\n  Controller.prototype.mark_range = function() {\n    var sel;\n    if (!this.$inputor.is('[contentEditable]')) {\n      return;\n    }\n    if (this.app.window.getSelection && (sel = this.app.window.getSelection()).rangeCount > 0) {\n      return this.range = sel.getRangeAt(0);\n    } else if (this.app.document.selection) {\n      return this.ie8_range = this.app.document.selection.createRange();\n    }\n  };\n\n  Controller.prototype.insert_content_for = function($li) {\n    var data, data_value, tpl;\n    data_value = $li.data('value');\n    tpl = this.get_opt('insert_tpl');\n    if (this.$inputor.is('textarea, input') || !tpl) {\n      return data_value;\n    }\n    data = $.extend({}, $li.data('item-data'), {\n      'atwho-data-value': data_value,\n      'atwho-at': this.at\n    });\n    return this.callbacks(\"tpl_eval\").call(this, tpl, data);\n  };\n\n  Controller.prototype.insert = function(content, $li) {\n    var $inputor, content_node, pos, range, sel, source, start_str, text, wrapped_content;\n    $inputor = this.$inputor;\n    wrapped_content = this.callbacks('inserting_wrapper').call(this, $inputor, content, this.get_opt(\"suffix\"));\n    if ($inputor.is('textarea, input')) {\n      source = $inputor.val();\n      start_str = source.slice(0, Math.max(this.query.head_pos - this.at.length, 0));\n      text = \"\" + start_str + wrapped_content + (source.slice(this.query['end_pos'] || 0));\n      $inputor.val(text);\n      $inputor.caret('pos', start_str.length + wrapped_content.length, {\n        iframe: this.app.iframe\n      });\n    } else if (range = this.range) {\n      pos = range.startOffset - (this.query.end_pos - this.query.head_pos) - this.at.length;\n      range.setStart(range.endContainer, Math.max(pos, 0));\n      range.setEnd(range.endContainer, range.endOffset);\n      range.deleteContents();\n      content_node = $(wrapped_content, this.app.document)[0];\n      range.insertNode(content_node);\n      range.setEndAfter(content_node);\n      range.collapse(false);\n      sel = this.app.window.getSelection();\n      sel.removeAllRanges();\n      sel.addRange(range);\n    } else if (range = this.ie8_range) {\n      range.moveStart('character', this.query.end_pos - this.query.head_pos - this.at.length);\n      range.pasteHTML(wrapped_content);\n      range.collapse(false);\n      range.select();\n    }\n    if (!$inputor.is(':focus')) {\n      $inputor.focus();\n    }\n    return $inputor.change();\n  };\n\n  Controller.prototype.render_view = function(data) {\n    var search_key;\n    search_key = this.get_opt(\"search_key\");\n    data = this.callbacks(\"sorter\").call(this, this.query.text, data.slice(0, 1001), search_key);\n    return this.view.render(data.slice(0, this.get_opt('limit')));\n  };\n\n  Controller.prototype.look_up = function() {\n    var query, _callback;\n    if (!(query = this.catch_query())) {\n      return;\n    }\n    _callback = function(data) {\n      if (data && data.length > 0) {\n        return this.render_view(data);\n      } else {\n        return this.view.hide();\n      }\n    };\n    this.model.query(query.text, $.proxy(_callback, this));\n    return query;\n  };\n\n  return Controller;\n\n})();\n\nModel = (function() {\n  function Model(context) {\n    this.context = context;\n    this.at = this.context.at;\n    this.storage = this.context.$inputor;\n  }\n\n  Model.prototype.destroy = function() {\n    return this.storage.data(this.at, null);\n  };\n\n  Model.prototype.saved = function() {\n    return this.fetch() > 0;\n  };\n\n  Model.prototype.query = function(query, callback) {\n    var data, search_key, _remote_filter;\n    data = this.fetch();\n    search_key = this.context.get_opt(\"search_key\");\n    data = this.context.callbacks('filter').call(this.context, query, data, search_key) || [];\n    _remote_filter = this.context.callbacks('remote_filter');\n    if (data.length > 0 || (!_remote_filter && data.length === 0)) {\n      return callback(data);\n    } else {\n      return _remote_filter.call(this.context, query, callback);\n    }\n  };\n\n  Model.prototype.fetch = function() {\n    return this.storage.data(this.at) || [];\n  };\n\n  Model.prototype.save = function(data) {\n    return this.storage.data(this.at, this.context.callbacks(\"before_save\").call(this.context, data || []));\n  };\n\n  Model.prototype.load = function(data) {\n    if (!(this.saved() || !data)) {\n      return this._load(data);\n    }\n  };\n\n  Model.prototype.reload = function(data) {\n    return this._load(data);\n  };\n\n  Model.prototype._load = function(data) {\n    if (typeof data === \"string\") {\n      return $.ajax(data, {\n        dataType: \"json\"\n      }).done((function(_this) {\n        return function(data) {\n          return _this.save(data);\n        };\n      })(this));\n    } else {\n      return this.save(data);\n    }\n  };\n\n  return Model;\n\n})();\n\nView = (function() {\n  function View(context) {\n    this.context = context;\n    this.$el = $(\"<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>\");\n    this.timeout_id = null;\n    this.context.$el.append(this.$el);\n    this.bind_event();\n  }\n\n  View.prototype.init = function() {\n    var id;\n    id = this.context.get_opt(\"alias\") || this.context.at.charCodeAt(0);\n    return this.$el.attr({\n      'id': \"at-view-\" + id\n    });\n  };\n\n  View.prototype.destroy = function() {\n    return this.$el.remove();\n  };\n\n  View.prototype.bind_event = function() {\n    var $menu;\n    $menu = this.$el.find('ul');\n    return $menu.on('mouseenter.atwho-view', 'li', function(e) {\n      $menu.find('.cur').removeClass('cur');\n      return $(e.currentTarget).addClass('cur');\n    }).on('click', (function(_this) {\n      return function(e) {\n        _this.choose(e);\n        return e.preventDefault();\n      };\n    })(this));\n  };\n\n  View.prototype.visible = function() {\n    return this.$el.is(\":visible\");\n  };\n\n  View.prototype.choose = function(e) {\n    var $li, content;\n    if (($li = this.$el.find(\".cur\")).length) {\n      content = this.context.insert_content_for($li);\n      this.context.insert(this.context.callbacks(\"before_insert\").call(this.context, content, $li), $li);\n      this.context.trigger(\"inserted\", [$li, e]);\n      this.hide(e);\n    }\n    if (this.context.get_opt(\"hide_without_suffix\")) {\n      return this.stop_showing = true;\n    }\n  };\n\n  View.prototype.reposition = function(rect) {\n    var offset, overflowOffset, _ref, _window;\n    _window = this.context.app.iframeStandalone ? this.context.app.window : window;\n    if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {\n      rect.bottom = rect.top - this.$el.height();\n    }\n    if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {\n      rect.left = overflowOffset;\n    }\n    offset = {\n      left: rect.left,\n      top: rect.bottom\n    };\n    if ((_ref = this.context.callbacks(\"before_reposition\")) != null) {\n      _ref.call(this.context, offset);\n    }\n    this.$el.offset(offset);\n    return this.context.trigger(\"reposition\", [offset]);\n  };\n\n  View.prototype.next = function() {\n    var cur, next;\n    cur = this.$el.find('.cur').removeClass('cur');\n    next = cur.next();\n    if (!next.length) {\n      next = this.$el.find('li:first');\n    }\n    return next.addClass('cur');\n  };\n\n  View.prototype.prev = function() {\n    var cur, prev;\n    cur = this.$el.find('.cur').removeClass('cur');\n    prev = cur.prev();\n    if (!prev.length) {\n      prev = this.$el.find('li:last');\n    }\n    return prev.addClass('cur');\n  };\n\n  View.prototype.show = function() {\n    var rect;\n    if (this.stop_showing) {\n      this.stop_showing = false;\n      return;\n    }\n    this.context.mark_range();\n    if (!this.visible()) {\n      this.$el.show();\n      this.context.trigger('shown');\n    }\n    if (rect = this.context.rect()) {\n      return this.reposition(rect);\n    }\n  };\n\n  View.prototype.hide = function(e, time) {\n    var callback;\n    if (!this.visible()) {\n      return;\n    }\n    if (isNaN(time)) {\n      this.context.reset_rect();\n      this.$el.hide();\n      return this.context.trigger('hidden', [e]);\n    } else {\n      callback = (function(_this) {\n        return function() {\n          return _this.hide();\n        };\n      })(this);\n      clearTimeout(this.timeout_id);\n      return this.timeout_id = setTimeout(callback, time);\n    }\n  };\n\n  View.prototype.render = function(list) {\n    var $li, $ul, item, li, tpl, _i, _len;\n    if (!($.isArray(list) && list.length > 0)) {\n      this.hide();\n      return;\n    }\n    this.$el.find('ul').empty();\n    $ul = this.$el.find('ul');\n    tpl = this.context.get_opt('tpl');\n    for (_i = 0, _len = list.length; _i < _len; _i++) {\n      item = list[_i];\n      item = $.extend({}, item, {\n        'atwho-at': this.context.at\n      });\n      li = this.context.callbacks(\"tpl_eval\").call(this.context, tpl, item);\n      $li = $(this.context.callbacks(\"highlighter\").call(this.context, li, this.context.query.text));\n      $li.data(\"item-data\", item);\n      $ul.append($li);\n    }\n    this.show();\n    if (this.context.get_opt('highlight_first')) {\n      return $ul.find(\"li:first\").addClass(\"cur\");\n    }\n  };\n\n  return View;\n\n})();\n\nKEY_CODE = {\n  DOWN: 40,\n  UP: 38,\n  ESC: 27,\n  TAB: 9,\n  ENTER: 13,\n  CTRL: 17,\n  P: 80,\n  N: 78\n};\n\nDEFAULT_CALLBACKS = {\n  before_save: function(data) {\n    var item, _i, _len, _results;\n    if (!$.isArray(data)) {\n      return data;\n    }\n    _results = [];\n    for (_i = 0, _len = data.length; _i < _len; _i++) {\n      item = data[_i];\n      if ($.isPlainObject(item)) {\n        _results.push(item);\n      } else {\n        _results.push({\n          name: item\n        });\n      }\n    }\n    return _results;\n  },\n  matcher: function(flag, subtext, should_start_with_space) {\n    var match, regexp, _a, _y;\n    flag = flag.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\");\n    if (should_start_with_space) {\n      flag = '(?:^|\\\\s)' + flag;\n    }\n    _a = decodeURI(\"%C3%80\");\n    _y = decodeURI(\"%C3%BF\");\n    regexp = new RegExp(\"\" + flag + \"([A-Za-z\" + _a + \"-\" + _y + \"0-9_\\+\\-]*)$|\" + flag + \"([^\\\\x00-\\\\xff]*)$\", 'gi');\n    match = regexp.exec(subtext);\n    if (match) {\n      return match[2] || match[1];\n    } else {\n      return null;\n    }\n  },\n  filter: function(query, data, search_key) {\n    var item, _i, _len, _results;\n    _results = [];\n    for (_i = 0, _len = data.length; _i < _len; _i++) {\n      item = data[_i];\n      if (~new String(item[search_key]).toLowerCase().indexOf(query.toLowerCase())) {\n        _results.push(item);\n      }\n    }\n    return _results;\n  },\n  remote_filter: null,\n  sorter: function(query, items, search_key) {\n    var item, _i, _len, _results;\n    if (!query) {\n      return items;\n    }\n    _results = [];\n    for (_i = 0, _len = items.length; _i < _len; _i++) {\n      item = items[_i];\n      item.atwho_order = new String(item[search_key]).toLowerCase().indexOf(query.toLowerCase());\n      if (item.atwho_order > -1) {\n        _results.push(item);\n      }\n    }\n    return _results.sort(function(a, b) {\n      return a.atwho_order - b.atwho_order;\n    });\n  },\n  tpl_eval: function(tpl, map) {\n    var error;\n    try {\n      return tpl.replace(/\\$\\{([^\\}]*)\\}/g, function(tag, key, pos) {\n        return map[key];\n      });\n    } catch (_error) {\n      error = _error;\n      return \"\";\n    }\n  },\n  highlighter: function(li, query) {\n    var regexp;\n    if (!query) {\n      return li;\n    }\n    regexp = new RegExp(\">\\\\s*(\\\\w*?)(\" + query.replace(\"+\", \"\\\\+\") + \")(\\\\w*)\\\\s*<\", 'ig');\n    return li.replace(regexp, function(str, $1, $2, $3) {\n      return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';\n    });\n  },\n  before_insert: function(value, $li) {\n    return value;\n  },\n  inserting_wrapper: function($inputor, content, suffix) {\n    var new_suffix, wrapped_content;\n    new_suffix = suffix === \"\" ? suffix : suffix || \" \";\n    if ($inputor.is('textarea, input')) {\n      return '' + content + new_suffix;\n    } else if ($inputor.attr('contentEditable') === 'true') {\n      new_suffix = suffix === \"\" ? suffix : suffix || \"&nbsp;\";\n      if (/firefox/i.test(navigator.userAgent)) {\n        wrapped_content = \"<span>\" + content + new_suffix + \"</span>\";\n      } else {\n        suffix = \"<span contenteditable='false'>\" + new_suffix + \"<span>\";\n        wrapped_content = \"<span contenteditable='false'>\" + content + suffix + \"</span>\";\n      }\n      if (this.app.document.selection) {\n        wrapped_content = \"<span contenteditable='true'>\" + content + \"</span>\";\n      }\n      return wrapped_content;\n    }\n  }\n};\n\nApi = {\n  load: function(at, data) {\n    var c;\n    if (c = this.controller(at)) {\n      return c.model.load(data);\n    }\n  },\n  setIframe: function(iframe, standalone) {\n    this.setIframe(iframe, standalone);\n    return null;\n  },\n  run: function() {\n    return this.dispatch();\n  },\n  destroy: function() {\n    this.shutdown();\n    return this.$inputor.data('atwho', null);\n  }\n};\n\n$.fn.atwho = function(method) {\n  var result, _args;\n  _args = arguments;\n  result = null;\n  this.filter('textarea, input, [contenteditable=\"\"], [contenteditable=true]').each(function() {\n    var $this, app;\n    if (!(app = ($this = $(this)).data(\"atwho\"))) {\n      $this.data('atwho', (app = new App(this)));\n    }\n    if (typeof method === 'object' || !method) {\n      return app.reg(method.at, method);\n    } else if (Api[method] && app) {\n      return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));\n    } else {\n      return $.error(\"Method \" + method + \" does not exist on jQuery.caret\");\n    }\n  });\n  return result || this;\n};\n\n$.fn.atwho[\"default\"] = {\n  at: void 0,\n  alias: void 0,\n  data: null,\n  tpl: \"<li data-value='${atwho-at}${name}'>${name}</li>\",\n  insert_tpl: \"<span id='${id}'>${atwho-data-value}</span>\",\n  callbacks: DEFAULT_CALLBACKS,\n  search_key: \"name\",\n  suffix: void 0,\n  hide_without_suffix: false,\n  start_with_space: true,\n  highlight_first: true,\n  limit: 5,\n  max_len: 20,\n  display_timeout: 300,\n  delay: null\n};\n\n  });\n}).call(this);\n"
  },
  {
    "path": "public/libs/jquery.caret.js",
    "content": "/*\n  Implement Github like autocomplete mentions\n  http://ichord.github.com/At.js\n\n  Copyright (c) 2013 chord.luo@gmail.com\n  Licensed under the MIT license.\n*/\n\n\n/*\n本插件操作 textarea 或者 input 内的插入符\n只实现了获得插入符在文本框中的位置，我设置\n插入符的位置.\n*/\n\n\n(function() {\n  (function(factory) {\n    if (typeof define === 'function' && define.amd) {\n      return define(['jquery'], factory);\n    } else {\n      return factory(window.jQuery);\n    }\n  })(function($) {\n    \"use strict\";\n    var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;\n    pluginName = 'caret';\n    EditableCaret = (function() {\n      function EditableCaret($inputor) {\n        this.$inputor = $inputor;\n        this.domInputor = this.$inputor[0];\n      }\n\n      EditableCaret.prototype.setPos = function(pos) {\n        return this.domInputor;\n      };\n\n      EditableCaret.prototype.getIEPosition = function() {\n        return this.getPosition();\n      };\n\n      EditableCaret.prototype.getPosition = function() {\n        var inputor_offset, offset;\n        offset = this.getOffset();\n        inputor_offset = this.$inputor.offset();\n        offset.left -= inputor_offset.left;\n        offset.top -= inputor_offset.top;\n        return offset;\n      };\n\n      EditableCaret.prototype.getOldIEPos = function() {\n        var preCaretTextRange, textRange;\n        textRange = oDocument.selection.createRange();\n        preCaretTextRange = oDocument.body.createTextRange();\n        preCaretTextRange.moveToElementText(this.domInputor);\n        preCaretTextRange.setEndPoint(\"EndToEnd\", textRange);\n        return preCaretTextRange.text.length;\n      };\n\n      EditableCaret.prototype.getPos = function() {\n        var clonedRange, pos, range;\n        if (range = this.range()) {\n          clonedRange = range.cloneRange();\n          clonedRange.selectNodeContents(this.domInputor);\n          clonedRange.setEnd(range.endContainer, range.endOffset);\n          pos = clonedRange.toString().length;\n          clonedRange.detach();\n          return pos;\n        } else if (oDocument.selection) {\n          return this.getOldIEPos();\n        }\n      };\n\n      EditableCaret.prototype.getOldIEOffset = function() {\n        var range, rect;\n        range = oDocument.selection.createRange().duplicate();\n        range.moveStart(\"character\", -1);\n        rect = range.getBoundingClientRect();\n        return {\n          height: rect.bottom - rect.top,\n          left: rect.left,\n          top: rect.top\n        };\n      };\n\n      EditableCaret.prototype.getOffset = function(pos) {\n        var clonedRange, offset, range, rect;\n        if (oWindow.getSelection && (range = this.range())) {\n          if (range.endOffset - 1 < 0) {\n            return null;\n          }\n          clonedRange = range.cloneRange();\n          clonedRange.setStart(range.endContainer, range.endOffset - 1);\n          clonedRange.setEnd(range.endContainer, range.endOffset);\n          rect = clonedRange.getBoundingClientRect();\n          offset = {\n            height: rect.height,\n            left: rect.left + rect.width,\n            top: rect.top\n          };\n          clonedRange.detach();\n        } else if (oDocument.selection) {\n          offset = this.getOldIEOffset();\n        }\n        if (offset) {\n          offset.top += $(oWindow).scrollTop();\n          offset.left += $(oWindow).scrollLeft();\n        }\n        return offset;\n      };\n\n      EditableCaret.prototype.range = function() {\n        var sel;\n        if (!oWindow.getSelection) {\n          return;\n        }\n        sel = oWindow.getSelection();\n        if (sel.rangeCount > 0) {\n          return sel.getRangeAt(0);\n        } else {\n          return null;\n        }\n      };\n\n      return EditableCaret;\n\n    })();\n    InputCaret = (function() {\n      function InputCaret($inputor) {\n        this.$inputor = $inputor;\n        this.domInputor = this.$inputor[0];\n      }\n\n      InputCaret.prototype.getIEPos = function() {\n        var endRange, inputor, len, normalizedValue, pos, range, textInputRange;\n        inputor = this.domInputor;\n        range = oDocument.selection.createRange();\n        pos = 0;\n        if (range && range.parentElement() === inputor) {\n          normalizedValue = inputor.value.replace(/\\r\\n/g, \"\\n\");\n          len = normalizedValue.length;\n          textInputRange = inputor.createTextRange();\n          textInputRange.moveToBookmark(range.getBookmark());\n          endRange = inputor.createTextRange();\n          endRange.collapse(false);\n          if (textInputRange.compareEndPoints(\"StartToEnd\", endRange) > -1) {\n            pos = len;\n          } else {\n            pos = -textInputRange.moveStart(\"character\", -len);\n          }\n        }\n        return pos;\n      };\n\n      InputCaret.prototype.getPos = function() {\n        if (oDocument.selection) {\n          return this.getIEPos();\n        } else {\n          return this.domInputor.selectionStart;\n        }\n      };\n\n      InputCaret.prototype.setPos = function(pos) {\n        var inputor, range;\n        inputor = this.domInputor;\n        if (oDocument.selection) {\n          range = inputor.createTextRange();\n          range.move(\"character\", pos);\n          range.select();\n        } else if (inputor.setSelectionRange) {\n          inputor.setSelectionRange(pos, pos);\n        }\n        return inputor;\n      };\n\n      InputCaret.prototype.getIEOffset = function(pos) {\n        var h, textRange, x, y;\n        textRange = this.domInputor.createTextRange();\n        pos || (pos = this.getPos());\n        textRange.move('character', pos);\n        x = textRange.boundingLeft;\n        y = textRange.boundingTop;\n        h = textRange.boundingHeight;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      InputCaret.prototype.getOffset = function(pos) {\n        var $inputor, offset, position;\n        $inputor = this.$inputor;\n        if (oDocument.selection) {\n          offset = this.getIEOffset(pos);\n          offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();\n          offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();\n          return offset;\n        } else {\n          offset = $inputor.offset();\n          position = this.getPosition(pos);\n          return offset = {\n            left: offset.left + position.left - $inputor.scrollLeft(),\n            top: offset.top + position.top - $inputor.scrollTop(),\n            height: position.height\n          };\n        }\n      };\n\n      InputCaret.prototype.getPosition = function(pos) {\n        var $inputor, at_rect, end_range, format, html, mirror, start_range;\n        $inputor = this.$inputor;\n        format = function(value) {\n          return $('<div></div>').text(value).html().replace(/\\r\\n|\\r|\\n/g, \"<br/>\").replace(/\\s/g, \"&nbsp;\");\n        };\n        if (pos === void 0) {\n          pos = this.getPos();\n        }\n        start_range = $inputor.val().slice(0, pos);\n        end_range = $inputor.val().slice(pos);\n        html = \"<span style='position: relative; display: inline;'>\" + format(start_range) + \"</span>\";\n        html += \"<span id='caret' style='position: relative; display: inline;'>|</span>\";\n        html += \"<span style='position: relative; display: inline;'>\" + format(end_range) + \"</span>\";\n        mirror = new Mirror($inputor);\n        return at_rect = mirror.create(html).rect();\n      };\n\n      InputCaret.prototype.getIEPosition = function(pos) {\n        var h, inputorOffset, offset, x, y;\n        offset = this.getIEOffset(pos);\n        inputorOffset = this.$inputor.offset();\n        x = offset.left - inputorOffset.left;\n        y = offset.top - inputorOffset.top;\n        h = offset.height;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      return InputCaret;\n\n    })();\n    Mirror = (function() {\n      Mirror.prototype.css_attr = [\"borderBottomWidth\", \"borderLeftWidth\", \"borderRightWidth\", \"borderTopStyle\", \"borderRightStyle\", \"borderBottomStyle\", \"borderLeftStyle\", \"borderTopWidth\", \"boxSizing\", \"fontFamily\", \"fontSize\", \"fontWeight\", \"height\", \"letterSpacing\", \"lineHeight\", \"marginBottom\", \"marginLeft\", \"marginRight\", \"marginTop\", \"outlineWidth\", \"overflow\", \"overflowX\", \"overflowY\", \"paddingBottom\", \"paddingLeft\", \"paddingRight\", \"paddingTop\", \"textAlign\", \"textOverflow\", \"textTransform\", \"whiteSpace\", \"wordBreak\", \"wordWrap\"];\n\n      function Mirror($inputor) {\n        this.$inputor = $inputor;\n      }\n\n      Mirror.prototype.mirrorCss = function() {\n        var css,\n          _this = this;\n        css = {\n          position: 'absolute',\n          left: -9999,\n          top: 0,\n          zIndex: -20000\n        };\n        if (this.$inputor.prop('tagName') === 'TEXTAREA') {\n          this.css_attr.push('width');\n        }\n        $.each(this.css_attr, function(i, p) {\n          return css[p] = _this.$inputor.css(p);\n        });\n        return css;\n      };\n\n      Mirror.prototype.create = function(html) {\n        this.$mirror = $('<div></div>');\n        this.$mirror.css(this.mirrorCss());\n        this.$mirror.html(html);\n        this.$inputor.after(this.$mirror);\n        return this;\n      };\n\n      Mirror.prototype.rect = function() {\n        var $flag, pos, rect;\n        $flag = this.$mirror.find(\"#caret\");\n        pos = $flag.position();\n        rect = {\n          left: pos.left,\n          top: pos.top,\n          height: $flag.height()\n        };\n        this.$mirror.remove();\n        return rect;\n      };\n\n      return Mirror;\n\n    })();\n    Utils = {\n      contentEditable: function($inputor) {\n        return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');\n      }\n    };\n    methods = {\n      pos: function(pos) {\n        if (pos || pos === 0) {\n          return this.setPos(pos);\n        } else {\n          return this.getPos();\n        }\n      },\n      position: function(pos) {\n        if (oDocument.selection) {\n          return this.getIEPosition(pos);\n        } else {\n          return this.getPosition(pos);\n        }\n      },\n      offset: function(pos) {\n        var offset;\n        offset = this.getOffset(pos);\n        return offset;\n      }\n    };\n    oDocument = null;\n    oWindow = null;\n    oFrame = null;\n    setContextBy = function(settings) {\n      var iframe;\n      if (iframe = settings != null ? settings.iframe : void 0) {\n        oFrame = iframe;\n        oWindow = iframe.contentWindow;\n        return oDocument = iframe.contentDocument || oWindow.document;\n      } else {\n        oFrame = void 0;\n        oWindow = window;\n        return oDocument = document;\n      }\n    };\n    discoveryIframeOf = function($dom) {\n      var error;\n      oDocument = $dom[0].ownerDocument;\n      oWindow = oDocument.defaultView || oDocument.parentWindow;\n      try {\n        return oFrame = oWindow.frameElement;\n      } catch (_error) {\n        error = _error;\n      }\n    };\n    $.fn.caret = function(method, value, settings) {\n      var caret;\n      if (methods[method]) {\n        if ($.isPlainObject(value)) {\n          setContextBy(value);\n          value = void 0;\n        } else {\n          setContextBy(settings);\n        }\n        caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);\n        return methods[method].apply(caret, [value]);\n      } else {\n        return $.error(\"Method \" + method + \" does not exist on jQuery.caret\");\n      }\n    };\n    $.fn.caret.EditableCaret = EditableCaret;\n    $.fn.caret.InputCaret = InputCaret;\n    $.fn.caret.Utils = Utils;\n    return $.fn.caret.apis = methods;\n  });\n\n}).call(this);\n"
  },
  {
    "path": "public/libs/lodash.compat.js",
    "content": "/**\n * @license\n * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>\n * Build: `lodash -o ./dist/lodash.compat.js`\n * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>\n * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>\n * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n * Available under MIT license <http://lodash.com/license>\n */\n;\n(function () {\n\n  /** Used as a safe reference for `undefined` in pre ES5 environments */\n  var undefined;\n\n  /** Used to pool arrays and objects used internally */\n  var arrayPool = [],\n    objectPool = [];\n\n  /** Used to generate unique IDs */\n  var idCounter = 0;\n\n  /** Used internally to indicate various things */\n  var indicatorObject = {};\n\n  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */\n  var keyPrefix = +new Date + '';\n\n  /** Used as the size when optimizations are enabled for large arrays */\n  var largeArraySize = 75;\n\n  /** Used as the max size of the `arrayPool` and `objectPool` */\n  var maxPoolSize = 40;\n\n  /** Used to detect and test whitespace */\n  var whitespace = (\n    // whitespace\n    ' \\t\\x0B\\f\\xA0\\ufeff' +\n\n    // line terminators\n    '\\n\\r\\u2028\\u2029' +\n\n    // unicode category \"Zs\" space separators\n    '\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000'\n    );\n\n  /** Used to match empty string literals in compiled template source */\n  var reEmptyStringLeading = /\\b__p \\+= '';/g,\n    reEmptyStringMiddle = /\\b(__p \\+=) '' \\+/g,\n    reEmptyStringTrailing = /(__e\\(.*?\\)|\\b__t\\)) \\+\\n'';/g;\n\n  /**\n   * Used to match ES6 template delimiters\n   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals\n   */\n  var reEsTemplate = /\\$\\{([^\\\\}]*(?:\\\\.[^\\\\}]*)*)\\}/g;\n\n  /** Used to match regexp flags from their coerced string values */\n  var reFlags = /\\w*$/;\n\n  /** Used to detected named functions */\n  var reFuncName = /^\\s*function[ \\n\\r\\t]+\\w/;\n\n  /** Used to match \"interpolate\" template delimiters */\n  var reInterpolate = /<%=([\\s\\S]+?)%>/g;\n\n  /** Used to match leading whitespace and zeros to be removed */\n  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');\n\n  /** Used to ensure capturing order of template delimiters */\n  var reNoMatch = /($^)/;\n\n  /** Used to detect functions containing a `this` reference */\n  var reThis = /\\bthis\\b/;\n\n  /** Used to match unescaped characters in compiled string literals */\n  var reUnescapedString = /['\\n\\r\\t\\u2028\\u2029\\\\]/g;\n\n  /** Used to assign default `context` object properties */\n  var contextProps = [\n    'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',\n    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',\n    'parseInt', 'setTimeout'\n  ];\n\n  /** Used to fix the JScript [[DontEnum]] bug */\n  var shadowedProps = [\n    'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',\n    'toLocaleString', 'toString', 'valueOf'\n  ];\n\n  /** Used to make template sourceURLs easier to identify */\n  var templateCounter = 0;\n\n  /** `Object#toString` result shortcuts */\n  var argsClass = '[object Arguments]',\n    arrayClass = '[object Array]',\n    boolClass = '[object Boolean]',\n    dateClass = '[object Date]',\n    errorClass = '[object Error]',\n    funcClass = '[object Function]',\n    numberClass = '[object Number]',\n    objectClass = '[object Object]',\n    regexpClass = '[object RegExp]',\n    stringClass = '[object String]';\n\n  /** Used to identify object classifications that `_.clone` supports */\n  var cloneableClasses = {};\n  cloneableClasses[funcClass] = false;\n  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =\n    cloneableClasses[boolClass] = cloneableClasses[dateClass] =\n      cloneableClasses[numberClass] = cloneableClasses[objectClass] =\n        cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;\n\n  /** Used as an internal `_.debounce` options object */\n  var debounceOptions = {\n    'leading': false,\n    'maxWait': 0,\n    'trailing': false\n  };\n\n  /** Used as the property descriptor for `__bindData__` */\n  var descriptor = {\n    'configurable': false,\n    'enumerable': false,\n    'value': null,\n    'writable': false\n  };\n\n  /** Used as the data object for `iteratorTemplate` */\n  var iteratorData = {\n    'args': '',\n    'array': null,\n    'bottom': '',\n    'firstArg': '',\n    'init': '',\n    'keys': null,\n    'loop': '',\n    'shadowedProps': null,\n    'support': null,\n    'top': '',\n    'useHas': false\n  };\n\n  /** Used to determine if values are of the language type Object */\n  var objectTypes = {\n    'boolean': false,\n    'function': true,\n    'object': true,\n    'number': false,\n    'string': false,\n    'undefined': false\n  };\n\n  /** Used to escape characters for inclusion in compiled string literals */\n  var stringEscapes = {\n    '\\\\': '\\\\',\n    \"'\": \"'\",\n    '\\n': 'n',\n    '\\r': 'r',\n    '\\t': 't',\n    '\\u2028': 'u2028',\n    '\\u2029': 'u2029'\n  };\n\n  /** Used as a reference to the global object */\n  var root = (objectTypes[typeof window] && window) || this;\n\n  /** Detect free variable `exports` */\n  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;\n\n  /** Detect free variable `module` */\n  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;\n\n  /** Detect the popular CommonJS extension `module.exports` */\n  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;\n\n  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */\n  var freeGlobal = objectTypes[typeof global] && global;\n  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {\n    root = freeGlobal;\n  }\n\n  /*--------------------------------------------------------------------------*/\n\n  /**\n   * The base implementation of `_.indexOf` without support for binary searches\n   * or `fromIndex` constraints.\n   *\n   * @private\n   * @param {Array} array The array to search.\n   * @param {*} value The value to search for.\n   * @param {number} [fromIndex=0] The index to search from.\n   * @returns {number} Returns the index of the matched value or `-1`.\n   */\n  function baseIndexOf(array, value, fromIndex) {\n    var index = (fromIndex || 0) - 1,\n      length = array ? array.length : 0;\n\n    while (++index < length) {\n      if (array[index] === value) {\n        return index;\n      }\n    }\n    return -1;\n  }\n\n  /**\n   * An implementation of `_.contains` for cache objects that mimics the return\n   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.\n   *\n   * @private\n   * @param {Object} cache The cache object to inspect.\n   * @param {*} value The value to search for.\n   * @returns {number} Returns `0` if `value` is found, else `-1`.\n   */\n  function cacheIndexOf(cache, value) {\n    var type = typeof value;\n    cache = cache.cache;\n\n    if (type == 'boolean' || value == null) {\n      return cache[value] ? 0 : -1;\n    }\n    if (type != 'number' && type != 'string') {\n      type = 'object';\n    }\n    var key = type == 'number' ? value : keyPrefix + value;\n    cache = (cache = cache[type]) && cache[key];\n\n    return type == 'object'\n      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)\n      : (cache ? 0 : -1);\n  }\n\n  /**\n   * Adds a given value to the corresponding cache object.\n   *\n   * @private\n   * @param {*} value The value to add to the cache.\n   */\n  function cachePush(value) {\n    var cache = this.cache,\n      type = typeof value;\n\n    if (type == 'boolean' || value == null) {\n      cache[value] = true;\n    } else {\n      if (type != 'number' && type != 'string') {\n        type = 'object';\n      }\n      var key = type == 'number' ? value : keyPrefix + value,\n        typeCache = cache[type] || (cache[type] = {});\n\n      if (type == 'object') {\n        (typeCache[key] || (typeCache[key] = [])).push(value);\n      } else {\n        typeCache[key] = true;\n      }\n    }\n  }\n\n  /**\n   * Used by `_.max` and `_.min` as the default callback when a given\n   * collection is a string value.\n   *\n   * @private\n   * @param {string} value The character to inspect.\n   * @returns {number} Returns the code unit of given character.\n   */\n  function charAtCallback(value) {\n    return value.charCodeAt(0);\n  }\n\n  /**\n   * Used by `sortBy` to compare transformed `collection` elements, stable sorting\n   * them in ascending order.\n   *\n   * @private\n   * @param {Object} a The object to compare to `b`.\n   * @param {Object} b The object to compare to `a`.\n   * @returns {number} Returns the sort order indicator of `1` or `-1`.\n   */\n  function compareAscending(a, b) {\n    var ac = a.criteria,\n      bc = b.criteria,\n      index = -1,\n      length = ac.length;\n\n    while (++index < length) {\n      var value = ac[index],\n        other = bc[index];\n\n      if (value !== other) {\n        if (value > other || typeof value == 'undefined') {\n          return 1;\n        }\n        if (value < other || typeof other == 'undefined') {\n          return -1;\n        }\n      }\n    }\n    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications\n    // that causes it, under certain circumstances, to return the same value for\n    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247\n    //\n    // This also ensures a stable sort in V8 and other engines.\n    // See http://code.google.com/p/v8/issues/detail?id=90\n    return a.index - b.index;\n  }\n\n  /**\n   * Creates a cache object to optimize linear searches of large arrays.\n   *\n   * @private\n   * @param {Array} [array=[]] The array to search.\n   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.\n   */\n  function createCache(array) {\n    var index = -1,\n      length = array.length,\n      first = array[0],\n      mid = array[(length / 2) | 0],\n      last = array[length - 1];\n\n    if (first && typeof first == 'object' &&\n      mid && typeof mid == 'object' && last && typeof last == 'object') {\n      return false;\n    }\n    var cache = getObject();\n    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;\n\n    var result = getObject();\n    result.array = array;\n    result.cache = cache;\n    result.push = cachePush;\n\n    while (++index < length) {\n      result.push(array[index]);\n    }\n    return result;\n  }\n\n  /**\n   * Used by `template` to escape characters for inclusion in compiled\n   * string literals.\n   *\n   * @private\n   * @param {string} match The matched character to escape.\n   * @returns {string} Returns the escaped character.\n   */\n  function escapeStringChar(match) {\n    return '\\\\' + stringEscapes[match];\n  }\n\n  /**\n   * Gets an array from the array pool or creates a new one if the pool is empty.\n   *\n   * @private\n   * @returns {Array} The array from the pool.\n   */\n  function getArray() {\n    return arrayPool.pop() || [];\n  }\n\n  /**\n   * Gets an object from the object pool or creates a new one if the pool is empty.\n   *\n   * @private\n   * @returns {Object} The object from the pool.\n   */\n  function getObject() {\n    return objectPool.pop() || {\n      'array': null,\n      'cache': null,\n      'criteria': null,\n      'false': false,\n      'index': 0,\n      'null': false,\n      'number': null,\n      'object': null,\n      'push': null,\n      'string': null,\n      'true': false,\n      'undefined': false,\n      'value': null\n    };\n  }\n\n  /**\n   * Checks if `value` is a DOM node in IE < 9.\n   *\n   * @private\n   * @param {*} value The value to check.\n   * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`.\n   */\n  function isNode(value) {\n    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`\n    // methods that are `typeof` \"string\" and still can coerce nodes to strings\n    return typeof value.toString != 'function' && typeof (value + '') == 'string';\n  }\n\n  /**\n   * Releases the given array back to the array pool.\n   *\n   * @private\n   * @param {Array} [array] The array to release.\n   */\n  function releaseArray(array) {\n    array.length = 0;\n    if (arrayPool.length < maxPoolSize) {\n      arrayPool.push(array);\n    }\n  }\n\n  /**\n   * Releases the given object back to the object pool.\n   *\n   * @private\n   * @param {Object} [object] The object to release.\n   */\n  function releaseObject(object) {\n    var cache = object.cache;\n    if (cache) {\n      releaseObject(cache);\n    }\n    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;\n    if (objectPool.length < maxPoolSize) {\n      objectPool.push(object);\n    }\n  }\n\n  /**\n   * Slices the `collection` from the `start` index up to, but not including,\n   * the `end` index.\n   *\n   * Note: This function is used instead of `Array#slice` to support node lists\n   * in IE < 9 and to ensure dense arrays are returned.\n   *\n   * @private\n   * @param {Array|Object|string} collection The collection to slice.\n   * @param {number} start The start index.\n   * @param {number} end The end index.\n   * @returns {Array} Returns the new array.\n   */\n  function slice(array, start, end) {\n    start || (start = 0);\n    if (typeof end == 'undefined') {\n      end = array ? array.length : 0;\n    }\n    var index = -1,\n      length = end - start || 0,\n      result = Array(length < 0 ? 0 : length);\n\n    while (++index < length) {\n      result[index] = array[start + index];\n    }\n    return result;\n  }\n\n  /*--------------------------------------------------------------------------*/\n\n  /**\n   * Create a new `lodash` function using the given context object.\n   *\n   * @static\n   * @memberOf _\n   * @category Utilities\n   * @param {Object} [context=root] The context object.\n   * @returns {Function} Returns the `lodash` function.\n   */\n  function runInContext(context) {\n    // Avoid issues with some ES3 environments that attempt to use values, named\n    // after built-in constructors like `Object`, for the creation of literals.\n    // ES5 clears this up by stating that literals must use built-in constructors.\n    // See http://es5.github.io/#x11.1.5.\n    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;\n\n    /** Native constructor references */\n    var Array = context.Array,\n      Boolean = context.Boolean,\n      Date = context.Date,\n      Error = context.Error,\n      Function = context.Function,\n      Math = context.Math,\n      Number = context.Number,\n      Object = context.Object,\n      RegExp = context.RegExp,\n      String = context.String,\n      TypeError = context.TypeError;\n\n    /**\n     * Used for `Array` method references.\n     *\n     * Normally `Array.prototype` would suffice, however, using an array literal\n     * avoids issues in Narwhal.\n     */\n    var arrayRef = [];\n\n    /** Used for native method references */\n    var errorProto = Error.prototype,\n      objectProto = Object.prototype,\n      stringProto = String.prototype;\n\n    /** Used to restore the original `_` reference in `noConflict` */\n    var oldDash = context._;\n\n    /** Used to resolve the internal [[Class]] of values */\n    var toString = objectProto.toString;\n\n    /** Used to detect if a method is native */\n    var reNative = RegExp('^' +\n        String(toString)\n          .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n          .replace(/toString| for [^\\]]+/g, '.*?') + '$'\n    );\n\n    /** Native method shortcuts */\n    var ceil = Math.ceil,\n      clearTimeout = context.clearTimeout,\n      floor = Math.floor,\n      fnToString = Function.prototype.toString,\n      getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,\n      hasOwnProperty = objectProto.hasOwnProperty,\n      push = arrayRef.push,\n      propertyIsEnumerable = objectProto.propertyIsEnumerable,\n      setTimeout = context.setTimeout,\n      splice = arrayRef.splice,\n      unshift = arrayRef.unshift;\n\n    /** Used to set meta data on functions */\n    var defineProperty = (function () {\n      // IE 8 only accepts DOM elements\n      try {\n        var o = {},\n          func = isNative(func = Object.defineProperty) && func,\n          result = func(o, o, o) && func;\n      } catch (e) {\n      }\n      return result;\n    }());\n\n    /* Native method shortcuts for methods with the same name as other `lodash` methods */\n    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,\n      nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,\n      nativeIsFinite = context.isFinite,\n      nativeIsNaN = context.isNaN,\n      nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,\n      nativeMax = Math.max,\n      nativeMin = Math.min,\n      nativeParseInt = context.parseInt,\n      nativeRandom = Math.random;\n\n    /** Used to lookup a built-in constructor by [[Class]] */\n    var ctorByClass = {};\n    ctorByClass[arrayClass] = Array;\n    ctorByClass[boolClass] = Boolean;\n    ctorByClass[dateClass] = Date;\n    ctorByClass[funcClass] = Function;\n    ctorByClass[objectClass] = Object;\n    ctorByClass[numberClass] = Number;\n    ctorByClass[regexpClass] = RegExp;\n    ctorByClass[stringClass] = String;\n\n    /** Used to avoid iterating non-enumerable properties in IE < 9 */\n    var nonEnumProps = {};\n    nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };\n    nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true };\n    nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true };\n    nonEnumProps[objectClass] = { 'constructor': true };\n\n    (function () {\n      var length = shadowedProps.length;\n      while (length--) {\n        var key = shadowedProps[length];\n        for (var className in nonEnumProps) {\n          if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) {\n            nonEnumProps[className][key] = false;\n          }\n        }\n      }\n    }());\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates a `lodash` object which wraps the given value to enable intuitive\n     * method chaining.\n     *\n     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:\n     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,\n     * and `unshift`\n     *\n     * Chaining is supported in custom builds as long as the `value` method is\n     * implicitly or explicitly included in the build.\n     *\n     * The chainable wrapper functions are:\n     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,\n     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,\n     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,\n     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,\n     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,\n     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,\n     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,\n     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,\n     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,\n     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,\n     * and `zip`\n     *\n     * The non-chainable wrapper functions are:\n     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,\n     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,\n     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,\n     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,\n     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,\n     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,\n     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,\n     * `template`, `unescape`, `uniqueId`, and `value`\n     *\n     * The wrapper functions `first` and `last` return wrapped values when `n` is\n     * provided, otherwise they return unwrapped values.\n     *\n     * Explicit chaining can be enabled by using the `_.chain` method.\n     *\n     * @name _\n     * @constructor\n     * @category Chaining\n     * @param {*} value The value to wrap in a `lodash` instance.\n     * @returns {Object} Returns a `lodash` instance.\n     * @example\n     *\n     * var wrapped = _([1, 2, 3]);\n     *\n     * // returns an unwrapped value\n     * wrapped.reduce(function(sum, num) {\n     *   return sum + num;\n     * });\n     * // => 6\n     *\n     * // returns a wrapped value\n     * var squares = wrapped.map(function(num) {\n     *   return num * num;\n     * });\n     *\n     * _.isArray(squares);\n     * // => false\n     *\n     * _.isArray(squares.value());\n     * // => true\n     */\n    function lodash(value) {\n      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor\n      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))\n        ? value\n        : new lodashWrapper(value);\n    }\n\n    /**\n     * A fast path for creating `lodash` wrapper objects.\n     *\n     * @private\n     * @param {*} value The value to wrap in a `lodash` instance.\n     * @param {boolean} chainAll A flag to enable chaining for all methods\n     * @returns {Object} Returns a `lodash` instance.\n     */\n    function lodashWrapper(value, chainAll) {\n      this.__chain__ = !!chainAll;\n      this.__wrapped__ = value;\n    }\n\n    // ensure `new lodashWrapper` is an instance of `lodash`\n    lodashWrapper.prototype = lodash.prototype;\n\n    /**\n     * An object used to flag environments features.\n     *\n     * @static\n     * @memberOf _\n     * @type Object\n     */\n    var support = lodash.support = {};\n\n    (function () {\n      var ctor = function () {\n          this.x = 1;\n        },\n        object = { '0': 1, 'length': 1 },\n        props = [];\n\n      ctor.prototype = { 'valueOf': 1, 'y': 1 };\n      for (var key in new ctor) {\n        props.push(key);\n      }\n      for (key in arguments) {\n      }\n\n      /**\n       * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.argsClass = toString.call(arguments) == argsClass;\n\n      /**\n       * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.argsObject = arguments.constructor == Object && !(arguments instanceof Array);\n\n      /**\n       * Detect if `name` or `message` properties of `Error.prototype` are\n       * enumerable by default. (IE < 9, Safari < 5.1)\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name');\n\n      /**\n       * Detect if `prototype` properties are enumerable by default.\n       *\n       * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1\n       * (if the prototype or a property on the prototype has been set)\n       * incorrectly sets a function's `prototype` property [[Enumerable]]\n       * value to `true`.\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.enumPrototypes = propertyIsEnumerable.call(ctor, 'prototype');\n\n      /**\n       * Detect if functions can be decompiled by `Function#toString`\n       * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);\n\n      /**\n       * Detect if `Function#name` is supported (all but IE).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.funcNames = typeof Function.name == 'string';\n\n      /**\n       * Detect if `arguments` object indexes are non-enumerable\n       * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.nonEnumArgs = key != 0;\n\n      /**\n       * Detect if properties shadowing those on `Object.prototype` are non-enumerable.\n       *\n       * In IE < 9 an objects own properties, shadowing non-enumerable ones, are\n       * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.nonEnumShadows = !/valueOf/.test(props);\n\n      /**\n       * Detect if own properties are iterated after inherited properties (all but IE < 9).\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.ownLast = props[0] != 'x';\n\n      /**\n       * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.\n       *\n       * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`\n       * and `splice()` functions that fail to remove the last element, `value[0]`,\n       * of array-like objects even though the `length` property is set to `0`.\n       * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`\n       * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);\n\n      /**\n       * Detect lack of support for accessing string characters by index.\n       *\n       * IE < 8 can't access characters by index and IE 8 can only access\n       * characters by index on string literals.\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';\n\n      /**\n       * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9)\n       * and that the JS engine errors when attempting to coerce an object to\n       * a string without a `toString` function.\n       *\n       * @memberOf _.support\n       * @type boolean\n       */\n      try {\n        support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + ''));\n      } catch (e) {\n        support.nodeClass = true;\n      }\n    }(1));\n\n    /**\n     * By default, the template delimiters used by Lo-Dash are similar to those in\n     * embedded Ruby (ERB). Change the following template settings to use alternative\n     * delimiters.\n     *\n     * @static\n     * @memberOf _\n     * @type Object\n     */\n    lodash.templateSettings = {\n\n      /**\n       * Used to detect `data` property values to be HTML-escaped.\n       *\n       * @memberOf _.templateSettings\n       * @type RegExp\n       */\n      'escape': /<%-([\\s\\S]+?)%>/g,\n\n      /**\n       * Used to detect code to be evaluated.\n       *\n       * @memberOf _.templateSettings\n       * @type RegExp\n       */\n      'evaluate': /<%([\\s\\S]+?)%>/g,\n\n      /**\n       * Used to detect `data` property values to inject.\n       *\n       * @memberOf _.templateSettings\n       * @type RegExp\n       */\n      'interpolate': reInterpolate,\n\n      /**\n       * Used to reference the data object in the template text.\n       *\n       * @memberOf _.templateSettings\n       * @type string\n       */\n      'variable': '',\n\n      /**\n       * Used to import variables into the compiled template.\n       *\n       * @memberOf _.templateSettings\n       * @type Object\n       */\n      'imports': {\n\n        /**\n         * A reference to the `lodash` function.\n         *\n         * @memberOf _.templateSettings.imports\n         * @type Function\n         */\n        '_': lodash\n      }\n    };\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * The template used to create iterator functions.\n     *\n     * @private\n     * @param {Object} data The data object used to populate the text.\n     * @returns {string} Returns the interpolated text.\n     */\n    var iteratorTemplate = function (obj) {\n\n      var __p = 'var index, iterable = ' +\n        (obj.firstArg) +\n        ', result = ' +\n        (obj.init) +\n        ';\\nif (!iterable) return result;\\n' +\n        (obj.top) +\n        ';';\n      if (obj.array) {\n        __p += '\\nvar length = iterable.length; index = -1;\\nif (' +\n          (obj.array) +\n          ') {  ';\n        if (support.unindexedChars) {\n          __p += '\\n  if (isString(iterable)) {\\n    iterable = iterable.split(\\'\\')\\n  }  ';\n        }\n        __p += '\\n  while (++index < length) {\\n    ' +\n          (obj.loop) +\n          ';\\n  }\\n}\\nelse {  ';\n      } else if (support.nonEnumArgs) {\n        __p += '\\n  var length = iterable.length; index = -1;\\n  if (length && isArguments(iterable)) {\\n    while (++index < length) {\\n      index += \\'\\';\\n      ' +\n          (obj.loop) +\n          ';\\n    }\\n  } else {  ';\n      }\n\n      if (support.enumPrototypes) {\n        __p += '\\n  var skipProto = typeof iterable == \\'function\\';\\n  ';\n      }\n\n      if (support.enumErrorProps) {\n        __p += '\\n  var skipErrorProps = iterable === errorProto || iterable instanceof Error;\\n  ';\n      }\n\n      var conditions = [];\n      if (support.enumPrototypes) {\n        conditions.push('!(skipProto && index == \"prototype\")');\n      }\n      if (support.enumErrorProps) {\n        conditions.push('!(skipErrorProps && (index == \"message\" || index == \"name\"))');\n      }\n\n      if (obj.useHas && obj.keys) {\n        __p += '\\n  var ownIndex = -1,\\n      ownProps = objectTypes[typeof iterable] && keys(iterable),\\n      length = ownProps ? ownProps.length : 0;\\n\\n  while (++ownIndex < length) {\\n    index = ownProps[ownIndex];\\n';\n        if (conditions.length) {\n          __p += '    if (' +\n            (conditions.join(' && ')) +\n            ') {\\n  ';\n        }\n        __p +=\n          (obj.loop) +\n          ';    ';\n        if (conditions.length) {\n          __p += '\\n    }';\n        }\n        __p += '\\n  }  ';\n      } else {\n        __p += '\\n  for (index in iterable) {\\n';\n        if (obj.useHas) {\n          conditions.push(\"hasOwnProperty.call(iterable, index)\");\n        }\n        if (conditions.length) {\n          __p += '    if (' +\n            (conditions.join(' && ')) +\n            ') {\\n  ';\n        }\n        __p +=\n          (obj.loop) +\n          ';    ';\n        if (conditions.length) {\n          __p += '\\n    }';\n        }\n        __p += '\\n  }    ';\n        if (support.nonEnumShadows) {\n          __p += '\\n\\n  if (iterable !== objectProto) {\\n    var ctor = iterable.constructor,\\n        isProto = iterable === (ctor && ctor.prototype),\\n        className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\\n        nonEnum = nonEnumProps[className];\\n      ';\n          for (k = 0; k < 7; k++) {\n            __p += '\\n    index = \\'' +\n              (obj.shadowedProps[k]) +\n              '\\';\\n    if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))';\n            if (!obj.useHas) {\n              __p += ' || (!nonEnum[index] && iterable[index] !== objectProto[index])';\n            }\n            __p += ') {\\n      ' +\n              (obj.loop) +\n              ';\\n    }      ';\n          }\n          __p += '\\n  }    ';\n        }\n\n      }\n\n      if (obj.array || support.nonEnumArgs) {\n        __p += '\\n}';\n      }\n      __p +=\n        (obj.bottom) +\n        ';\\nreturn result';\n\n      return __p\n    };\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * The base implementation of `_.bind` that creates the bound function and\n     * sets its meta data.\n     *\n     * @private\n     * @param {Array} bindData The bind data array.\n     * @returns {Function} Returns the new bound function.\n     */\n    function baseBind(bindData) {\n      var func = bindData[0],\n        partialArgs = bindData[2],\n        thisArg = bindData[4];\n\n      function bound() {\n        // `Function#bind` spec\n        // http://es5.github.io/#x15.3.4.5\n        if (partialArgs) {\n          // avoid `arguments` object deoptimizations by using `slice` instead\n          // of `Array.prototype.slice.call` and not assigning `arguments` to a\n          // variable as a ternary expression\n          var args = slice(partialArgs);\n          push.apply(args, arguments);\n        }\n        // mimic the constructor's `return` behavior\n        // http://es5.github.io/#x13.2.2\n        if (this instanceof bound) {\n          // ensure `new bound` is an instance of `func`\n          var thisBinding = baseCreate(func.prototype),\n            result = func.apply(thisBinding, args || arguments);\n          return isObject(result) ? result : thisBinding;\n        }\n        return func.apply(thisArg, args || arguments);\n      }\n\n      setBindData(bound, bindData);\n      return bound;\n    }\n\n    /**\n     * The base implementation of `_.clone` without argument juggling or support\n     * for `thisArg` binding.\n     *\n     * @private\n     * @param {*} value The value to clone.\n     * @param {boolean} [isDeep=false] Specify a deep clone.\n     * @param {Function} [callback] The function to customize cloning values.\n     * @param {Array} [stackA=[]] Tracks traversed source objects.\n     * @param {Array} [stackB=[]] Associates clones with source counterparts.\n     * @returns {*} Returns the cloned value.\n     */\n    function baseClone(value, isDeep, callback, stackA, stackB) {\n      if (callback) {\n        var result = callback(value);\n        if (typeof result != 'undefined') {\n          return result;\n        }\n      }\n      // inspect [[Class]]\n      var isObj = isObject(value);\n      if (isObj) {\n        var className = toString.call(value);\n        if (!cloneableClasses[className] || (!support.nodeClass && isNode(value))) {\n          return value;\n        }\n        var ctor = ctorByClass[className];\n        switch (className) {\n          case boolClass:\n          case dateClass:\n            return new ctor(+value);\n\n          case numberClass:\n          case stringClass:\n            return new ctor(value);\n\n          case regexpClass:\n            result = ctor(value.source, reFlags.exec(value));\n            result.lastIndex = value.lastIndex;\n            return result;\n        }\n      } else {\n        return value;\n      }\n      var isArr = isArray(value);\n      if (isDeep) {\n        // check for circular references and return corresponding clone\n        var initedStack = !stackA;\n        stackA || (stackA = getArray());\n        stackB || (stackB = getArray());\n\n        var length = stackA.length;\n        while (length--) {\n          if (stackA[length] == value) {\n            return stackB[length];\n          }\n        }\n        result = isArr ? ctor(value.length) : {};\n      }\n      else {\n        result = isArr ? slice(value) : assign({}, value);\n      }\n      // add array properties assigned by `RegExp#exec`\n      if (isArr) {\n        if (hasOwnProperty.call(value, 'index')) {\n          result.index = value.index;\n        }\n        if (hasOwnProperty.call(value, 'input')) {\n          result.input = value.input;\n        }\n      }\n      // exit for shallow clone\n      if (!isDeep) {\n        return result;\n      }\n      // add the source value to the stack of traversed objects\n      // and associate it with its clone\n      stackA.push(value);\n      stackB.push(result);\n\n      // recursively populate clone (susceptible to call stack limits)\n      (isArr ? baseEach : forOwn)(value, function (objValue, key) {\n        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);\n      });\n\n      if (initedStack) {\n        releaseArray(stackA);\n        releaseArray(stackB);\n      }\n      return result;\n    }\n\n    /**\n     * The base implementation of `_.create` without support for assigning\n     * properties to the created object.\n     *\n     * @private\n     * @param {Object} prototype The object to inherit from.\n     * @returns {Object} Returns the new object.\n     */\n    function baseCreate(prototype, properties) {\n      return isObject(prototype) ? nativeCreate(prototype) : {};\n    }\n\n    // fallback for browsers without `Object.create`\n    if (!nativeCreate) {\n      baseCreate = (function () {\n        function Object() {\n        }\n\n        return function (prototype) {\n          if (isObject(prototype)) {\n            Object.prototype = prototype;\n            var result = new Object;\n            Object.prototype = null;\n          }\n          return result || context.Object();\n        };\n      }());\n    }\n\n    /**\n     * The base implementation of `_.createCallback` without support for creating\n     * \"_.pluck\" or \"_.where\" style callbacks.\n     *\n     * @private\n     * @param {*} [func=identity] The value to convert to a callback.\n     * @param {*} [thisArg] The `this` binding of the created callback.\n     * @param {number} [argCount] The number of arguments the callback accepts.\n     * @returns {Function} Returns a callback function.\n     */\n    function baseCreateCallback(func, thisArg, argCount) {\n      if (typeof func != 'function') {\n        return identity;\n      }\n      // exit early for no `thisArg` or already bound by `Function#bind`\n      if (typeof thisArg == 'undefined' || !('prototype' in func)) {\n        return func;\n      }\n      var bindData = func.__bindData__;\n      if (typeof bindData == 'undefined') {\n        if (support.funcNames) {\n          bindData = !func.name;\n        }\n        bindData = bindData || !support.funcDecomp;\n        if (!bindData) {\n          var source = fnToString.call(func);\n          if (!support.funcNames) {\n            bindData = !reFuncName.test(source);\n          }\n          if (!bindData) {\n            // checks if `func` references the `this` keyword and stores the result\n            bindData = reThis.test(source);\n            setBindData(func, bindData);\n          }\n        }\n      }\n      // exit early if there are no `this` references or `func` is bound\n      if (bindData === false || (bindData !== true && bindData[1] & 1)) {\n        return func;\n      }\n      switch (argCount) {\n        case 1:\n          return function (value) {\n            return func.call(thisArg, value);\n          };\n        case 2:\n          return function (a, b) {\n            return func.call(thisArg, a, b);\n          };\n        case 3:\n          return function (value, index, collection) {\n            return func.call(thisArg, value, index, collection);\n          };\n        case 4:\n          return function (accumulator, value, index, collection) {\n            return func.call(thisArg, accumulator, value, index, collection);\n          };\n      }\n      return bind(func, thisArg);\n    }\n\n    /**\n     * The base implementation of `createWrapper` that creates the wrapper and\n     * sets its meta data.\n     *\n     * @private\n     * @param {Array} bindData The bind data array.\n     * @returns {Function} Returns the new function.\n     */\n    function baseCreateWrapper(bindData) {\n      var func = bindData[0],\n        bitmask = bindData[1],\n        partialArgs = bindData[2],\n        partialRightArgs = bindData[3],\n        thisArg = bindData[4],\n        arity = bindData[5];\n\n      var isBind = bitmask & 1,\n        isBindKey = bitmask & 2,\n        isCurry = bitmask & 4,\n        isCurryBound = bitmask & 8,\n        key = func;\n\n      function bound() {\n        var thisBinding = isBind ? thisArg : this;\n        if (partialArgs) {\n          var args = slice(partialArgs);\n          push.apply(args, arguments);\n        }\n        if (partialRightArgs || isCurry) {\n          args || (args = slice(arguments));\n          if (partialRightArgs) {\n            push.apply(args, partialRightArgs);\n          }\n          if (isCurry && args.length < arity) {\n            bitmask |= 16 & ~32;\n            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);\n          }\n        }\n        args || (args = arguments);\n        if (isBindKey) {\n          func = thisBinding[key];\n        }\n        if (this instanceof bound) {\n          thisBinding = baseCreate(func.prototype);\n          var result = func.apply(thisBinding, args);\n          return isObject(result) ? result : thisBinding;\n        }\n        return func.apply(thisBinding, args);\n      }\n\n      setBindData(bound, bindData);\n      return bound;\n    }\n\n    /**\n     * The base implementation of `_.difference` that accepts a single array\n     * of values to exclude.\n     *\n     * @private\n     * @param {Array} array The array to process.\n     * @param {Array} [values] The array of values to exclude.\n     * @returns {Array} Returns a new array of filtered values.\n     */\n    function baseDifference(array, values) {\n      var index = -1,\n        indexOf = getIndexOf(),\n        length = array ? array.length : 0,\n        isLarge = length >= largeArraySize && indexOf === baseIndexOf,\n        result = [];\n\n      if (isLarge) {\n        var cache = createCache(values);\n        if (cache) {\n          indexOf = cacheIndexOf;\n          values = cache;\n        } else {\n          isLarge = false;\n        }\n      }\n      while (++index < length) {\n        var value = array[index];\n        if (indexOf(values, value) < 0) {\n          result.push(value);\n        }\n      }\n      if (isLarge) {\n        releaseObject(values);\n      }\n      return result;\n    }\n\n    /**\n     * The base implementation of `_.flatten` without support for callback\n     * shorthands or `thisArg` binding.\n     *\n     * @private\n     * @param {Array} array The array to flatten.\n     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.\n     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.\n     * @param {number} [fromIndex=0] The index to start from.\n     * @returns {Array} Returns a new flattened array.\n     */\n    function baseFlatten(array, isShallow, isStrict, fromIndex) {\n      var index = (fromIndex || 0) - 1,\n        length = array ? array.length : 0,\n        result = [];\n\n      while (++index < length) {\n        var value = array[index];\n\n        if (value && typeof value == 'object' && typeof value.length == 'number'\n          && (isArray(value) || isArguments(value))) {\n          // recursively flatten arrays (susceptible to call stack limits)\n          if (!isShallow) {\n            value = baseFlatten(value, isShallow, isStrict);\n          }\n          var valIndex = -1,\n            valLength = value.length,\n            resIndex = result.length;\n\n          result.length += valLength;\n          while (++valIndex < valLength) {\n            result[resIndex++] = value[valIndex];\n          }\n        } else if (!isStrict) {\n          result.push(value);\n        }\n      }\n      return result;\n    }\n\n    /**\n     * The base implementation of `_.isEqual`, without support for `thisArg` binding,\n     * that allows partial \"_.where\" style comparisons.\n     *\n     * @private\n     * @param {*} a The value to compare.\n     * @param {*} b The other value to compare.\n     * @param {Function} [callback] The function to customize comparing values.\n     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.\n     * @param {Array} [stackA=[]] Tracks traversed `a` objects.\n     * @param {Array} [stackB=[]] Tracks traversed `b` objects.\n     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n     */\n    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {\n      // used to indicate that when comparing objects, `a` has at least the properties of `b`\n      if (callback) {\n        var result = callback(a, b);\n        if (typeof result != 'undefined') {\n          return !!result;\n        }\n      }\n      // exit early for identical values\n      if (a === b) {\n        // treat `+0` vs. `-0` as not equal\n        return a !== 0 || (1 / a == 1 / b);\n      }\n      var type = typeof a,\n        otherType = typeof b;\n\n      // exit early for unlike primitive values\n      if (a === a && !(a && objectTypes[type]) && !(b && objectTypes[otherType])) {\n        return false;\n      }\n      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior\n      // http://es5.github.io/#x15.3.4.4\n      if (a == null || b == null) {\n        return a === b;\n      }\n      // compare [[Class]] names\n      var className = toString.call(a),\n        otherClass = toString.call(b);\n\n      if (className == argsClass) {\n        className = objectClass;\n      }\n      if (otherClass == argsClass) {\n        otherClass = objectClass;\n      }\n      if (className != otherClass) {\n        return false;\n      }\n      switch (className) {\n        case boolClass:\n        case dateClass:\n          // coerce dates and booleans to numbers, dates to milliseconds and booleans\n          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal\n          return +a == +b;\n\n        case numberClass:\n          // treat `NaN` vs. `NaN` as equal\n          return (a != +a)\n            ? b != +b\n            // but treat `+0` vs. `-0` as not equal\n            : (a == 0 ? (1 / a == 1 / b) : a == +b);\n\n        case regexpClass:\n        case stringClass:\n          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)\n          // treat string primitives and their corresponding object instances as equal\n          return a == String(b);\n      }\n      var isArr = className == arrayClass;\n      if (!isArr) {\n        // unwrap any `lodash` wrapped values\n        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),\n          bWrapped = hasOwnProperty.call(b, '__wrapped__');\n\n        if (aWrapped || bWrapped) {\n          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);\n        }\n        // exit for functions and DOM nodes\n        if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) {\n          return false;\n        }\n        // in older versions of Opera, `arguments` objects have `Array` constructors\n        var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor,\n          ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor;\n\n        // non `Object` object instances with different constructors are not equal\n        if (ctorA != ctorB && !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&\n          ('constructor' in a && 'constructor' in b)\n          ) {\n          return false;\n        }\n      }\n      // assume cyclic structures are equal\n      // the algorithm for detecting cyclic structures is adapted from ES 5.1\n      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)\n      var initedStack = !stackA;\n      stackA || (stackA = getArray());\n      stackB || (stackB = getArray());\n\n      var length = stackA.length;\n      while (length--) {\n        if (stackA[length] == a) {\n          return stackB[length] == b;\n        }\n      }\n      var size = 0;\n      result = true;\n\n      // add `a` and `b` to the stack of traversed objects\n      stackA.push(a);\n      stackB.push(b);\n\n      // recursively compare objects and arrays (susceptible to call stack limits)\n      if (isArr) {\n        // compare lengths to determine if a deep comparison is necessary\n        length = a.length;\n        size = b.length;\n        result = size == length;\n\n        if (result || isWhere) {\n          // deep compare the contents, ignoring non-numeric properties\n          while (size--) {\n            var index = length,\n              value = b[size];\n\n            if (isWhere) {\n              while (index--) {\n                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {\n                  break;\n                }\n              }\n            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {\n              break;\n            }\n          }\n        }\n      }\n      else {\n        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`\n        // which, in this case, is more costly\n        forIn(b, function (value, key, b) {\n          if (hasOwnProperty.call(b, key)) {\n            // count the number of properties.\n            size++;\n            // deep compare each property value.\n            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));\n          }\n        });\n\n        if (result && !isWhere) {\n          // ensure both objects have the same number of properties\n          forIn(a, function (value, key, a) {\n            if (hasOwnProperty.call(a, key)) {\n              // `size` will be `-1` if `a` has more properties than `b`\n              return (result = --size > -1);\n            }\n          });\n        }\n      }\n      stackA.pop();\n      stackB.pop();\n\n      if (initedStack) {\n        releaseArray(stackA);\n        releaseArray(stackB);\n      }\n      return result;\n    }\n\n    /**\n     * The base implementation of `_.merge` without argument juggling or support\n     * for `thisArg` binding.\n     *\n     * @private\n     * @param {Object} object The destination object.\n     * @param {Object} source The source object.\n     * @param {Function} [callback] The function to customize merging properties.\n     * @param {Array} [stackA=[]] Tracks traversed source objects.\n     * @param {Array} [stackB=[]] Associates values with source counterparts.\n     */\n    function baseMerge(object, source, callback, stackA, stackB) {\n      (isArray(source) ? forEach : forOwn)(source, function (source, key) {\n        var found,\n          isArr,\n          result = source,\n          value = object[key];\n\n        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {\n          // avoid merging previously merged cyclic sources\n          var stackLength = stackA.length;\n          while (stackLength--) {\n            if ((found = stackA[stackLength] == source)) {\n              value = stackB[stackLength];\n              break;\n            }\n          }\n          if (!found) {\n            var isShallow;\n            if (callback) {\n              result = callback(value, source);\n              if ((isShallow = typeof result != 'undefined')) {\n                value = result;\n              }\n            }\n            if (!isShallow) {\n              value = isArr\n                ? (isArray(value) ? value : [])\n                : (isPlainObject(value) ? value : {});\n            }\n            // add `source` and associated `value` to the stack of traversed objects\n            stackA.push(source);\n            stackB.push(value);\n\n            // recursively merge objects and arrays (susceptible to call stack limits)\n            if (!isShallow) {\n              baseMerge(value, source, callback, stackA, stackB);\n            }\n          }\n        }\n        else {\n          if (callback) {\n            result = callback(value, source);\n            if (typeof result == 'undefined') {\n              result = source;\n            }\n          }\n          if (typeof result != 'undefined') {\n            value = result;\n          }\n        }\n        object[key] = value;\n      });\n    }\n\n    /**\n     * The base implementation of `_.random` without argument juggling or support\n     * for returning floating-point numbers.\n     *\n     * @private\n     * @param {number} min The minimum possible value.\n     * @param {number} max The maximum possible value.\n     * @returns {number} Returns a random number.\n     */\n    function baseRandom(min, max) {\n      return min + floor(nativeRandom() * (max - min + 1));\n    }\n\n    /**\n     * The base implementation of `_.uniq` without support for callback shorthands\n     * or `thisArg` binding.\n     *\n     * @private\n     * @param {Array} array The array to process.\n     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.\n     * @param {Function} [callback] The function called per iteration.\n     * @returns {Array} Returns a duplicate-value-free array.\n     */\n    function baseUniq(array, isSorted, callback) {\n      var index = -1,\n        indexOf = getIndexOf(),\n        length = array ? array.length : 0,\n        result = [];\n\n      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,\n        seen = (callback || isLarge) ? getArray() : result;\n\n      if (isLarge) {\n        var cache = createCache(seen);\n        indexOf = cacheIndexOf;\n        seen = cache;\n      }\n      while (++index < length) {\n        var value = array[index],\n          computed = callback ? callback(value, index, array) : value;\n\n        if (isSorted\n          ? !index || seen[seen.length - 1] !== computed\n          : indexOf(seen, computed) < 0\n          ) {\n          if (callback || isLarge) {\n            seen.push(computed);\n          }\n          result.push(value);\n        }\n      }\n      if (isLarge) {\n        releaseArray(seen.array);\n        releaseObject(seen);\n      } else if (callback) {\n        releaseArray(seen);\n      }\n      return result;\n    }\n\n    /**\n     * Creates a function that aggregates a collection, creating an object composed\n     * of keys generated from the results of running each element of the collection\n     * through a callback. The given `setter` function sets the keys and values\n     * of the composed object.\n     *\n     * @private\n     * @param {Function} setter The setter function.\n     * @returns {Function} Returns the new aggregator function.\n     */\n    function createAggregator(setter) {\n      return function (collection, callback, thisArg) {\n        var result = {};\n        callback = lodash.createCallback(callback, thisArg, 3);\n\n        if (isArray(collection)) {\n          var index = -1,\n            length = collection.length;\n\n          while (++index < length) {\n            var value = collection[index];\n            setter(result, value, callback(value, index, collection), collection);\n          }\n        } else {\n          baseEach(collection, function (value, key, collection) {\n            setter(result, value, callback(value, key, collection), collection);\n          });\n        }\n        return result;\n      };\n    }\n\n    /**\n     * Creates a function that, when called, either curries or invokes `func`\n     * with an optional `this` binding and partially applied arguments.\n     *\n     * @private\n     * @param {Function|string} func The function or method name to reference.\n     * @param {number} bitmask The bitmask of method flags to compose.\n     *  The bitmask may be composed of the following flags:\n     *  1 - `_.bind`\n     *  2 - `_.bindKey`\n     *  4 - `_.curry`\n     *  8 - `_.curry` (bound)\n     *  16 - `_.partial`\n     *  32 - `_.partialRight`\n     * @param {Array} [partialArgs] An array of arguments to prepend to those\n     *  provided to the new function.\n     * @param {Array} [partialRightArgs] An array of arguments to append to those\n     *  provided to the new function.\n     * @param {*} [thisArg] The `this` binding of `func`.\n     * @param {number} [arity] The arity of `func`.\n     * @returns {Function} Returns the new function.\n     */\n    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {\n      var isBind = bitmask & 1,\n        isBindKey = bitmask & 2,\n        isCurry = bitmask & 4,\n        isCurryBound = bitmask & 8,\n        isPartial = bitmask & 16,\n        isPartialRight = bitmask & 32;\n\n      if (!isBindKey && !isFunction(func)) {\n        throw new TypeError;\n      }\n      if (isPartial && !partialArgs.length) {\n        bitmask &= ~16;\n        isPartial = partialArgs = false;\n      }\n      if (isPartialRight && !partialRightArgs.length) {\n        bitmask &= ~32;\n        isPartialRight = partialRightArgs = false;\n      }\n      var bindData = func && func.__bindData__;\n      if (bindData && bindData !== true) {\n        // clone `bindData`\n        bindData = slice(bindData);\n        if (bindData[2]) {\n          bindData[2] = slice(bindData[2]);\n        }\n        if (bindData[3]) {\n          bindData[3] = slice(bindData[3]);\n        }\n        // set `thisBinding` is not previously bound\n        if (isBind && !(bindData[1] & 1)) {\n          bindData[4] = thisArg;\n        }\n        // set if previously bound but not currently (subsequent curried functions)\n        if (!isBind && bindData[1] & 1) {\n          bitmask |= 8;\n        }\n        // set curried arity if not yet set\n        if (isCurry && !(bindData[1] & 4)) {\n          bindData[5] = arity;\n        }\n        // append partial left arguments\n        if (isPartial) {\n          push.apply(bindData[2] || (bindData[2] = []), partialArgs);\n        }\n        // append partial right arguments\n        if (isPartialRight) {\n          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);\n        }\n        // merge flags\n        bindData[1] |= bitmask;\n        return createWrapper.apply(null, bindData);\n      }\n      // fast path for `_.bind`\n      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;\n      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);\n    }\n\n    /**\n     * Creates compiled iteration functions.\n     *\n     * @private\n     * @param {...Object} [options] The compile options object(s).\n     * @param {string} [options.array] Code to determine if the iterable is an array or array-like.\n     * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop.\n     * @param {Function} [options.keys] A reference to `_.keys` for use in own property iteration.\n     * @param {string} [options.args] A comma separated string of iteration function arguments.\n     * @param {string} [options.top] Code to execute before the iteration branches.\n     * @param {string} [options.loop] Code to execute in the object loop.\n     * @param {string} [options.bottom] Code to execute after the iteration branches.\n     * @returns {Function} Returns the compiled function.\n     */\n    function createIterator() {\n      // data properties\n      iteratorData.shadowedProps = shadowedProps;\n\n      // iterator options\n      iteratorData.array = iteratorData.bottom = iteratorData.loop = iteratorData.top = '';\n      iteratorData.init = 'iterable';\n      iteratorData.useHas = true;\n\n      // merge options into a template data object\n      for (var object, index = 0; object = arguments[index]; index++) {\n        for (var key in object) {\n          iteratorData[key] = object[key];\n        }\n      }\n      var args = iteratorData.args;\n      iteratorData.firstArg = /^[^,]+/.exec(args)[0];\n\n      // create the function factory\n      var factory = Function(\n          'baseCreateCallback, errorClass, errorProto, hasOwnProperty, ' +\n          'indicatorObject, isArguments, isArray, isString, keys, objectProto, ' +\n          'objectTypes, nonEnumProps, stringClass, stringProto, toString',\n          'return function(' + args + ') {\\n' + iteratorTemplate(iteratorData) + '\\n}'\n      );\n\n      // return the compiled function\n      return factory(\n        baseCreateCallback, errorClass, errorProto, hasOwnProperty,\n        indicatorObject, isArguments, isArray, isString, iteratorData.keys, objectProto,\n        objectTypes, nonEnumProps, stringClass, stringProto, toString\n      );\n    }\n\n    /**\n     * Used by `escape` to convert characters to HTML entities.\n     *\n     * @private\n     * @param {string} match The matched character to escape.\n     * @returns {string} Returns the escaped character.\n     */\n    function escapeHtmlChar(match) {\n      return htmlEscapes[match];\n    }\n\n    /**\n     * Gets the appropriate \"indexOf\" function. If the `_.indexOf` method is\n     * customized, this method returns the custom method, otherwise it returns\n     * the `baseIndexOf` function.\n     *\n     * @private\n     * @returns {Function} Returns the \"indexOf\" function.\n     */\n    function getIndexOf() {\n      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;\n      return result;\n    }\n\n    /**\n     * Checks if `value` is a native function.\n     *\n     * @private\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.\n     */\n    function isNative(value) {\n      return typeof value == 'function' && reNative.test(value);\n    }\n\n    /**\n     * Sets `this` binding data on a given function.\n     *\n     * @private\n     * @param {Function} func The function to set data on.\n     * @param {Array} value The data array to set.\n     */\n    var setBindData = !defineProperty ? noop : function (func, value) {\n      descriptor.value = value;\n      defineProperty(func, '__bindData__', descriptor);\n    };\n\n    /**\n     * A fallback implementation of `isPlainObject` which checks if a given value\n     * is an object created by the `Object` constructor, assuming objects created\n     * by the `Object` constructor have no inherited enumerable properties and that\n     * there are no `Object.prototype` extensions.\n     *\n     * @private\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.\n     */\n    function shimIsPlainObject(value) {\n      var ctor,\n        result;\n\n      // avoid non Object objects, `arguments` objects, and DOM elements\n      if (!(value && toString.call(value) == objectClass) ||\n        (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor)) ||\n        (!support.argsClass && isArguments(value)) ||\n        (!support.nodeClass && isNode(value))) {\n        return false;\n      }\n      // IE < 9 iterates inherited properties before own properties. If the first\n      // iterated property is an object's own property then there are no inherited\n      // enumerable properties.\n      if (support.ownLast) {\n        forIn(value, function (value, key, object) {\n          result = hasOwnProperty.call(object, key);\n          return false;\n        });\n        return result !== false;\n      }\n      // In most environments an object's own properties are iterated before\n      // its inherited properties. If the last iterated property is an object's\n      // own property then there are no inherited enumerable properties.\n      forIn(value, function (value, key) {\n        result = key;\n      });\n      return typeof result == 'undefined' || hasOwnProperty.call(value, result);\n    }\n\n    /**\n     * Used by `unescape` to convert HTML entities to characters.\n     *\n     * @private\n     * @param {string} match The matched character to unescape.\n     * @returns {string} Returns the unescaped character.\n     */\n    function unescapeHtmlChar(match) {\n      return htmlUnescapes[match];\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Checks if `value` is an `arguments` object.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.\n     * @example\n     *\n     * (function() { return _.isArguments(arguments); })(1, 2, 3);\n     * // => true\n     *\n     * _.isArguments([1, 2, 3]);\n     * // => false\n     */\n    function isArguments(value) {\n      return value && typeof value == 'object' && typeof value.length == 'number' &&\n        toString.call(value) == argsClass || false;\n    }\n\n    // fallback for browsers that can't detect `arguments` objects by [[Class]]\n    if (!support.argsClass) {\n      isArguments = function (value) {\n        return value && typeof value == 'object' && typeof value.length == 'number' &&\n          hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;\n      };\n    }\n\n    /**\n     * Checks if `value` is an array.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.\n     * @example\n     *\n     * (function() { return _.isArray(arguments); })();\n     * // => false\n     *\n     * _.isArray([1, 2, 3]);\n     * // => true\n     */\n    var isArray = nativeIsArray || function (value) {\n      return value && typeof value == 'object' && typeof value.length == 'number' &&\n        toString.call(value) == arrayClass || false;\n    };\n\n    /**\n     * A fallback implementation of `Object.keys` which produces an array of the\n     * given object's own enumerable property names.\n     *\n     * @private\n     * @type Function\n     * @param {Object} object The object to inspect.\n     * @returns {Array} Returns an array of property names.\n     */\n    var shimKeys = createIterator({\n      'args': 'object',\n      'init': '[]',\n      'top': 'if (!(objectTypes[typeof object])) return result',\n      'loop': 'result.push(index)'\n    });\n\n    /**\n     * Creates an array composed of the own enumerable property names of an object.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to inspect.\n     * @returns {Array} Returns an array of property names.\n     * @example\n     *\n     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });\n     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)\n     */\n    var keys = !nativeKeys ? shimKeys : function (object) {\n      if (!isObject(object)) {\n        return [];\n      }\n      if ((support.enumPrototypes && typeof object == 'function') ||\n        (support.nonEnumArgs && object.length && isArguments(object))) {\n        return shimKeys(object);\n      }\n      return nativeKeys(object);\n    };\n\n    /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */\n    var eachIteratorOptions = {\n      'args': 'collection, callback, thisArg',\n      'top': \"callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3)\",\n      'array': \"typeof length == 'number'\",\n      'keys': keys,\n      'loop': 'if (callback(iterable[index], index, collection) === false) return result'\n    };\n\n    /** Reusable iterator options for `assign` and `defaults` */\n    var defaultsIteratorOptions = {\n      'args': 'object, source, guard',\n      'top': 'var args = arguments,\\n' +\n        '    argsIndex = 0,\\n' +\n        \"    argsLength = typeof guard == 'number' ? 2 : args.length;\\n\" +\n        'while (++argsIndex < argsLength) {\\n' +\n        '  iterable = args[argsIndex];\\n' +\n        '  if (iterable && objectTypes[typeof iterable]) {',\n      'keys': keys,\n      'loop': \"if (typeof result[index] == 'undefined') result[index] = iterable[index]\",\n      'bottom': '  }\\n}'\n    };\n\n    /** Reusable iterator options for `forIn` and `forOwn` */\n    var forOwnIteratorOptions = {\n      'top': 'if (!objectTypes[typeof iterable]) return result;\\n' + eachIteratorOptions.top,\n      'array': false\n    };\n\n    /**\n     * Used to convert characters to HTML entities:\n     *\n     * Though the `>` character is escaped for symmetry, characters like `>` and `/`\n     * don't require escaping in HTML and have no special meaning unless they're part\n     * of a tag or an unquoted attribute value.\n     * http://mathiasbynens.be/notes/ambiguous-ampersands (under \"semi-related fun fact\")\n     */\n    var htmlEscapes = {\n      '&': '&amp;',\n      '<': '&lt;',\n      '>': '&gt;',\n      '\"': '&quot;',\n      \"'\": '&#39;'\n    };\n\n    /** Used to convert HTML entities to characters */\n    var htmlUnescapes = invert(htmlEscapes);\n\n    /** Used to match HTML entities and HTML characters */\n    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),\n      reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');\n\n    /**\n     * A function compiled to iterate `arguments` objects, arrays, objects, and\n     * strings consistenly across environments, executing the callback for each\n     * element in the collection. The callback is bound to `thisArg` and invoked\n     * with three arguments; (value, index|key, collection). Callbacks may exit\n     * iteration early by explicitly returning `false`.\n     *\n     * @private\n     * @type Function\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array|Object|string} Returns `collection`.\n     */\n    var baseEach = createIterator(eachIteratorOptions);\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Assigns own enumerable properties of source object(s) to the destination\n     * object. Subsequent sources will overwrite property assignments of previous\n     * sources. If a callback is provided it will be executed to produce the\n     * assigned values. The callback is bound to `thisArg` and invoked with two\n     * arguments; (objectValue, sourceValue).\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @alias extend\n     * @category Objects\n     * @param {Object} object The destination object.\n     * @param {...Object} [source] The source objects.\n     * @param {Function} [callback] The function to customize assigning values.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns the destination object.\n     * @example\n     *\n     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });\n     * // => { 'name': 'fred', 'employer': 'slate' }\n     *\n     * var defaults = _.partialRight(_.assign, function(a, b) {\n     *   return typeof a == 'undefined' ? b : a;\n     * });\n     *\n     * var object = { 'name': 'barney' };\n     * defaults(object, { 'name': 'fred', 'employer': 'slate' });\n     * // => { 'name': 'barney', 'employer': 'slate' }\n     */\n    var assign = createIterator(defaultsIteratorOptions, {\n      'top': defaultsIteratorOptions.top.replace(';',\n          ';\\n' +\n          \"if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\\n\" +\n          '  var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);\\n' +\n          \"} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\\n\" +\n          '  callback = args[--argsLength];\\n' +\n          '}'\n      ),\n      'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]'\n    });\n\n    /**\n     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also\n     * be cloned, otherwise they will be assigned by reference. If a callback\n     * is provided it will be executed to produce the cloned values. If the\n     * callback returns `undefined` cloning will be handled by the method instead.\n     * The callback is bound to `thisArg` and invoked with one argument; (value).\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to clone.\n     * @param {boolean} [isDeep=false] Specify a deep clone.\n     * @param {Function} [callback] The function to customize cloning values.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the cloned value.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * var shallow = _.clone(characters);\n     * shallow[0] === characters[0];\n     * // => true\n     *\n     * var deep = _.clone(characters, true);\n     * deep[0] === characters[0];\n     * // => false\n     *\n     * _.mixin({\n     *   'clone': _.partialRight(_.clone, function(value) {\n     *     return _.isElement(value) ? value.cloneNode(false) : undefined;\n     *   })\n     * });\n     *\n     * var clone = _.clone(document.body);\n     * clone.childNodes.length;\n     * // => 0\n     */\n    function clone(value, isDeep, callback, thisArg) {\n      // allows working with \"Collections\" methods without using their `index`\n      // and `collection` arguments for `isDeep` and `callback`\n      if (typeof isDeep != 'boolean' && isDeep != null) {\n        thisArg = callback;\n        callback = isDeep;\n        isDeep = false;\n      }\n      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));\n    }\n\n    /**\n     * Creates a deep clone of `value`. If a callback is provided it will be\n     * executed to produce the cloned values. If the callback returns `undefined`\n     * cloning will be handled by the method instead. The callback is bound to\n     * `thisArg` and invoked with one argument; (value).\n     *\n     * Note: This method is loosely based on the structured clone algorithm. Functions\n     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and\n     * objects created by constructors other than `Object` are cloned to plain `Object` objects.\n     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to deep clone.\n     * @param {Function} [callback] The function to customize cloning values.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the deep cloned value.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * var deep = _.cloneDeep(characters);\n     * deep[0] === characters[0];\n     * // => false\n     *\n     * var view = {\n     *   'label': 'docs',\n     *   'node': element\n     * };\n     *\n     * var clone = _.cloneDeep(view, function(value) {\n     *   return _.isElement(value) ? value.cloneNode(true) : undefined;\n     * });\n     *\n     * clone.node == view.node;\n     * // => false\n     */\n    function cloneDeep(value, callback, thisArg) {\n      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));\n    }\n\n    /**\n     * Creates an object that inherits from the given `prototype` object. If a\n     * `properties` object is provided its own enumerable properties are assigned\n     * to the created object.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} prototype The object to inherit from.\n     * @param {Object} [properties] The properties to assign to the object.\n     * @returns {Object} Returns the new object.\n     * @example\n     *\n     * function Shape() {\n     *   this.x = 0;\n     *   this.y = 0;\n     * }\n     *\n     * function Circle() {\n     *   Shape.call(this);\n     * }\n     *\n     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });\n     *\n     * var circle = new Circle;\n     * circle instanceof Circle;\n     * // => true\n     *\n     * circle instanceof Shape;\n     * // => true\n     */\n    function create(prototype, properties) {\n      var result = baseCreate(prototype);\n      return properties ? assign(result, properties) : result;\n    }\n\n    /**\n     * Assigns own enumerable properties of source object(s) to the destination\n     * object for all destination properties that resolve to `undefined`. Once a\n     * property is set, additional defaults of the same property will be ignored.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Objects\n     * @param {Object} object The destination object.\n     * @param {...Object} [source] The source objects.\n     * @param- {Object} [guard] Allows working with `_.reduce` without using its\n     *  `key` and `object` arguments as sources.\n     * @returns {Object} Returns the destination object.\n     * @example\n     *\n     * var object = { 'name': 'barney' };\n     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });\n     * // => { 'name': 'barney', 'employer': 'slate' }\n     */\n    var defaults = createIterator(defaultsIteratorOptions);\n\n    /**\n     * This method is like `_.findIndex` except that it returns the key of the\n     * first element that passes the callback check, instead of the element itself.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to search.\n     * @param {Function|Object|string} [callback=identity] The function called per\n     *  iteration. If a property name or object is provided it will be used to\n     *  create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {string|undefined} Returns the key of the found element, else `undefined`.\n     * @example\n     *\n     * var characters = {\n     *   'barney': {  'age': 36, 'blocked': false },\n     *   'fred': {    'age': 40, 'blocked': true },\n     *   'pebbles': { 'age': 1,  'blocked': false }\n     * };\n     *\n     * _.findKey(characters, function(chr) {\n     *   return chr.age < 40;\n     * });\n     * // => 'barney' (property order is not guaranteed across environments)\n     *\n     * // using \"_.where\" callback shorthand\n     * _.findKey(characters, { 'age': 1 });\n     * // => 'pebbles'\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.findKey(characters, 'blocked');\n     * // => 'fred'\n     */\n    function findKey(object, callback, thisArg) {\n      var result;\n      callback = lodash.createCallback(callback, thisArg, 3);\n      forOwn(object, function (value, key, object) {\n        if (callback(value, key, object)) {\n          result = key;\n          return false;\n        }\n      });\n      return result;\n    }\n\n    /**\n     * This method is like `_.findKey` except that it iterates over elements\n     * of a `collection` in the opposite order.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to search.\n     * @param {Function|Object|string} [callback=identity] The function called per\n     *  iteration. If a property name or object is provided it will be used to\n     *  create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {string|undefined} Returns the key of the found element, else `undefined`.\n     * @example\n     *\n     * var characters = {\n     *   'barney': {  'age': 36, 'blocked': true },\n     *   'fred': {    'age': 40, 'blocked': false },\n     *   'pebbles': { 'age': 1,  'blocked': true }\n     * };\n     *\n     * _.findLastKey(characters, function(chr) {\n     *   return chr.age < 40;\n     * });\n     * // => returns `pebbles`, assuming `_.findKey` returns `barney`\n     *\n     * // using \"_.where\" callback shorthand\n     * _.findLastKey(characters, { 'age': 40 });\n     * // => 'fred'\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.findLastKey(characters, 'blocked');\n     * // => 'pebbles'\n     */\n    function findLastKey(object, callback, thisArg) {\n      var result;\n      callback = lodash.createCallback(callback, thisArg, 3);\n      forOwnRight(object, function (value, key, object) {\n        if (callback(value, key, object)) {\n          result = key;\n          return false;\n        }\n      });\n      return result;\n    }\n\n    /**\n     * Iterates over own and inherited enumerable properties of an object,\n     * executing the callback for each property. The callback is bound to `thisArg`\n     * and invoked with three arguments; (value, key, object). Callbacks may exit\n     * iteration early by explicitly returning `false`.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Objects\n     * @param {Object} object The object to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns `object`.\n     * @example\n     *\n     * function Shape() {\n     *   this.x = 0;\n     *   this.y = 0;\n     * }\n     *\n     * Shape.prototype.move = function(x, y) {\n     *   this.x += x;\n     *   this.y += y;\n     * };\n     *\n     * _.forIn(new Shape, function(value, key) {\n     *   console.log(key);\n     * });\n     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)\n     */\n    var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {\n      'useHas': false\n    });\n\n    /**\n     * This method is like `_.forIn` except that it iterates over elements\n     * of a `collection` in the opposite order.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns `object`.\n     * @example\n     *\n     * function Shape() {\n     *   this.x = 0;\n     *   this.y = 0;\n     * }\n     *\n     * Shape.prototype.move = function(x, y) {\n     *   this.x += x;\n     *   this.y += y;\n     * };\n     *\n     * _.forInRight(new Shape, function(value, key) {\n     *   console.log(key);\n     * });\n     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'\n     */\n    function forInRight(object, callback, thisArg) {\n      var pairs = [];\n\n      forIn(object, function (value, key) {\n        pairs.push(key, value);\n      });\n\n      var length = pairs.length;\n      callback = baseCreateCallback(callback, thisArg, 3);\n      while (length--) {\n        if (callback(pairs[length--], pairs[length], object) === false) {\n          break;\n        }\n      }\n      return object;\n    }\n\n    /**\n     * Iterates over own enumerable properties of an object, executing the callback\n     * for each property. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, key, object). Callbacks may exit iteration early by\n     * explicitly returning `false`.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Objects\n     * @param {Object} object The object to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns `object`.\n     * @example\n     *\n     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {\n     *   console.log(key);\n     * });\n     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)\n     */\n    var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);\n\n    /**\n     * This method is like `_.forOwn` except that it iterates over elements\n     * of a `collection` in the opposite order.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns `object`.\n     * @example\n     *\n     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {\n     *   console.log(key);\n     * });\n     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'\n     */\n    function forOwnRight(object, callback, thisArg) {\n      var props = keys(object),\n        length = props.length;\n\n      callback = baseCreateCallback(callback, thisArg, 3);\n      while (length--) {\n        var key = props[length];\n        if (callback(object[key], key, object) === false) {\n          break;\n        }\n      }\n      return object;\n    }\n\n    /**\n     * Creates a sorted array of property names of all enumerable properties,\n     * own and inherited, of `object` that have function values.\n     *\n     * @static\n     * @memberOf _\n     * @alias methods\n     * @category Objects\n     * @param {Object} object The object to inspect.\n     * @returns {Array} Returns an array of property names that have function values.\n     * @example\n     *\n     * _.functions(_);\n     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]\n     */\n    function functions(object) {\n      var result = [];\n      forIn(object, function (value, key) {\n        if (isFunction(value)) {\n          result.push(key);\n        }\n      });\n      return result.sort();\n    }\n\n    /**\n     * Checks if the specified property name exists as a direct property of `object`,\n     * instead of an inherited property.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to inspect.\n     * @param {string} key The name of the property to check.\n     * @returns {boolean} Returns `true` if key is a direct property, else `false`.\n     * @example\n     *\n     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');\n     * // => true\n     */\n    function has(object, key) {\n      return object ? hasOwnProperty.call(object, key) : false;\n    }\n\n    /**\n     * Creates an object composed of the inverted keys and values of the given object.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to invert.\n     * @returns {Object} Returns the created inverted object.\n     * @example\n     *\n     * _.invert({ 'first': 'fred', 'second': 'barney' });\n     * // => { 'fred': 'first', 'barney': 'second' }\n     */\n    function invert(object) {\n      var index = -1,\n        props = keys(object),\n        length = props.length,\n        result = {};\n\n      while (++index < length) {\n        var key = props[index];\n        result[object[key]] = key;\n      }\n      return result;\n    }\n\n    /**\n     * Checks if `value` is a boolean value.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.\n     * @example\n     *\n     * _.isBoolean(null);\n     * // => false\n     */\n    function isBoolean(value) {\n      return value === true || value === false ||\n        value && typeof value == 'object' && toString.call(value) == boolClass || false;\n    }\n\n    /**\n     * Checks if `value` is a date.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.\n     * @example\n     *\n     * _.isDate(new Date);\n     * // => true\n     */\n    function isDate(value) {\n      return value && typeof value == 'object' && toString.call(value) == dateClass || false;\n    }\n\n    /**\n     * Checks if `value` is a DOM element.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.\n     * @example\n     *\n     * _.isElement(document.body);\n     * // => true\n     */\n    function isElement(value) {\n      return value && value.nodeType === 1 || false;\n    }\n\n    /**\n     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a\n     * length of `0` and objects with no own enumerable properties are considered\n     * \"empty\".\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Array|Object|string} value The value to inspect.\n     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.\n     * @example\n     *\n     * _.isEmpty([1, 2, 3]);\n     * // => false\n     *\n     * _.isEmpty({});\n     * // => true\n     *\n     * _.isEmpty('');\n     * // => true\n     */\n    function isEmpty(value) {\n      var result = true;\n      if (!value) {\n        return result;\n      }\n      var className = toString.call(value),\n        length = value.length;\n\n      if ((className == arrayClass || className == stringClass ||\n        (support.argsClass ? className == argsClass : isArguments(value))) ||\n        (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {\n        return !length;\n      }\n      forOwn(value, function () {\n        return (result = false);\n      });\n      return result;\n    }\n\n    /**\n     * Performs a deep comparison between two values to determine if they are\n     * equivalent to each other. If a callback is provided it will be executed\n     * to compare values. If the callback returns `undefined` comparisons will\n     * be handled by the method instead. The callback is bound to `thisArg` and\n     * invoked with two arguments; (a, b).\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} a The value to compare.\n     * @param {*} b The other value to compare.\n     * @param {Function} [callback] The function to customize comparing values.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n     * @example\n     *\n     * var object = { 'name': 'fred' };\n     * var copy = { 'name': 'fred' };\n     *\n     * object == copy;\n     * // => false\n     *\n     * _.isEqual(object, copy);\n     * // => true\n     *\n     * var words = ['hello', 'goodbye'];\n     * var otherWords = ['hi', 'goodbye'];\n     *\n     * _.isEqual(words, otherWords, function(a, b) {\n     *   var reGreet = /^(?:hello|hi)$/i,\n     *       aGreet = _.isString(a) && reGreet.test(a),\n     *       bGreet = _.isString(b) && reGreet.test(b);\n     *\n     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;\n     * });\n     * // => true\n     */\n    function isEqual(a, b, callback, thisArg) {\n      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));\n    }\n\n    /**\n     * Checks if `value` is, or can be coerced to, a finite number.\n     *\n     * Note: This is not the same as native `isFinite` which will return true for\n     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.\n     * @example\n     *\n     * _.isFinite(-101);\n     * // => true\n     *\n     * _.isFinite('10');\n     * // => true\n     *\n     * _.isFinite(true);\n     * // => false\n     *\n     * _.isFinite('');\n     * // => false\n     *\n     * _.isFinite(Infinity);\n     * // => false\n     */\n    function isFinite(value) {\n      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));\n    }\n\n    /**\n     * Checks if `value` is a function.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.\n     * @example\n     *\n     * _.isFunction(_);\n     * // => true\n     */\n    function isFunction(value) {\n      return typeof value == 'function';\n    }\n\n    // fallback for older versions of Chrome and Safari\n    if (isFunction(/x/)) {\n      isFunction = function (value) {\n        return typeof value == 'function' && toString.call(value) == funcClass;\n      };\n    }\n\n    /**\n     * Checks if `value` is the language type of Object.\n     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.\n     * @example\n     *\n     * _.isObject({});\n     * // => true\n     *\n     * _.isObject([1, 2, 3]);\n     * // => true\n     *\n     * _.isObject(1);\n     * // => false\n     */\n    function isObject(value) {\n      // check if the value is the ECMAScript language type of Object\n      // http://es5.github.io/#x8\n      // and avoid a V8 bug\n      // http://code.google.com/p/v8/issues/detail?id=2291\n      return !!(value && objectTypes[typeof value]);\n    }\n\n    /**\n     * Checks if `value` is `NaN`.\n     *\n     * Note: This is not the same as native `isNaN` which will return `true` for\n     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.\n     * @example\n     *\n     * _.isNaN(NaN);\n     * // => true\n     *\n     * _.isNaN(new Number(NaN));\n     * // => true\n     *\n     * isNaN(undefined);\n     * // => true\n     *\n     * _.isNaN(undefined);\n     * // => false\n     */\n    function isNaN(value) {\n      // `NaN` as a primitive is the only value that is not equal to itself\n      // (perform the [[Class]] check first to avoid errors with some host objects in IE)\n      return isNumber(value) && value != +value;\n    }\n\n    /**\n     * Checks if `value` is `null`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.\n     * @example\n     *\n     * _.isNull(null);\n     * // => true\n     *\n     * _.isNull(undefined);\n     * // => false\n     */\n    function isNull(value) {\n      return value === null;\n    }\n\n    /**\n     * Checks if `value` is a number.\n     *\n     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.\n     * @example\n     *\n     * _.isNumber(8.4 * 5);\n     * // => true\n     */\n    function isNumber(value) {\n      return typeof value == 'number' ||\n        value && typeof value == 'object' && toString.call(value) == numberClass || false;\n    }\n\n    /**\n     * Checks if `value` is an object created by the `Object` constructor.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.\n     * @example\n     *\n     * function Shape() {\n     *   this.x = 0;\n     *   this.y = 0;\n     * }\n     *\n     * _.isPlainObject(new Shape);\n     * // => false\n     *\n     * _.isPlainObject([1, 2, 3]);\n     * // => false\n     *\n     * _.isPlainObject({ 'x': 0, 'y': 0 });\n     * // => true\n     */\n    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function (value) {\n      if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) {\n        return false;\n      }\n      var valueOf = value.valueOf,\n        objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);\n\n      return objProto\n        ? (value == objProto || getPrototypeOf(value) == objProto)\n        : shimIsPlainObject(value);\n    };\n\n    /**\n     * Checks if `value` is a regular expression.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.\n     * @example\n     *\n     * _.isRegExp(/fred/);\n     * // => true\n     */\n    function isRegExp(value) {\n      return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;\n    }\n\n    /**\n     * Checks if `value` is a string.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.\n     * @example\n     *\n     * _.isString('fred');\n     * // => true\n     */\n    function isString(value) {\n      return typeof value == 'string' ||\n        value && typeof value == 'object' && toString.call(value) == stringClass || false;\n    }\n\n    /**\n     * Checks if `value` is `undefined`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {*} value The value to check.\n     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.\n     * @example\n     *\n     * _.isUndefined(void 0);\n     * // => true\n     */\n    function isUndefined(value) {\n      return typeof value == 'undefined';\n    }\n\n    /**\n     * Creates an object with the same keys as `object` and values generated by\n     * running each own enumerable property of `object` through the callback.\n     * The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, key, object).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new object with values of the results of each `callback` execution.\n     * @example\n     *\n     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });\n     * // => { 'a': 3, 'b': 6, 'c': 9 }\n     *\n     * var characters = {\n     *   'fred': { 'name': 'fred', 'age': 40 },\n     *   'pebbles': { 'name': 'pebbles', 'age': 1 }\n     * };\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.mapValues(characters, 'age');\n     * // => { 'fred': 40, 'pebbles': 1 }\n     */\n    function mapValues(object, callback, thisArg) {\n      var result = {};\n      callback = lodash.createCallback(callback, thisArg, 3);\n\n      forOwn(object, function (value, key, object) {\n        result[key] = callback(value, key, object);\n      });\n      return result;\n    }\n\n    /**\n     * Recursively merges own enumerable properties of the source object(s), that\n     * don't resolve to `undefined` into the destination object. Subsequent sources\n     * will overwrite property assignments of previous sources. If a callback is\n     * provided it will be executed to produce the merged values of the destination\n     * and source properties. If the callback returns `undefined` merging will\n     * be handled by the method instead. The callback is bound to `thisArg` and\n     * invoked with two arguments; (objectValue, sourceValue).\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The destination object.\n     * @param {...Object} [source] The source objects.\n     * @param {Function} [callback] The function to customize merging properties.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns the destination object.\n     * @example\n     *\n     * var names = {\n     *   'characters': [\n     *     { 'name': 'barney' },\n     *     { 'name': 'fred' }\n     *   ]\n     * };\n     *\n     * var ages = {\n     *   'characters': [\n     *     { 'age': 36 },\n     *     { 'age': 40 }\n     *   ]\n     * };\n     *\n     * _.merge(names, ages);\n     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }\n     *\n     * var food = {\n     *   'fruits': ['apple'],\n     *   'vegetables': ['beet']\n     * };\n     *\n     * var otherFood = {\n     *   'fruits': ['banana'],\n     *   'vegetables': ['carrot']\n     * };\n     *\n     * _.merge(food, otherFood, function(a, b) {\n     *   return _.isArray(a) ? a.concat(b) : undefined;\n     * });\n     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }\n     */\n    function merge(object) {\n      var args = arguments,\n        length = 2;\n\n      if (!isObject(object)) {\n        return object;\n      }\n      // allows working with `_.reduce` and `_.reduceRight` without using\n      // their `index` and `collection` arguments\n      if (typeof args[2] != 'number') {\n        length = args.length;\n      }\n      if (length > 3 && typeof args[length - 2] == 'function') {\n        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);\n      } else if (length > 2 && typeof args[length - 1] == 'function') {\n        callback = args[--length];\n      }\n      var sources = slice(arguments, 1, length),\n        index = -1,\n        stackA = getArray(),\n        stackB = getArray();\n\n      while (++index < length) {\n        baseMerge(object, sources[index], callback, stackA, stackB);\n      }\n      releaseArray(stackA);\n      releaseArray(stackB);\n      return object;\n    }\n\n    /**\n     * Creates a shallow clone of `object` excluding the specified properties.\n     * Property names may be specified as individual arguments or as arrays of\n     * property names. If a callback is provided it will be executed for each\n     * property of `object` omitting the properties the callback returns truey\n     * for. The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, key, object).\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The source object.\n     * @param {Function|...string|string[]} [callback] The properties to omit or the\n     *  function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns an object without the omitted properties.\n     * @example\n     *\n     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');\n     * // => { 'name': 'fred' }\n     *\n     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {\n     *   return typeof value == 'number';\n     * });\n     * // => { 'name': 'fred' }\n     */\n    function omit(object, callback, thisArg) {\n      var result = {};\n      if (typeof callback != 'function') {\n        var props = [];\n        forIn(object, function (value, key) {\n          props.push(key);\n        });\n        props = baseDifference(props, baseFlatten(arguments, true, false, 1));\n\n        var index = -1,\n          length = props.length;\n\n        while (++index < length) {\n          var key = props[index];\n          result[key] = object[key];\n        }\n      } else {\n        callback = lodash.createCallback(callback, thisArg, 3);\n        forIn(object, function (value, key, object) {\n          if (!callback(value, key, object)) {\n            result[key] = value;\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Creates a two dimensional array of an object's key-value pairs,\n     * i.e. `[[key1, value1], [key2, value2]]`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to inspect.\n     * @returns {Array} Returns new array of key-value pairs.\n     * @example\n     *\n     * _.pairs({ 'barney': 36, 'fred': 40 });\n     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)\n     */\n    function pairs(object) {\n      var index = -1,\n        props = keys(object),\n        length = props.length,\n        result = Array(length);\n\n      while (++index < length) {\n        var key = props[index];\n        result[index] = [key, object[key]];\n      }\n      return result;\n    }\n\n    /**\n     * Creates a shallow clone of `object` composed of the specified properties.\n     * Property names may be specified as individual arguments or as arrays of\n     * property names. If a callback is provided it will be executed for each\n     * property of `object` picking the properties the callback returns truey\n     * for. The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, key, object).\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The source object.\n     * @param {Function|...string|string[]} [callback] The function called per\n     *  iteration or property names to pick, specified as individual property\n     *  names or arrays of property names.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns an object composed of the picked properties.\n     * @example\n     *\n     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');\n     * // => { 'name': 'fred' }\n     *\n     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {\n     *   return key.charAt(0) != '_';\n     * });\n     * // => { 'name': 'fred' }\n     */\n    function pick(object, callback, thisArg) {\n      var result = {};\n      if (typeof callback != 'function') {\n        var index = -1,\n          props = baseFlatten(arguments, true, false, 1),\n          length = isObject(object) ? props.length : 0;\n\n        while (++index < length) {\n          var key = props[index];\n          if (key in object) {\n            result[key] = object[key];\n          }\n        }\n      } else {\n        callback = lodash.createCallback(callback, thisArg, 3);\n        forIn(object, function (value, key, object) {\n          if (callback(value, key, object)) {\n            result[key] = value;\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * An alternative to `_.reduce` this method transforms `object` to a new\n     * `accumulator` object which is the result of running each of its own\n     * enumerable properties through a callback, with each callback execution\n     * potentially mutating the `accumulator` object. The callback is bound to\n     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).\n     * Callbacks may exit iteration early by explicitly returning `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Array|Object} object The object to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [accumulator] The custom accumulator value.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the accumulated value.\n     * @example\n     *\n     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {\n     *   num *= num;\n     *   if (num % 2) {\n     *     return result.push(num) < 3;\n     *   }\n     * });\n     * // => [1, 9, 25]\n     *\n     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {\n     *   result[key] = num * 3;\n     * });\n     * // => { 'a': 3, 'b': 6, 'c': 9 }\n     */\n    function transform(object, callback, accumulator, thisArg) {\n      var isArr = isArray(object);\n      if (accumulator == null) {\n        if (isArr) {\n          accumulator = [];\n        } else {\n          var ctor = object && object.constructor,\n            proto = ctor && ctor.prototype;\n\n          accumulator = baseCreate(proto);\n        }\n      }\n      if (callback) {\n        callback = lodash.createCallback(callback, thisArg, 4);\n        (isArr ? baseEach : forOwn)(object, function (value, index, object) {\n          return callback(accumulator, value, index, object);\n        });\n      }\n      return accumulator;\n    }\n\n    /**\n     * Creates an array composed of the own enumerable property values of `object`.\n     *\n     * @static\n     * @memberOf _\n     * @category Objects\n     * @param {Object} object The object to inspect.\n     * @returns {Array} Returns an array of property values.\n     * @example\n     *\n     * _.values({ 'one': 1, 'two': 2, 'three': 3 });\n     * // => [1, 2, 3] (property order is not guaranteed across environments)\n     */\n    function values(object) {\n      var index = -1,\n        props = keys(object),\n        length = props.length,\n        result = Array(length);\n\n      while (++index < length) {\n        result[index] = object[props[index]];\n      }\n      return result;\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates an array of elements from the specified indexes, or keys, of the\n     * `collection`. Indexes may be specified as individual arguments or as arrays\n     * of indexes.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`\n     *   to retrieve, specified as individual indexes or arrays of indexes.\n     * @returns {Array} Returns a new array of elements corresponding to the\n     *  provided indexes.\n     * @example\n     *\n     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);\n     * // => ['a', 'c', 'e']\n     *\n     * _.at(['fred', 'barney', 'pebbles'], 0, 2);\n     * // => ['fred', 'pebbles']\n     */\n    function at(collection) {\n      var args = arguments,\n        index = -1,\n        props = baseFlatten(args, true, false, 1),\n        length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,\n        result = Array(length);\n\n      if (support.unindexedChars && isString(collection)) {\n        collection = collection.split('');\n      }\n      while (++index < length) {\n        result[index] = collection[props[index]];\n      }\n      return result;\n    }\n\n    /**\n     * Checks if a given value is present in a collection using strict equality\n     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the\n     * offset from the end of the collection.\n     *\n     * @static\n     * @memberOf _\n     * @alias include\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {*} target The value to check for.\n     * @param {number} [fromIndex=0] The index to search from.\n     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.\n     * @example\n     *\n     * _.contains([1, 2, 3], 1);\n     * // => true\n     *\n     * _.contains([1, 2, 3], 1, 2);\n     * // => false\n     *\n     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');\n     * // => true\n     *\n     * _.contains('pebbles', 'eb');\n     * // => true\n     */\n    function contains(collection, target, fromIndex) {\n      var index = -1,\n        indexOf = getIndexOf(),\n        length = collection ? collection.length : 0,\n        result = false;\n\n      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;\n      if (isArray(collection)) {\n        result = indexOf(collection, target, fromIndex) > -1;\n      } else if (typeof length == 'number') {\n        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;\n      } else {\n        baseEach(collection, function (value) {\n          if (++index >= fromIndex) {\n            return !(result = value === target);\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Creates an object composed of keys generated from the results of running\n     * each element of `collection` through the callback. The corresponding value\n     * of each key is the number of times the key was returned by the callback.\n     * The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns the composed aggregate object.\n     * @example\n     *\n     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });\n     * // => { '4': 1, '6': 2 }\n     *\n     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);\n     * // => { '4': 1, '6': 2 }\n     *\n     * _.countBy(['one', 'two', 'three'], 'length');\n     * // => { '3': 2, '5': 1 }\n     */\n    var countBy = createAggregator(function (result, value, key) {\n      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);\n    });\n\n    /**\n     * Checks if the given callback returns truey value for **all** elements of\n     * a collection. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias all\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {boolean} Returns `true` if all elements passed the callback check,\n     *  else `false`.\n     * @example\n     *\n     * _.every([true, 1, null, 'yes']);\n     * // => false\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.every(characters, 'age');\n     * // => true\n     *\n     * // using \"_.where\" callback shorthand\n     * _.every(characters, { 'age': 36 });\n     * // => false\n     */\n    function every(collection, callback, thisArg) {\n      var result = true;\n      callback = lodash.createCallback(callback, thisArg, 3);\n\n      if (isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          if (!(result = !!callback(collection[index], index, collection))) {\n            break;\n          }\n        }\n      } else {\n        baseEach(collection, function (value, index, collection) {\n          return (result = !!callback(value, index, collection));\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Iterates over elements of a collection, returning an array of all elements\n     * the callback returns truey for. The callback is bound to `thisArg` and\n     * invoked with three arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias select\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new array of elements that passed the callback check.\n     * @example\n     *\n     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });\n     * // => [2, 4, 6]\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36, 'blocked': false },\n     *   { 'name': 'fred',   'age': 40, 'blocked': true }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.filter(characters, 'blocked');\n     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]\n     *\n     * // using \"_.where\" callback shorthand\n     * _.filter(characters, { 'age': 36 });\n     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]\n     */\n    function filter(collection, callback, thisArg) {\n      var result = [];\n      callback = lodash.createCallback(callback, thisArg, 3);\n\n      if (isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          var value = collection[index];\n          if (callback(value, index, collection)) {\n            result.push(value);\n          }\n        }\n      } else {\n        baseEach(collection, function (value, index, collection) {\n          if (callback(value, index, collection)) {\n            result.push(value);\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Iterates over elements of a collection, returning the first element that\n     * the callback returns truey for. The callback is bound to `thisArg` and\n     * invoked with three arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias detect, findWhere\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the found element, else `undefined`.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'age': 36, 'blocked': false },\n     *   { 'name': 'fred',    'age': 40, 'blocked': true },\n     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }\n     * ];\n     *\n     * _.find(characters, function(chr) {\n     *   return chr.age < 40;\n     * });\n     * // => { 'name': 'barney', 'age': 36, 'blocked': false }\n     *\n     * // using \"_.where\" callback shorthand\n     * _.find(characters, { 'age': 1 });\n     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.find(characters, 'blocked');\n     * // => { 'name': 'fred', 'age': 40, 'blocked': true }\n     */\n    function find(collection, callback, thisArg) {\n      callback = lodash.createCallback(callback, thisArg, 3);\n\n      if (isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          var value = collection[index];\n          if (callback(value, index, collection)) {\n            return value;\n          }\n        }\n      } else {\n        var result;\n        baseEach(collection, function (value, index, collection) {\n          if (callback(value, index, collection)) {\n            result = value;\n            return false;\n          }\n        });\n        return result;\n      }\n    }\n\n    /**\n     * This method is like `_.find` except that it iterates over elements\n     * of a `collection` from right to left.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the found element, else `undefined`.\n     * @example\n     *\n     * _.findLast([1, 2, 3, 4], function(num) {\n     *   return num % 2 == 1;\n     * });\n     * // => 3\n     */\n    function findLast(collection, callback, thisArg) {\n      var result;\n      callback = lodash.createCallback(callback, thisArg, 3);\n      forEachRight(collection, function (value, index, collection) {\n        if (callback(value, index, collection)) {\n          result = value;\n          return false;\n        }\n      });\n      return result;\n    }\n\n    /**\n     * Iterates over elements of a collection, executing the callback for each\n     * element. The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, index|key, collection). Callbacks may exit iteration early by\n     * explicitly returning `false`.\n     *\n     * Note: As with other \"Collections\" methods, objects with a `length` property\n     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`\n     * may be used for object iteration.\n     *\n     * @static\n     * @memberOf _\n     * @alias each\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array|Object|string} Returns `collection`.\n     * @example\n     *\n     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');\n     * // => logs each number and returns '1,2,3'\n     *\n     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });\n     * // => logs each number and returns the object (property order is not guaranteed across environments)\n     */\n    function forEach(collection, callback, thisArg) {\n      if (callback && typeof thisArg == 'undefined' && isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          if (callback(collection[index], index, collection) === false) {\n            break;\n          }\n        }\n      } else {\n        baseEach(collection, callback, thisArg);\n      }\n      return collection;\n    }\n\n    /**\n     * This method is like `_.forEach` except that it iterates over elements\n     * of a `collection` from right to left.\n     *\n     * @static\n     * @memberOf _\n     * @alias eachRight\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array|Object|string} Returns `collection`.\n     * @example\n     *\n     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');\n     * // => logs each number from right to left and returns '3,2,1'\n     */\n    function forEachRight(collection, callback, thisArg) {\n      var iterable = collection,\n        length = collection ? collection.length : 0;\n\n      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);\n      if (isArray(collection)) {\n        while (length--) {\n          if (callback(collection[length], length, collection) === false) {\n            break;\n          }\n        }\n      } else {\n        if (typeof length != 'number') {\n          var props = keys(collection);\n          length = props.length;\n        } else if (support.unindexedChars && isString(collection)) {\n          iterable = collection.split('');\n        }\n        baseEach(collection, function (value, key, collection) {\n          key = props ? props[--length] : --length;\n          return callback(iterable[key], key, collection);\n        });\n      }\n      return collection;\n    }\n\n    /**\n     * Creates an object composed of keys generated from the results of running\n     * each element of a collection through the callback. The corresponding value\n     * of each key is an array of the elements responsible for generating the key.\n     * The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns the composed aggregate object.\n     * @example\n     *\n     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });\n     * // => { '4': [4.2], '6': [6.1, 6.4] }\n     *\n     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);\n     * // => { '4': [4.2], '6': [6.1, 6.4] }\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.groupBy(['one', 'two', 'three'], 'length');\n     * // => { '3': ['one', 'two'], '5': ['three'] }\n     */\n    var groupBy = createAggregator(function (result, value, key) {\n      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);\n    });\n\n    /**\n     * Creates an object composed of keys generated from the results of running\n     * each element of the collection through the given callback. The corresponding\n     * value of each key is the last element responsible for generating the key.\n     * The callback is bound to `thisArg` and invoked with three arguments;\n     * (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Object} Returns the composed aggregate object.\n     * @example\n     *\n     * var keys = [\n     *   { 'dir': 'left', 'code': 97 },\n     *   { 'dir': 'right', 'code': 100 }\n     * ];\n     *\n     * _.indexBy(keys, 'dir');\n     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }\n     *\n     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });\n     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }\n     *\n     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);\n     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }\n     */\n    var indexBy = createAggregator(function (result, value, key) {\n      result[key] = value;\n    });\n\n    /**\n     * Invokes the method named by `methodName` on each element in the `collection`\n     * returning an array of the results of each invoked method. Additional arguments\n     * will be provided to each invoked method. If `methodName` is a function it\n     * will be invoked for, and `this` bound to, each element in the `collection`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|string} methodName The name of the method to invoke or\n     *  the function invoked per iteration.\n     * @param {...*} [arg] Arguments to invoke the method with.\n     * @returns {Array} Returns a new array of the results of each invoked method.\n     * @example\n     *\n     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');\n     * // => [[1, 5, 7], [1, 2, 3]]\n     *\n     * _.invoke([123, 456], String.prototype.split, '');\n     * // => [['1', '2', '3'], ['4', '5', '6']]\n     */\n    function invoke(collection, methodName) {\n      var args = slice(arguments, 2),\n        index = -1,\n        isFunc = typeof methodName == 'function',\n        length = collection ? collection.length : 0,\n        result = Array(typeof length == 'number' ? length : 0);\n\n      forEach(collection, function (value) {\n        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);\n      });\n      return result;\n    }\n\n    /**\n     * Creates an array of values by running each element in the collection\n     * through the callback. The callback is bound to `thisArg` and invoked with\n     * three arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias collect\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new array of the results of each `callback` execution.\n     * @example\n     *\n     * _.map([1, 2, 3], function(num) { return num * 3; });\n     * // => [3, 6, 9]\n     *\n     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });\n     * // => [3, 6, 9] (property order is not guaranteed across environments)\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.map(characters, 'name');\n     * // => ['barney', 'fred']\n     */\n    function map(collection, callback, thisArg) {\n      var index = -1,\n        length = collection ? collection.length : 0,\n        result = Array(typeof length == 'number' ? length : 0);\n\n      callback = lodash.createCallback(callback, thisArg, 3);\n      if (isArray(collection)) {\n        while (++index < length) {\n          result[index] = callback(collection[index], index, collection);\n        }\n      } else {\n        baseEach(collection, function (value, key, collection) {\n          result[++index] = callback(value, key, collection);\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Retrieves the maximum value of a collection. If the collection is empty or\n     * falsey `-Infinity` is returned. If a callback is provided it will be executed\n     * for each value in the collection to generate the criterion by which the value\n     * is ranked. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, index, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the maximum value.\n     * @example\n     *\n     * _.max([4, 2, 8, 6]);\n     * // => 8\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * _.max(characters, function(chr) { return chr.age; });\n     * // => { 'name': 'fred', 'age': 40 };\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.max(characters, 'age');\n     * // => { 'name': 'fred', 'age': 40 };\n     */\n    function max(collection, callback, thisArg) {\n      var computed = -Infinity,\n        result = computed;\n\n      // allows working with functions like `_.map` without using\n      // their `index` argument as a callback\n      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {\n        callback = null;\n      }\n      if (callback == null && isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          var value = collection[index];\n          if (value > result) {\n            result = value;\n          }\n        }\n      } else {\n        callback = (callback == null && isString(collection))\n          ? charAtCallback\n          : lodash.createCallback(callback, thisArg, 3);\n\n        baseEach(collection, function (value, index, collection) {\n          var current = callback(value, index, collection);\n          if (current > computed) {\n            computed = current;\n            result = value;\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Retrieves the minimum value of a collection. If the collection is empty or\n     * falsey `Infinity` is returned. If a callback is provided it will be executed\n     * for each value in the collection to generate the criterion by which the value\n     * is ranked. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, index, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the minimum value.\n     * @example\n     *\n     * _.min([4, 2, 8, 6]);\n     * // => 2\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * _.min(characters, function(chr) { return chr.age; });\n     * // => { 'name': 'barney', 'age': 36 };\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.min(characters, 'age');\n     * // => { 'name': 'barney', 'age': 36 };\n     */\n    function min(collection, callback, thisArg) {\n      var computed = Infinity,\n        result = computed;\n\n      // allows working with functions like `_.map` without using\n      // their `index` argument as a callback\n      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {\n        callback = null;\n      }\n      if (callback == null && isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          var value = collection[index];\n          if (value < result) {\n            result = value;\n          }\n        }\n      } else {\n        callback = (callback == null && isString(collection))\n          ? charAtCallback\n          : lodash.createCallback(callback, thisArg, 3);\n\n        baseEach(collection, function (value, index, collection) {\n          var current = callback(value, index, collection);\n          if (current < computed) {\n            computed = current;\n            result = value;\n          }\n        });\n      }\n      return result;\n    }\n\n    /**\n     * Retrieves the value of a specified property from all elements in the collection.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {string} property The name of the property to pluck.\n     * @returns {Array} Returns a new array of property values.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * _.pluck(characters, 'name');\n     * // => ['barney', 'fred']\n     */\n    var pluck = map;\n\n    /**\n     * Reduces a collection to a value which is the accumulated result of running\n     * each element in the collection through the callback, where each successive\n     * callback execution consumes the return value of the previous execution. If\n     * `accumulator` is not provided the first element of the collection will be\n     * used as the initial `accumulator` value. The callback is bound to `thisArg`\n     * and invoked with four arguments; (accumulator, value, index|key, collection).\n     *\n     * @static\n     * @memberOf _\n     * @alias foldl, inject\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [accumulator] Initial value of the accumulator.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the accumulated value.\n     * @example\n     *\n     * var sum = _.reduce([1, 2, 3], function(sum, num) {\n     *   return sum + num;\n     * });\n     * // => 6\n     *\n     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {\n     *   result[key] = num * 3;\n     *   return result;\n     * }, {});\n     * // => { 'a': 3, 'b': 6, 'c': 9 }\n     */\n    function reduce(collection, callback, accumulator, thisArg) {\n      var noaccum = arguments.length < 3;\n      callback = lodash.createCallback(callback, thisArg, 4);\n\n      if (isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        if (noaccum) {\n          accumulator = collection[++index];\n        }\n        while (++index < length) {\n          accumulator = callback(accumulator, collection[index], index, collection);\n        }\n      } else {\n        baseEach(collection, function (value, index, collection) {\n          accumulator = noaccum\n            ? (noaccum = false, value)\n            : callback(accumulator, value, index, collection)\n        });\n      }\n      return accumulator;\n    }\n\n    /**\n     * This method is like `_.reduce` except that it iterates over elements\n     * of a `collection` from right to left.\n     *\n     * @static\n     * @memberOf _\n     * @alias foldr\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function} [callback=identity] The function called per iteration.\n     * @param {*} [accumulator] Initial value of the accumulator.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the accumulated value.\n     * @example\n     *\n     * var list = [[0, 1], [2, 3], [4, 5]];\n     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);\n     * // => [4, 5, 2, 3, 0, 1]\n     */\n    function reduceRight(collection, callback, accumulator, thisArg) {\n      var noaccum = arguments.length < 3;\n      callback = lodash.createCallback(callback, thisArg, 4);\n      forEachRight(collection, function (value, index, collection) {\n        accumulator = noaccum\n          ? (noaccum = false, value)\n          : callback(accumulator, value, index, collection);\n      });\n      return accumulator;\n    }\n\n    /**\n     * The opposite of `_.filter` this method returns the elements of a\n     * collection that the callback does **not** return truey for.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new array of elements that failed the callback check.\n     * @example\n     *\n     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });\n     * // => [1, 3, 5]\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36, 'blocked': false },\n     *   { 'name': 'fred',   'age': 40, 'blocked': true }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.reject(characters, 'blocked');\n     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]\n     *\n     * // using \"_.where\" callback shorthand\n     * _.reject(characters, { 'age': 36 });\n     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]\n     */\n    function reject(collection, callback, thisArg) {\n      callback = lodash.createCallback(callback, thisArg, 3);\n      return filter(collection, function (value, index, collection) {\n        return !callback(value, index, collection);\n      });\n    }\n\n    /**\n     * Retrieves a random element or `n` random elements from a collection.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to sample.\n     * @param {number} [n] The number of elements to sample.\n     * @param- {Object} [guard] Allows working with functions like `_.map`\n     *  without using their `index` arguments as `n`.\n     * @returns {Array} Returns the random sample(s) of `collection`.\n     * @example\n     *\n     * _.sample([1, 2, 3, 4]);\n     * // => 2\n     *\n     * _.sample([1, 2, 3, 4], 2);\n     * // => [3, 1]\n     */\n    function sample(collection, n, guard) {\n      if (collection && typeof collection.length != 'number') {\n        collection = values(collection);\n      } else if (support.unindexedChars && isString(collection)) {\n        collection = collection.split('');\n      }\n      if (n == null || guard) {\n        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;\n      }\n      var result = shuffle(collection);\n      result.length = nativeMin(nativeMax(0, n), result.length);\n      return result;\n    }\n\n    /**\n     * Creates an array of shuffled values, using a version of the Fisher-Yates\n     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to shuffle.\n     * @returns {Array} Returns a new shuffled collection.\n     * @example\n     *\n     * _.shuffle([1, 2, 3, 4, 5, 6]);\n     * // => [4, 1, 6, 3, 5, 2]\n     */\n    function shuffle(collection) {\n      var index = -1,\n        length = collection ? collection.length : 0,\n        result = Array(typeof length == 'number' ? length : 0);\n\n      forEach(collection, function (value) {\n        var rand = baseRandom(0, ++index);\n        result[index] = result[rand];\n        result[rand] = value;\n      });\n      return result;\n    }\n\n    /**\n     * Gets the size of the `collection` by returning `collection.length` for arrays\n     * and array-like objects or the number of own enumerable properties for objects.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to inspect.\n     * @returns {number} Returns `collection.length` or number of own enumerable properties.\n     * @example\n     *\n     * _.size([1, 2]);\n     * // => 2\n     *\n     * _.size({ 'one': 1, 'two': 2, 'three': 3 });\n     * // => 3\n     *\n     * _.size('pebbles');\n     * // => 7\n     */\n    function size(collection) {\n      var length = collection ? collection.length : 0;\n      return typeof length == 'number' ? length : keys(collection).length;\n    }\n\n    /**\n     * Checks if the callback returns a truey value for **any** element of a\n     * collection. The function returns as soon as it finds a passing value and\n     * does not iterate over the entire collection. The callback is bound to\n     * `thisArg` and invoked with three arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias any\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {boolean} Returns `true` if any element passed the callback check,\n     *  else `false`.\n     * @example\n     *\n     * _.some([null, 0, 'yes', false], Boolean);\n     * // => true\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36, 'blocked': false },\n     *   { 'name': 'fred',   'age': 40, 'blocked': true }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.some(characters, 'blocked');\n     * // => true\n     *\n     * // using \"_.where\" callback shorthand\n     * _.some(characters, { 'age': 1 });\n     * // => false\n     */\n    function some(collection, callback, thisArg) {\n      var result;\n      callback = lodash.createCallback(callback, thisArg, 3);\n\n      if (isArray(collection)) {\n        var index = -1,\n          length = collection.length;\n\n        while (++index < length) {\n          if ((result = callback(collection[index], index, collection))) {\n            break;\n          }\n        }\n      } else {\n        baseEach(collection, function (value, index, collection) {\n          return !(result = callback(value, index, collection));\n        });\n      }\n      return !!result;\n    }\n\n    /**\n     * Creates an array of elements, sorted in ascending order by the results of\n     * running each element in a collection through the callback. This method\n     * performs a stable sort, that is, it will preserve the original sort order\n     * of equal elements. The callback is bound to `thisArg` and invoked with\n     * three arguments; (value, index|key, collection).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an array of property names is provided for `callback` the collection\n     * will be sorted by each property value.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Array|Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new array of sorted elements.\n     * @example\n     *\n     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });\n     * // => [3, 1, 2]\n     *\n     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);\n     * // => [3, 1, 2]\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'age': 36 },\n     *   { 'name': 'fred',    'age': 40 },\n     *   { 'name': 'barney',  'age': 26 },\n     *   { 'name': 'fred',    'age': 30 }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.map(_.sortBy(characters, 'age'), _.values);\n     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]\n     *\n     * // sorting by multiple properties\n     * _.map(_.sortBy(characters, ['name', 'age']), _.values);\n     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]\n     */\n    function sortBy(collection, callback, thisArg) {\n      var index = -1,\n        isArr = isArray(callback),\n        length = collection ? collection.length : 0,\n        result = Array(typeof length == 'number' ? length : 0);\n\n      if (!isArr) {\n        callback = lodash.createCallback(callback, thisArg, 3);\n      }\n      forEach(collection, function (value, key, collection) {\n        var object = result[++index] = getObject();\n        if (isArr) {\n          object.criteria = map(callback, function (key) {\n            return value[key];\n          });\n        } else {\n          (object.criteria = getArray())[0] = callback(value, key, collection);\n        }\n        object.index = index;\n        object.value = value;\n      });\n\n      length = result.length;\n      result.sort(compareAscending);\n      while (length--) {\n        var object = result[length];\n        result[length] = object.value;\n        if (!isArr) {\n          releaseArray(object.criteria);\n        }\n        releaseObject(object);\n      }\n      return result;\n    }\n\n    /**\n     * Converts the `collection` to an array.\n     *\n     * @static\n     * @memberOf _\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to convert.\n     * @returns {Array} Returns the new converted array.\n     * @example\n     *\n     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);\n     * // => [2, 3, 4]\n     */\n    function toArray(collection) {\n      if (collection && typeof collection.length == 'number') {\n        return (support.unindexedChars && isString(collection))\n          ? collection.split('')\n          : slice(collection);\n      }\n      return values(collection);\n    }\n\n    /**\n     * Performs a deep comparison of each element in a `collection` to the given\n     * `properties` object, returning an array of all elements that have equivalent\n     * property values.\n     *\n     * @static\n     * @memberOf _\n     * @type Function\n     * @category Collections\n     * @param {Array|Object|string} collection The collection to iterate over.\n     * @param {Object} props The object of property values to filter by.\n     * @returns {Array} Returns a new array of elements that have the given properties.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },\n     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }\n     * ];\n     *\n     * _.where(characters, { 'age': 36 });\n     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]\n     *\n     * _.where(characters, { 'pets': ['dino'] });\n     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]\n     */\n    var where = filter;\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates an array with all falsey values removed. The values `false`, `null`,\n     * `0`, `\"\"`, `undefined`, and `NaN` are all falsey.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to compact.\n     * @returns {Array} Returns a new array of filtered values.\n     * @example\n     *\n     * _.compact([0, 1, false, 2, '', 3]);\n     * // => [1, 2, 3]\n     */\n    function compact(array) {\n      var index = -1,\n        length = array ? array.length : 0,\n        result = [];\n\n      while (++index < length) {\n        var value = array[index];\n        if (value) {\n          result.push(value);\n        }\n      }\n      return result;\n    }\n\n    /**\n     * Creates an array excluding all values of the provided arrays using strict\n     * equality for comparisons, i.e. `===`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to process.\n     * @param {...Array} [values] The arrays of values to exclude.\n     * @returns {Array} Returns a new array of filtered values.\n     * @example\n     *\n     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);\n     * // => [1, 3, 4]\n     */\n    function difference(array) {\n      return baseDifference(array, baseFlatten(arguments, true, true, 1));\n    }\n\n    /**\n     * This method is like `_.find` except that it returns the index of the first\n     * element that passes the callback check, instead of the element itself.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to search.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {number} Returns the index of the found element, else `-1`.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'age': 36, 'blocked': false },\n     *   { 'name': 'fred',    'age': 40, 'blocked': true },\n     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }\n     * ];\n     *\n     * _.findIndex(characters, function(chr) {\n     *   return chr.age < 20;\n     * });\n     * // => 2\n     *\n     * // using \"_.where\" callback shorthand\n     * _.findIndex(characters, { 'age': 36 });\n     * // => 0\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.findIndex(characters, 'blocked');\n     * // => 1\n     */\n    function findIndex(array, callback, thisArg) {\n      var index = -1,\n        length = array ? array.length : 0;\n\n      callback = lodash.createCallback(callback, thisArg, 3);\n      while (++index < length) {\n        if (callback(array[index], index, array)) {\n          return index;\n        }\n      }\n      return -1;\n    }\n\n    /**\n     * This method is like `_.findIndex` except that it iterates over elements\n     * of a `collection` from right to left.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to search.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {number} Returns the index of the found element, else `-1`.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'age': 36, 'blocked': true },\n     *   { 'name': 'fred',    'age': 40, 'blocked': false },\n     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }\n     * ];\n     *\n     * _.findLastIndex(characters, function(chr) {\n     *   return chr.age > 30;\n     * });\n     * // => 1\n     *\n     * // using \"_.where\" callback shorthand\n     * _.findLastIndex(characters, { 'age': 36 });\n     * // => 0\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.findLastIndex(characters, 'blocked');\n     * // => 2\n     */\n    function findLastIndex(array, callback, thisArg) {\n      var length = array ? array.length : 0;\n      callback = lodash.createCallback(callback, thisArg, 3);\n      while (length--) {\n        if (callback(array[length], length, array)) {\n          return length;\n        }\n      }\n      return -1;\n    }\n\n    /**\n     * Gets the first element or first `n` elements of an array. If a callback\n     * is provided elements at the beginning of the array are returned as long\n     * as the callback returns truey. The callback is bound to `thisArg` and\n     * invoked with three arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias head, take\n     * @category Arrays\n     * @param {Array} array The array to query.\n     * @param {Function|Object|number|string} [callback] The function called\n     *  per element or the number of elements to return. If a property name or\n     *  object is provided it will be used to create a \"_.pluck\" or \"_.where\"\n     *  style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the first element(s) of `array`.\n     * @example\n     *\n     * _.first([1, 2, 3]);\n     * // => 1\n     *\n     * _.first([1, 2, 3], 2);\n     * // => [1, 2]\n     *\n     * _.first([1, 2, 3], function(num) {\n     *   return num < 3;\n     * });\n     * // => [1, 2]\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },\n     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },\n     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.first(characters, 'blocked');\n     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]\n     *\n     * // using \"_.where\" callback shorthand\n     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');\n     * // => ['barney', 'fred']\n     */\n    function first(array, callback, thisArg) {\n      var n = 0,\n        length = array ? array.length : 0;\n\n      if (typeof callback != 'number' && callback != null) {\n        var index = -1;\n        callback = lodash.createCallback(callback, thisArg, 3);\n        while (++index < length && callback(array[index], index, array)) {\n          n++;\n        }\n      } else {\n        n = callback;\n        if (n == null || thisArg) {\n          return array ? array[0] : undefined;\n        }\n      }\n      return slice(array, 0, nativeMin(nativeMax(0, n), length));\n    }\n\n    /**\n     * Flattens a nested array (the nesting can be to any depth). If `isShallow`\n     * is truey, the array will only be flattened a single level. If a callback\n     * is provided each element of the array is passed through the callback before\n     * flattening. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to flatten.\n     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new flattened array.\n     * @example\n     *\n     * _.flatten([1, [2], [3, [[4]]]]);\n     * // => [1, 2, 3, 4];\n     *\n     * _.flatten([1, [2], [3, [[4]]]], true);\n     * // => [1, 2, 3, [[4]]];\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },\n     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.flatten(characters, 'pets');\n     * // => ['hoppy', 'baby puss', 'dino']\n     */\n    function flatten(array, isShallow, callback, thisArg) {\n      // juggle arguments\n      if (typeof isShallow != 'boolean' && isShallow != null) {\n        thisArg = callback;\n        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;\n        isShallow = false;\n      }\n      if (callback != null) {\n        array = map(array, callback, thisArg);\n      }\n      return baseFlatten(array, isShallow);\n    }\n\n    /**\n     * Gets the index at which the first occurrence of `value` is found using\n     * strict equality for comparisons, i.e. `===`. If the array is already sorted\n     * providing `true` for `fromIndex` will run a faster binary search.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to search.\n     * @param {*} value The value to search for.\n     * @param {boolean|number} [fromIndex=0] The index to search from or `true`\n     *  to perform a binary search on a sorted array.\n     * @returns {number} Returns the index of the matched value or `-1`.\n     * @example\n     *\n     * _.indexOf([1, 2, 3, 1, 2, 3], 2);\n     * // => 1\n     *\n     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);\n     * // => 4\n     *\n     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);\n     * // => 2\n     */\n    function indexOf(array, value, fromIndex) {\n      if (typeof fromIndex == 'number') {\n        var length = array ? array.length : 0;\n        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);\n      } else if (fromIndex) {\n        var index = sortedIndex(array, value);\n        return array[index] === value ? index : -1;\n      }\n      return baseIndexOf(array, value, fromIndex);\n    }\n\n    /**\n     * Gets all but the last element or last `n` elements of an array. If a\n     * callback is provided elements at the end of the array are excluded from\n     * the result as long as the callback returns truey. The callback is bound\n     * to `thisArg` and invoked with three arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to query.\n     * @param {Function|Object|number|string} [callback=1] The function called\n     *  per element or the number of elements to exclude. If a property name or\n     *  object is provided it will be used to create a \"_.pluck\" or \"_.where\"\n     *  style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a slice of `array`.\n     * @example\n     *\n     * _.initial([1, 2, 3]);\n     * // => [1, 2]\n     *\n     * _.initial([1, 2, 3], 2);\n     * // => [1]\n     *\n     * _.initial([1, 2, 3], function(num) {\n     *   return num > 1;\n     * });\n     * // => [1]\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },\n     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },\n     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.initial(characters, 'blocked');\n     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]\n     *\n     * // using \"_.where\" callback shorthand\n     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');\n     * // => ['barney', 'fred']\n     */\n    function initial(array, callback, thisArg) {\n      var n = 0,\n        length = array ? array.length : 0;\n\n      if (typeof callback != 'number' && callback != null) {\n        var index = length;\n        callback = lodash.createCallback(callback, thisArg, 3);\n        while (index-- && callback(array[index], index, array)) {\n          n++;\n        }\n      } else {\n        n = (callback == null || thisArg) ? 1 : callback || n;\n      }\n      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));\n    }\n\n    /**\n     * Creates an array of unique values present in all provided arrays using\n     * strict equality for comparisons, i.e. `===`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {...Array} [array] The arrays to inspect.\n     * @returns {Array} Returns an array of shared values.\n     * @example\n     *\n     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);\n     * // => [1, 2]\n     */\n    function intersection() {\n      var args = [],\n        argsIndex = -1,\n        argsLength = arguments.length,\n        caches = getArray(),\n        indexOf = getIndexOf(),\n        trustIndexOf = indexOf === baseIndexOf,\n        seen = getArray();\n\n      while (++argsIndex < argsLength) {\n        var value = arguments[argsIndex];\n        if (isArray(value) || isArguments(value)) {\n          args.push(value);\n          caches.push(trustIndexOf && value.length >= largeArraySize &&\n            createCache(argsIndex ? args[argsIndex] : seen));\n        }\n      }\n      var array = args[0],\n        index = -1,\n        length = array ? array.length : 0,\n        result = [];\n\n      outer:\n        while (++index < length) {\n          var cache = caches[0];\n          value = array[index];\n\n          if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {\n            argsIndex = argsLength;\n            (cache || seen).push(value);\n            while (--argsIndex) {\n              cache = caches[argsIndex];\n              if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {\n                continue outer;\n              }\n            }\n            result.push(value);\n          }\n        }\n      while (argsLength--) {\n        cache = caches[argsLength];\n        if (cache) {\n          releaseObject(cache);\n        }\n      }\n      releaseArray(caches);\n      releaseArray(seen);\n      return result;\n    }\n\n    /**\n     * Gets the last element or last `n` elements of an array. If a callback is\n     * provided elements at the end of the array are returned as long as the\n     * callback returns truey. The callback is bound to `thisArg` and invoked\n     * with three arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to query.\n     * @param {Function|Object|number|string} [callback] The function called\n     *  per element or the number of elements to return. If a property name or\n     *  object is provided it will be used to create a \"_.pluck\" or \"_.where\"\n     *  style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {*} Returns the last element(s) of `array`.\n     * @example\n     *\n     * _.last([1, 2, 3]);\n     * // => 3\n     *\n     * _.last([1, 2, 3], 2);\n     * // => [2, 3]\n     *\n     * _.last([1, 2, 3], function(num) {\n     *   return num > 1;\n     * });\n     * // => [2, 3]\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },\n     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },\n     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.pluck(_.last(characters, 'blocked'), 'name');\n     * // => ['fred', 'pebbles']\n     *\n     * // using \"_.where\" callback shorthand\n     * _.last(characters, { 'employer': 'na' });\n     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]\n     */\n    function last(array, callback, thisArg) {\n      var n = 0,\n        length = array ? array.length : 0;\n\n      if (typeof callback != 'number' && callback != null) {\n        var index = length;\n        callback = lodash.createCallback(callback, thisArg, 3);\n        while (index-- && callback(array[index], index, array)) {\n          n++;\n        }\n      } else {\n        n = callback;\n        if (n == null || thisArg) {\n          return array ? array[length - 1] : undefined;\n        }\n      }\n      return slice(array, nativeMax(0, length - n));\n    }\n\n    /**\n     * Gets the index at which the last occurrence of `value` is found using strict\n     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used\n     * as the offset from the end of the collection.\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to search.\n     * @param {*} value The value to search for.\n     * @param {number} [fromIndex=array.length-1] The index to search from.\n     * @returns {number} Returns the index of the matched value or `-1`.\n     * @example\n     *\n     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);\n     * // => 4\n     *\n     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);\n     * // => 1\n     */\n    function lastIndexOf(array, value, fromIndex) {\n      var index = array ? array.length : 0;\n      if (typeof fromIndex == 'number') {\n        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;\n      }\n      while (index--) {\n        if (array[index] === value) {\n          return index;\n        }\n      }\n      return -1;\n    }\n\n    /**\n     * Removes all provided values from the given array using strict equality for\n     * comparisons, i.e. `===`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to modify.\n     * @param {...*} [value] The values to remove.\n     * @returns {Array} Returns `array`.\n     * @example\n     *\n     * var array = [1, 2, 3, 1, 2, 3];\n     * _.pull(array, 2, 3);\n     * console.log(array);\n     * // => [1, 1]\n     */\n    function pull(array) {\n      var args = arguments,\n        argsIndex = 0,\n        argsLength = args.length,\n        length = array ? array.length : 0;\n\n      while (++argsIndex < argsLength) {\n        var index = -1,\n          value = args[argsIndex];\n        while (++index < length) {\n          if (array[index] === value) {\n            splice.call(array, index--, 1);\n            length--;\n          }\n        }\n      }\n      return array;\n    }\n\n    /**\n     * Creates an array of numbers (positive and/or negative) progressing from\n     * `start` up to but not including `end`. If `start` is less than `stop` a\n     * zero-length range is created unless a negative `step` is specified.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {number} [start=0] The start of the range.\n     * @param {number} end The end of the range.\n     * @param {number} [step=1] The value to increment or decrement by.\n     * @returns {Array} Returns a new range array.\n     * @example\n     *\n     * _.range(4);\n     * // => [0, 1, 2, 3]\n     *\n     * _.range(1, 5);\n     * // => [1, 2, 3, 4]\n     *\n     * _.range(0, 20, 5);\n     * // => [0, 5, 10, 15]\n     *\n     * _.range(0, -4, -1);\n     * // => [0, -1, -2, -3]\n     *\n     * _.range(1, 4, 0);\n     * // => [1, 1, 1]\n     *\n     * _.range(0);\n     * // => []\n     */\n    function range(start, end, step) {\n      start = +start || 0;\n      step = typeof step == 'number' ? step : (+step || 1);\n\n      if (end == null) {\n        end = start;\n        start = 0;\n      }\n      // use `Array(length)` so engines like Chakra and V8 avoid slower modes\n      // http://youtu.be/XAqIpGU8ZZk#t=17m25s\n      var index = -1,\n        length = nativeMax(0, ceil((end - start) / (step || 1))),\n        result = Array(length);\n\n      while (++index < length) {\n        result[index] = start;\n        start += step;\n      }\n      return result;\n    }\n\n    /**\n     * Removes all elements from an array that the callback returns truey for\n     * and returns an array of removed elements. The callback is bound to `thisArg`\n     * and invoked with three arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to modify.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a new array of removed elements.\n     * @example\n     *\n     * var array = [1, 2, 3, 4, 5, 6];\n     * var evens = _.remove(array, function(num) { return num % 2 == 0; });\n     *\n     * console.log(array);\n     * // => [1, 3, 5]\n     *\n     * console.log(evens);\n     * // => [2, 4, 6]\n     */\n    function remove(array, callback, thisArg) {\n      var index = -1,\n        length = array ? array.length : 0,\n        result = [];\n\n      callback = lodash.createCallback(callback, thisArg, 3);\n      while (++index < length) {\n        var value = array[index];\n        if (callback(value, index, array)) {\n          result.push(value);\n          splice.call(array, index--, 1);\n          length--;\n        }\n      }\n      return result;\n    }\n\n    /**\n     * The opposite of `_.initial` this method gets all but the first element or\n     * first `n` elements of an array. If a callback function is provided elements\n     * at the beginning of the array are excluded from the result as long as the\n     * callback returns truey. The callback is bound to `thisArg` and invoked\n     * with three arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias drop, tail\n     * @category Arrays\n     * @param {Array} array The array to query.\n     * @param {Function|Object|number|string} [callback=1] The function called\n     *  per element or the number of elements to exclude. If a property name or\n     *  object is provided it will be used to create a \"_.pluck\" or \"_.where\"\n     *  style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a slice of `array`.\n     * @example\n     *\n     * _.rest([1, 2, 3]);\n     * // => [2, 3]\n     *\n     * _.rest([1, 2, 3], 2);\n     * // => [3]\n     *\n     * _.rest([1, 2, 3], function(num) {\n     *   return num < 3;\n     * });\n     * // => [3]\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },\n     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },\n     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }\n     * ];\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.pluck(_.rest(characters, 'blocked'), 'name');\n     * // => ['fred', 'pebbles']\n     *\n     * // using \"_.where\" callback shorthand\n     * _.rest(characters, { 'employer': 'slate' });\n     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]\n     */\n    function rest(array, callback, thisArg) {\n      if (typeof callback != 'number' && callback != null) {\n        var n = 0,\n          index = -1,\n          length = array ? array.length : 0;\n\n        callback = lodash.createCallback(callback, thisArg, 3);\n        while (++index < length && callback(array[index], index, array)) {\n          n++;\n        }\n      } else {\n        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);\n      }\n      return slice(array, n);\n    }\n\n    /**\n     * Uses a binary search to determine the smallest index at which a value\n     * should be inserted into a given sorted array in order to maintain the sort\n     * order of the array. If a callback is provided it will be executed for\n     * `value` and each element of `array` to compute their sort ranking. The\n     * callback is bound to `thisArg` and invoked with one argument; (value).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to inspect.\n     * @param {*} value The value to evaluate.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {number} Returns the index at which `value` should be inserted\n     *  into `array`.\n     * @example\n     *\n     * _.sortedIndex([20, 30, 50], 40);\n     * // => 2\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');\n     * // => 2\n     *\n     * var dict = {\n     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }\n     * };\n     *\n     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {\n     *   return dict.wordToNumber[word];\n     * });\n     * // => 2\n     *\n     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {\n     *   return this.wordToNumber[word];\n     * }, dict);\n     * // => 2\n     */\n    function sortedIndex(array, value, callback, thisArg) {\n      var low = 0,\n        high = array ? array.length : low;\n\n      // explicitly reference `identity` for better inlining in Firefox\n      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;\n      value = callback(value);\n\n      while (low < high) {\n        var mid = (low + high) >>> 1;\n        (callback(array[mid]) < value)\n          ? low = mid + 1\n          : high = mid;\n      }\n      return low;\n    }\n\n    /**\n     * Creates an array of unique values, in order, of the provided arrays using\n     * strict equality for comparisons, i.e. `===`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {...Array} [array] The arrays to inspect.\n     * @returns {Array} Returns an array of combined values.\n     * @example\n     *\n     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);\n     * // => [1, 2, 3, 5, 4]\n     */\n    function union() {\n      return baseUniq(baseFlatten(arguments, true, true));\n    }\n\n    /**\n     * Creates a duplicate-value-free version of an array using strict equality\n     * for comparisons, i.e. `===`. If the array is sorted, providing\n     * `true` for `isSorted` will use a faster algorithm. If a callback is provided\n     * each element of `array` is passed through the callback before uniqueness\n     * is computed. The callback is bound to `thisArg` and invoked with three\n     * arguments; (value, index, array).\n     *\n     * If a property name is provided for `callback` the created \"_.pluck\" style\n     * callback will return the property value of the given element.\n     *\n     * If an object is provided for `callback` the created \"_.where\" style callback\n     * will return `true` for elements that have the properties of the given object,\n     * else `false`.\n     *\n     * @static\n     * @memberOf _\n     * @alias unique\n     * @category Arrays\n     * @param {Array} array The array to process.\n     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.\n     * @param {Function|Object|string} [callback=identity] The function called\n     *  per iteration. If a property name or object is provided it will be used\n     *  to create a \"_.pluck\" or \"_.where\" style callback, respectively.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns a duplicate-value-free array.\n     * @example\n     *\n     * _.uniq([1, 2, 1, 3, 1]);\n     * // => [1, 2, 3]\n     *\n     * _.uniq([1, 1, 2, 2, 3], true);\n     * // => [1, 2, 3]\n     *\n     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });\n     * // => ['A', 'b', 'C']\n     *\n     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);\n     * // => [1, 2.5, 3]\n     *\n     * // using \"_.pluck\" callback shorthand\n     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');\n     * // => [{ 'x': 1 }, { 'x': 2 }]\n     */\n    function uniq(array, isSorted, callback, thisArg) {\n      // juggle arguments\n      if (typeof isSorted != 'boolean' && isSorted != null) {\n        thisArg = callback;\n        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;\n        isSorted = false;\n      }\n      if (callback != null) {\n        callback = lodash.createCallback(callback, thisArg, 3);\n      }\n      return baseUniq(array, isSorted, callback);\n    }\n\n    /**\n     * Creates an array excluding all provided values using strict equality for\n     * comparisons, i.e. `===`.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {Array} array The array to filter.\n     * @param {...*} [value] The values to exclude.\n     * @returns {Array} Returns a new array of filtered values.\n     * @example\n     *\n     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);\n     * // => [2, 3, 4]\n     */\n    function without(array) {\n      return baseDifference(array, slice(arguments, 1));\n    }\n\n    /**\n     * Creates an array that is the symmetric difference of the provided arrays.\n     * See http://en.wikipedia.org/wiki/Symmetric_difference.\n     *\n     * @static\n     * @memberOf _\n     * @category Arrays\n     * @param {...Array} [array] The arrays to inspect.\n     * @returns {Array} Returns an array of values.\n     * @example\n     *\n     * _.xor([1, 2, 3], [5, 2, 1, 4]);\n     * // => [3, 5, 4]\n     *\n     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);\n     * // => [1, 4, 5]\n     */\n    function xor() {\n      var index = -1,\n        length = arguments.length;\n\n      while (++index < length) {\n        var array = arguments[index];\n        if (isArray(array) || isArguments(array)) {\n          var result = result\n            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))\n            : array;\n        }\n      }\n      return result || [];\n    }\n\n    /**\n     * Creates an array of grouped elements, the first of which contains the first\n     * elements of the given arrays, the second of which contains the second\n     * elements of the given arrays, and so on.\n     *\n     * @static\n     * @memberOf _\n     * @alias unzip\n     * @category Arrays\n     * @param {...Array} [array] Arrays to process.\n     * @returns {Array} Returns a new array of grouped elements.\n     * @example\n     *\n     * _.zip(['fred', 'barney'], [30, 40], [true, false]);\n     * // => [['fred', 30, true], ['barney', 40, false]]\n     */\n    function zip() {\n      var array = arguments.length > 1 ? arguments : arguments[0],\n        index = -1,\n        length = array ? max(pluck(array, 'length')) : 0,\n        result = Array(length < 0 ? 0 : length);\n\n      while (++index < length) {\n        result[index] = pluck(array, index);\n      }\n      return result;\n    }\n\n    /**\n     * Creates an object composed from arrays of `keys` and `values`. Provide\n     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`\n     * or two arrays, one of `keys` and one of corresponding `values`.\n     *\n     * @static\n     * @memberOf _\n     * @alias object\n     * @category Arrays\n     * @param {Array} keys The array of keys.\n     * @param {Array} [values=[]] The array of values.\n     * @returns {Object} Returns an object composed of the given keys and\n     *  corresponding values.\n     * @example\n     *\n     * _.zipObject(['fred', 'barney'], [30, 40]);\n     * // => { 'fred': 30, 'barney': 40 }\n     */\n    function zipObject(keys, values) {\n      var index = -1,\n        length = keys ? keys.length : 0,\n        result = {};\n\n      if (!values && length && !isArray(keys[0])) {\n        values = [];\n      }\n      while (++index < length) {\n        var key = keys[index];\n        if (values) {\n          result[key] = values[index];\n        } else if (key) {\n          result[key[0]] = key[1];\n        }\n      }\n      return result;\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates a function that executes `func`, with  the `this` binding and\n     * arguments of the created function, only after being called `n` times.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {number} n The number of times the function must be called before\n     *  `func` is executed.\n     * @param {Function} func The function to restrict.\n     * @returns {Function} Returns the new restricted function.\n     * @example\n     *\n     * var saves = ['profile', 'settings'];\n     *\n     * var done = _.after(saves.length, function() {\n     *   console.log('Done saving!');\n     * });\n     *\n     * _.forEach(saves, function(type) {\n     *   asyncSave({ 'type': type, 'complete': done });\n     * });\n     * // => logs 'Done saving!', after all saves have completed\n     */\n    function after(n, func) {\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      return function () {\n        if (--n < 1) {\n          return func.apply(this, arguments);\n        }\n      };\n    }\n\n    /**\n     * Creates a function that, when called, invokes `func` with the `this`\n     * binding of `thisArg` and prepends any additional `bind` arguments to those\n     * provided to the bound function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to bind.\n     * @param {*} [thisArg] The `this` binding of `func`.\n     * @param {...*} [arg] Arguments to be partially applied.\n     * @returns {Function} Returns the new bound function.\n     * @example\n     *\n     * var func = function(greeting) {\n     *   return greeting + ' ' + this.name;\n     * };\n     *\n     * func = _.bind(func, { 'name': 'fred' }, 'hi');\n     * func();\n     * // => 'hi fred'\n     */\n    function bind(func, thisArg) {\n      return arguments.length > 2\n        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)\n        : createWrapper(func, 1, null, null, thisArg);\n    }\n\n    /**\n     * Binds methods of an object to the object itself, overwriting the existing\n     * method. Method names may be specified as individual arguments or as arrays\n     * of method names. If no method names are provided all the function properties\n     * of `object` will be bound.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Object} object The object to bind and assign the bound methods to.\n     * @param {...string} [methodName] The object method names to\n     *  bind, specified as individual method names or arrays of method names.\n     * @returns {Object} Returns `object`.\n     * @example\n     *\n     * var view = {\n     *   'label': 'docs',\n     *   'onClick': function() { console.log('clicked ' + this.label); }\n     * };\n     *\n     * _.bindAll(view);\n     * jQuery('#docs').on('click', view.onClick);\n     * // => logs 'clicked docs', when the button is clicked\n     */\n    function bindAll(object) {\n      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),\n        index = -1,\n        length = funcs.length;\n\n      while (++index < length) {\n        var key = funcs[index];\n        object[key] = createWrapper(object[key], 1, null, null, object);\n      }\n      return object;\n    }\n\n    /**\n     * Creates a function that, when called, invokes the method at `object[key]`\n     * and prepends any additional `bindKey` arguments to those provided to the bound\n     * function. This method differs from `_.bind` by allowing bound functions to\n     * reference methods that will be redefined or don't yet exist.\n     * See http://michaux.ca/articles/lazy-function-definition-pattern.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Object} object The object the method belongs to.\n     * @param {string} key The key of the method.\n     * @param {...*} [arg] Arguments to be partially applied.\n     * @returns {Function} Returns the new bound function.\n     * @example\n     *\n     * var object = {\n     *   'name': 'fred',\n     *   'greet': function(greeting) {\n     *     return greeting + ' ' + this.name;\n     *   }\n     * };\n     *\n     * var func = _.bindKey(object, 'greet', 'hi');\n     * func();\n     * // => 'hi fred'\n     *\n     * object.greet = function(greeting) {\n     *   return greeting + 'ya ' + this.name + '!';\n     * };\n     *\n     * func();\n     * // => 'hiya fred!'\n     */\n    function bindKey(object, key) {\n      return arguments.length > 2\n        ? createWrapper(key, 19, slice(arguments, 2), null, object)\n        : createWrapper(key, 3, null, null, object);\n    }\n\n    /**\n     * Creates a function that is the composition of the provided functions,\n     * where each function consumes the return value of the function that follows.\n     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.\n     * Each function is executed with the `this` binding of the composed function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {...Function} [func] Functions to compose.\n     * @returns {Function} Returns the new composed function.\n     * @example\n     *\n     * var realNameMap = {\n     *   'pebbles': 'penelope'\n     * };\n     *\n     * var format = function(name) {\n     *   name = realNameMap[name.toLowerCase()] || name;\n     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();\n     * };\n     *\n     * var greet = function(formatted) {\n     *   return 'Hiya ' + formatted + '!';\n     * };\n     *\n     * var welcome = _.compose(greet, format);\n     * welcome('pebbles');\n     * // => 'Hiya Penelope!'\n     */\n    function compose() {\n      var funcs = arguments,\n        length = funcs.length;\n\n      while (length--) {\n        if (!isFunction(funcs[length])) {\n          throw new TypeError;\n        }\n      }\n      return function () {\n        var args = arguments,\n          length = funcs.length;\n\n        while (length--) {\n          args = [funcs[length].apply(this, args)];\n        }\n        return args[0];\n      };\n    }\n\n    /**\n     * Creates a function which accepts one or more arguments of `func` that when\n     * invoked either executes `func` returning its result, if all `func` arguments\n     * have been provided, or returns a function that accepts one or more of the\n     * remaining `func` arguments, and so on. The arity of `func` can be specified\n     * if `func.length` is not sufficient.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to curry.\n     * @param {number} [arity=func.length] The arity of `func`.\n     * @returns {Function} Returns the new curried function.\n     * @example\n     *\n     * var curried = _.curry(function(a, b, c) {\n     *   console.log(a + b + c);\n     * });\n     *\n     * curried(1)(2)(3);\n     * // => 6\n     *\n     * curried(1, 2)(3);\n     * // => 6\n     *\n     * curried(1, 2, 3);\n     * // => 6\n     */\n    function curry(func, arity) {\n      arity = typeof arity == 'number' ? arity : (+arity || func.length);\n      return createWrapper(func, 4, null, null, null, arity);\n    }\n\n    /**\n     * Creates a function that will delay the execution of `func` until after\n     * `wait` milliseconds have elapsed since the last time it was invoked.\n     * Provide an options object to indicate that `func` should be invoked on\n     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls\n     * to the debounced function will return the result of the last `func` call.\n     *\n     * Note: If `leading` and `trailing` options are `true` `func` will be called\n     * on the trailing edge of the timeout only if the the debounced function is\n     * invoked more than once during the `wait` timeout.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to debounce.\n     * @param {number} wait The number of milliseconds to delay.\n     * @param {Object} [options] The options object.\n     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.\n     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.\n     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.\n     * @returns {Function} Returns the new debounced function.\n     * @example\n     *\n     * // avoid costly calculations while the window size is in flux\n     * var lazyLayout = _.debounce(calculateLayout, 150);\n     * jQuery(window).on('resize', lazyLayout);\n     *\n     * // execute `sendMail` when the click event is fired, debouncing subsequent calls\n     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {\n     *   'leading': true,\n     *   'trailing': false\n     * });\n     *\n     * // ensure `batchLog` is executed once after 1 second of debounced calls\n     * var source = new EventSource('/stream');\n     * source.addEventListener('message', _.debounce(batchLog, 250, {\n     *   'maxWait': 1000\n     * }, false);\n     */\n    function debounce(func, wait, options) {\n      var args,\n        maxTimeoutId,\n        result,\n        stamp,\n        thisArg,\n        timeoutId,\n        trailingCall,\n        lastCalled = 0,\n        maxWait = false,\n        trailing = true;\n\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      wait = nativeMax(0, wait) || 0;\n      if (options === true) {\n        var leading = true;\n        trailing = false;\n      } else if (isObject(options)) {\n        leading = options.leading;\n        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);\n        trailing = 'trailing' in options ? options.trailing : trailing;\n      }\n      var delayed = function () {\n        var remaining = wait - (now() - stamp);\n        if (remaining <= 0) {\n          if (maxTimeoutId) {\n            clearTimeout(maxTimeoutId);\n          }\n          var isCalled = trailingCall;\n          maxTimeoutId = timeoutId = trailingCall = undefined;\n          if (isCalled) {\n            lastCalled = now();\n            result = func.apply(thisArg, args);\n            if (!timeoutId && !maxTimeoutId) {\n              args = thisArg = null;\n            }\n          }\n        } else {\n          timeoutId = setTimeout(delayed, remaining);\n        }\n      };\n\n      var maxDelayed = function () {\n        if (timeoutId) {\n          clearTimeout(timeoutId);\n        }\n        maxTimeoutId = timeoutId = trailingCall = undefined;\n        if (trailing || (maxWait !== wait)) {\n          lastCalled = now();\n          result = func.apply(thisArg, args);\n          if (!timeoutId && !maxTimeoutId) {\n            args = thisArg = null;\n          }\n        }\n      };\n\n      return function () {\n        args = arguments;\n        stamp = now();\n        thisArg = this;\n        trailingCall = trailing && (timeoutId || !leading);\n\n        if (maxWait === false) {\n          var leadingCall = leading && !timeoutId;\n        } else {\n          if (!maxTimeoutId && !leading) {\n            lastCalled = stamp;\n          }\n          var remaining = maxWait - (stamp - lastCalled),\n            isCalled = remaining <= 0;\n\n          if (isCalled) {\n            if (maxTimeoutId) {\n              maxTimeoutId = clearTimeout(maxTimeoutId);\n            }\n            lastCalled = stamp;\n            result = func.apply(thisArg, args);\n          }\n          else if (!maxTimeoutId) {\n            maxTimeoutId = setTimeout(maxDelayed, remaining);\n          }\n        }\n        if (isCalled && timeoutId) {\n          timeoutId = clearTimeout(timeoutId);\n        }\n        else if (!timeoutId && wait !== maxWait) {\n          timeoutId = setTimeout(delayed, wait);\n        }\n        if (leadingCall) {\n          isCalled = true;\n          result = func.apply(thisArg, args);\n        }\n        if (isCalled && !timeoutId && !maxTimeoutId) {\n          args = thisArg = null;\n        }\n        return result;\n      };\n    }\n\n    /**\n     * Defers executing the `func` function until the current call stack has cleared.\n     * Additional arguments will be provided to `func` when it is invoked.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to defer.\n     * @param {...*} [arg] Arguments to invoke the function with.\n     * @returns {number} Returns the timer id.\n     * @example\n     *\n     * _.defer(function(text) { console.log(text); }, 'deferred');\n     * // logs 'deferred' after one or more milliseconds\n     */\n    function defer(func) {\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      var args = slice(arguments, 1);\n      return setTimeout(function () {\n        func.apply(undefined, args);\n      }, 1);\n    }\n\n    /**\n     * Executes the `func` function after `wait` milliseconds. Additional arguments\n     * will be provided to `func` when it is invoked.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to delay.\n     * @param {number} wait The number of milliseconds to delay execution.\n     * @param {...*} [arg] Arguments to invoke the function with.\n     * @returns {number} Returns the timer id.\n     * @example\n     *\n     * _.delay(function(text) { console.log(text); }, 1000, 'later');\n     * // => logs 'later' after one second\n     */\n    function delay(func, wait) {\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      var args = slice(arguments, 2);\n      return setTimeout(function () {\n        func.apply(undefined, args);\n      }, wait);\n    }\n\n    /**\n     * Creates a function that memoizes the result of `func`. If `resolver` is\n     * provided it will be used to determine the cache key for storing the result\n     * based on the arguments provided to the memoized function. By default, the\n     * first argument provided to the memoized function is used as the cache key.\n     * The `func` is executed with the `this` binding of the memoized function.\n     * The result cache is exposed as the `cache` property on the memoized function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to have its output memoized.\n     * @param {Function} [resolver] A function used to resolve the cache key.\n     * @returns {Function} Returns the new memoizing function.\n     * @example\n     *\n     * var fibonacci = _.memoize(function(n) {\n     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);\n     * });\n     *\n     * fibonacci(9)\n     * // => 34\n     *\n     * var data = {\n     *   'fred': { 'name': 'fred', 'age': 40 },\n     *   'pebbles': { 'name': 'pebbles', 'age': 1 }\n     * };\n     *\n     * // modifying the result cache\n     * var get = _.memoize(function(name) { return data[name]; }, _.identity);\n     * get('pebbles');\n     * // => { 'name': 'pebbles', 'age': 1 }\n     *\n     * get.cache.pebbles.name = 'penelope';\n     * get('pebbles');\n     * // => { 'name': 'penelope', 'age': 1 }\n     */\n    function memoize(func, resolver) {\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      var memoized = function () {\n        var cache = memoized.cache,\n          key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];\n\n        return hasOwnProperty.call(cache, key)\n          ? cache[key]\n          : (cache[key] = func.apply(this, arguments));\n      }\n      memoized.cache = {};\n      return memoized;\n    }\n\n    /**\n     * Creates a function that is restricted to execute `func` once. Repeat calls to\n     * the function will return the value of the first call. The `func` is executed\n     * with the `this` binding of the created function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to restrict.\n     * @returns {Function} Returns the new restricted function.\n     * @example\n     *\n     * var initialize = _.once(createApplication);\n     * initialize();\n     * initialize();\n     * // `initialize` executes `createApplication` once\n     */\n    function once(func) {\n      var ran,\n        result;\n\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      return function () {\n        if (ran) {\n          return result;\n        }\n        ran = true;\n        result = func.apply(this, arguments);\n\n        // clear the `func` variable so the function may be garbage collected\n        func = null;\n        return result;\n      };\n    }\n\n    /**\n     * Creates a function that, when called, invokes `func` with any additional\n     * `partial` arguments prepended to those provided to the new function. This\n     * method is similar to `_.bind` except it does **not** alter the `this` binding.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to partially apply arguments to.\n     * @param {...*} [arg] Arguments to be partially applied.\n     * @returns {Function} Returns the new partially applied function.\n     * @example\n     *\n     * var greet = function(greeting, name) { return greeting + ' ' + name; };\n     * var hi = _.partial(greet, 'hi');\n     * hi('fred');\n     * // => 'hi fred'\n     */\n    function partial(func) {\n      return createWrapper(func, 16, slice(arguments, 1));\n    }\n\n    /**\n     * This method is like `_.partial` except that `partial` arguments are\n     * appended to those provided to the new function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to partially apply arguments to.\n     * @param {...*} [arg] Arguments to be partially applied.\n     * @returns {Function} Returns the new partially applied function.\n     * @example\n     *\n     * var defaultsDeep = _.partialRight(_.merge, _.defaults);\n     *\n     * var options = {\n     *   'variable': 'data',\n     *   'imports': { 'jq': $ }\n     * };\n     *\n     * defaultsDeep(options, _.templateSettings);\n     *\n     * options.variable\n     * // => 'data'\n     *\n     * options.imports\n     * // => { '_': _, 'jq': $ }\n     */\n    function partialRight(func) {\n      return createWrapper(func, 32, null, slice(arguments, 1));\n    }\n\n    /**\n     * Creates a function that, when executed, will only call the `func` function\n     * at most once per every `wait` milliseconds. Provide an options object to\n     * indicate that `func` should be invoked on the leading and/or trailing edge\n     * of the `wait` timeout. Subsequent calls to the throttled function will\n     * return the result of the last `func` call.\n     *\n     * Note: If `leading` and `trailing` options are `true` `func` will be called\n     * on the trailing edge of the timeout only if the the throttled function is\n     * invoked more than once during the `wait` timeout.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {Function} func The function to throttle.\n     * @param {number} wait The number of milliseconds to throttle executions to.\n     * @param {Object} [options] The options object.\n     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.\n     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.\n     * @returns {Function} Returns the new throttled function.\n     * @example\n     *\n     * // avoid excessively updating the position while scrolling\n     * var throttled = _.throttle(updatePosition, 100);\n     * jQuery(window).on('scroll', throttled);\n     *\n     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes\n     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {\n     *   'trailing': false\n     * }));\n     */\n    function throttle(func, wait, options) {\n      var leading = true,\n        trailing = true;\n\n      if (!isFunction(func)) {\n        throw new TypeError;\n      }\n      if (options === false) {\n        leading = false;\n      } else if (isObject(options)) {\n        leading = 'leading' in options ? options.leading : leading;\n        trailing = 'trailing' in options ? options.trailing : trailing;\n      }\n      debounceOptions.leading = leading;\n      debounceOptions.maxWait = wait;\n      debounceOptions.trailing = trailing;\n\n      return debounce(func, wait, debounceOptions);\n    }\n\n    /**\n     * Creates a function that provides `value` to the wrapper function as its\n     * first argument. Additional arguments provided to the function are appended\n     * to those provided to the wrapper function. The wrapper is executed with\n     * the `this` binding of the created function.\n     *\n     * @static\n     * @memberOf _\n     * @category Functions\n     * @param {*} value The value to wrap.\n     * @param {Function} wrapper The wrapper function.\n     * @returns {Function} Returns the new function.\n     * @example\n     *\n     * var p = _.wrap(_.escape, function(func, text) {\n     *   return '<p>' + func(text) + '</p>';\n     * });\n     *\n     * p('Fred, Wilma, & Pebbles');\n     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'\n     */\n    function wrap(value, wrapper) {\n      return createWrapper(wrapper, 16, [value]);\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates a function that returns `value`.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {*} value The value to return from the new function.\n     * @returns {Function} Returns the new function.\n     * @example\n     *\n     * var object = { 'name': 'fred' };\n     * var getter = _.constant(object);\n     * getter() === object;\n     * // => true\n     */\n    function constant(value) {\n      return function () {\n        return value;\n      };\n    }\n\n    /**\n     * Produces a callback bound to an optional `thisArg`. If `func` is a property\n     * name the created callback will return the property value for a given element.\n     * If `func` is an object the created callback will return `true` for elements\n     * that contain the equivalent object properties, otherwise it will return `false`.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {*} [func=identity] The value to convert to a callback.\n     * @param {*} [thisArg] The `this` binding of the created callback.\n     * @param {number} [argCount] The number of arguments the callback accepts.\n     * @returns {Function} Returns a callback function.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * // wrap to create custom callback shorthands\n     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {\n     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);\n     *   return !match ? func(callback, thisArg) : function(object) {\n     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];\n     *   };\n     * });\n     *\n     * _.filter(characters, 'age__gt38');\n     * // => [{ 'name': 'fred', 'age': 40 }]\n     */\n    function createCallback(func, thisArg, argCount) {\n      var type = typeof func;\n      if (func == null || type == 'function') {\n        return baseCreateCallback(func, thisArg, argCount);\n      }\n      // handle \"_.pluck\" style callback shorthands\n      if (type != 'object') {\n        return property(func);\n      }\n      var props = keys(func),\n        key = props[0],\n        a = func[key];\n\n      // handle \"_.where\" style callback shorthands\n      if (props.length == 1 && a === a && !isObject(a)) {\n        // fast path the common case of providing an object with a single\n        // property containing a primitive value\n        return function (object) {\n          var b = object[key];\n          return a === b && (a !== 0 || (1 / a == 1 / b));\n        };\n      }\n      return function (object) {\n        var length = props.length,\n          result = false;\n\n        while (length--) {\n          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {\n            break;\n          }\n        }\n        return result;\n      };\n    }\n\n    /**\n     * Converts the characters `&`, `<`, `>`, `\"`, and `'` in `string` to their\n     * corresponding HTML entities.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} string The string to escape.\n     * @returns {string} Returns the escaped string.\n     * @example\n     *\n     * _.escape('Fred, Wilma, & Pebbles');\n     * // => 'Fred, Wilma, &amp; Pebbles'\n     */\n    function escape(string) {\n      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);\n    }\n\n    /**\n     * This method returns the first argument provided to it.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {*} value Any value.\n     * @returns {*} Returns `value`.\n     * @example\n     *\n     * var object = { 'name': 'fred' };\n     * _.identity(object) === object;\n     * // => true\n     */\n    function identity(value) {\n      return value;\n    }\n\n    /**\n     * Adds function properties of a source object to the destination object.\n     * If `object` is a function methods will be added to its prototype as well.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {Function|Object} [object=lodash] object The destination object.\n     * @param {Object} source The object of functions to add.\n     * @param {Object} [options] The options object.\n     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.\n     * @example\n     *\n     * function capitalize(string) {\n     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();\n     * }\n     *\n     * _.mixin({ 'capitalize': capitalize });\n     * _.capitalize('fred');\n     * // => 'Fred'\n     *\n     * _('fred').capitalize().value();\n     * // => 'Fred'\n     *\n     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });\n     * _('fred').capitalize();\n     * // => 'Fred'\n     */\n    function mixin(object, source, options) {\n      var chain = true,\n        methodNames = source && functions(source);\n\n      if (!source || (!options && !methodNames.length)) {\n        if (options == null) {\n          options = source;\n        }\n        ctor = lodashWrapper;\n        source = object;\n        object = lodash;\n        methodNames = functions(source);\n      }\n      if (options === false) {\n        chain = false;\n      } else if (isObject(options) && 'chain' in options) {\n        chain = options.chain;\n      }\n      var ctor = object,\n        isFunc = isFunction(ctor);\n\n      forEach(methodNames, function (methodName) {\n        var func = object[methodName] = source[methodName];\n        if (isFunc) {\n          ctor.prototype[methodName] = function () {\n            var chainAll = this.__chain__,\n              value = this.__wrapped__,\n              args = [value];\n\n            push.apply(args, arguments);\n            var result = func.apply(object, args);\n            if (chain || chainAll) {\n              if (value === result && isObject(result)) {\n                return this;\n              }\n              result = new ctor(result);\n              result.__chain__ = chainAll;\n            }\n            return result;\n          };\n        }\n      });\n    }\n\n    /**\n     * Reverts the '_' variable to its previous value and returns a reference to\n     * the `lodash` function.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @returns {Function} Returns the `lodash` function.\n     * @example\n     *\n     * var lodash = _.noConflict();\n     */\n    function noConflict() {\n      context._ = oldDash;\n      return this;\n    }\n\n    /**\n     * A no-operation function.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @example\n     *\n     * var object = { 'name': 'fred' };\n     * _.noop(object) === undefined;\n     * // => true\n     */\n    function noop() {\n      // no operation performed\n    }\n\n    /**\n     * Gets the number of milliseconds that have elapsed since the Unix epoch\n     * (1 January 1970 00:00:00 UTC).\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @example\n     *\n     * var stamp = _.now();\n     * _.defer(function() { console.log(_.now() - stamp); });\n     * // => logs the number of milliseconds it took for the deferred function to be called\n     */\n    var now = isNative(now = Date.now) && now || function () {\n      return new Date().getTime();\n    };\n\n    /**\n     * Converts the given value into an integer of the specified radix.\n     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the\n     * `value` is a hexadecimal, in which case a `radix` of `16` is used.\n     *\n     * Note: This method avoids differences in native ES3 and ES5 `parseInt`\n     * implementations. See http://es5.github.io/#E.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} value The value to parse.\n     * @param {number} [radix] The radix used to interpret the value to parse.\n     * @returns {number} Returns the new integer value.\n     * @example\n     *\n     * _.parseInt('08');\n     * // => 8\n     */\n    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function (value, radix) {\n      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`\n      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);\n    };\n\n    /**\n     * Creates a \"_.pluck\" style function, which returns the `key` value of a\n     * given object.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} key The name of the property to retrieve.\n     * @returns {Function} Returns the new function.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'fred',   'age': 40 },\n     *   { 'name': 'barney', 'age': 36 }\n     * ];\n     *\n     * var getName = _.property('name');\n     *\n     * _.map(characters, getName);\n     * // => ['barney', 'fred']\n     *\n     * _.sortBy(characters, getName);\n     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]\n     */\n    function property(key) {\n      return function (object) {\n        return object[key];\n      };\n    }\n\n    /**\n     * Produces a random number between `min` and `max` (inclusive). If only one\n     * argument is provided a number between `0` and the given number will be\n     * returned. If `floating` is truey or either `min` or `max` are floats a\n     * floating-point number will be returned instead of an integer.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {number} [min=0] The minimum possible value.\n     * @param {number} [max=1] The maximum possible value.\n     * @param {boolean} [floating=false] Specify returning a floating-point number.\n     * @returns {number} Returns a random number.\n     * @example\n     *\n     * _.random(0, 5);\n     * // => an integer between 0 and 5\n     *\n     * _.random(5);\n     * // => also an integer between 0 and 5\n     *\n     * _.random(5, true);\n     * // => a floating-point number between 0 and 5\n     *\n     * _.random(1.2, 5.2);\n     * // => a floating-point number between 1.2 and 5.2\n     */\n    function random(min, max, floating) {\n      var noMin = min == null,\n        noMax = max == null;\n\n      if (floating == null) {\n        if (typeof min == 'boolean' && noMax) {\n          floating = min;\n          min = 1;\n        }\n        else if (!noMax && typeof max == 'boolean') {\n          floating = max;\n          noMax = true;\n        }\n      }\n      if (noMin && noMax) {\n        max = 1;\n      }\n      min = +min || 0;\n      if (noMax) {\n        max = min;\n        min = 0;\n      } else {\n        max = +max || 0;\n      }\n      if (floating || min % 1 || max % 1) {\n        var rand = nativeRandom();\n        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max);\n      }\n      return baseRandom(min, max);\n    }\n\n    /**\n     * Resolves the value of property `key` on `object`. If `key` is a function\n     * it will be invoked with the `this` binding of `object` and its result returned,\n     * else the property value is returned. If `object` is falsey then `undefined`\n     * is returned.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {Object} object The object to inspect.\n     * @param {string} key The name of the property to resolve.\n     * @returns {*} Returns the resolved value.\n     * @example\n     *\n     * var object = {\n     *   'cheese': 'crumpets',\n     *   'stuff': function() {\n     *     return 'nonsense';\n     *   }\n     * };\n     *\n     * _.result(object, 'cheese');\n     * // => 'crumpets'\n     *\n     * _.result(object, 'stuff');\n     * // => 'nonsense'\n     */\n    function result(object, key) {\n      if (object) {\n        var value = object[key];\n        return isFunction(value) ? object[key]() : value;\n      }\n    }\n\n    /**\n     * A micro-templating method that handles arbitrary delimiters, preserves\n     * whitespace, and correctly escapes quotes within interpolated code.\n     *\n     * Note: In the development build, `_.template` utilizes sourceURLs for easier\n     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl\n     *\n     * For more information on precompiling templates see:\n     * http://lodash.com/custom-builds\n     *\n     * For more information on Chrome extension sandboxes see:\n     * http://developer.chrome.com/stable/extensions/sandboxingEval.html\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} text The template text.\n     * @param {Object} data The data object used to populate the text.\n     * @param {Object} [options] The options object.\n     * @param {RegExp} [options.escape] The \"escape\" delimiter.\n     * @param {RegExp} [options.evaluate] The \"evaluate\" delimiter.\n     * @param {Object} [options.imports] An object to import into the template as local variables.\n     * @param {RegExp} [options.interpolate] The \"interpolate\" delimiter.\n     * @param {string} [sourceURL] The sourceURL of the template's compiled source.\n     * @param {string} [variable] The data object variable name.\n     * @returns {Function|string} Returns a compiled function when no `data` object\n     *  is given, else it returns the interpolated text.\n     * @example\n     *\n     * // using the \"interpolate\" delimiter to create a compiled template\n     * var compiled = _.template('hello <%= name %>');\n     * compiled({ 'name': 'fred' });\n     * // => 'hello fred'\n     *\n     * // using the \"escape\" delimiter to escape HTML in data property values\n     * _.template('<b><%- value %></b>', { 'value': '<script>' });\n     * // => '<b>&lt;script&gt;</b>'\n     *\n     * // using the \"evaluate\" delimiter to generate HTML\n     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';\n     * _.template(list, { 'people': ['fred', 'barney'] });\n     * // => '<li>fred</li><li>barney</li>'\n     *\n     * // using the ES6 delimiter as an alternative to the default \"interpolate\" delimiter\n     * _.template('hello ${ name }', { 'name': 'pebbles' });\n     * // => 'hello pebbles'\n     *\n     * // using the internal `print` function in \"evaluate\" delimiters\n     * _.template('<% print(\"hello \" + name); %>!', { 'name': 'barney' });\n     * // => 'hello barney!'\n     *\n     * // using a custom template delimiters\n     * _.templateSettings = {\n     *   'interpolate': /{{([\\s\\S]+?)}}/g\n     * };\n     *\n     * _.template('hello {{ name }}!', { 'name': 'mustache' });\n     * // => 'hello mustache!'\n     *\n     * // using the `imports` option to import jQuery\n     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';\n     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });\n     * // => '<li>fred</li><li>barney</li>'\n     *\n     * // using the `sourceURL` option to specify a custom sourceURL for the template\n     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });\n     * compiled(data);\n     * // => find the source of \"greeting.jst\" under the Sources tab or Resources panel of the web inspector\n     *\n     * // using the `variable` option to ensure a with-statement isn't used in the compiled template\n     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });\n     * compiled.source;\n     * // => function(data) {\n     *   var __t, __p = '', __e = _.escape;\n     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';\n     *   return __p;\n     * }\n     *\n     * // using the `source` property to inline compiled templates for meaningful\n     * // line numbers in error messages and a stack trace\n     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\\\n     *   var JST = {\\\n     *     \"main\": ' + _.template(mainText).source + '\\\n     *   };\\\n     * ');\n     */\n    function template(text, data, options) {\n      // based on John Resig's `tmpl` implementation\n      // http://ejohn.org/blog/javascript-micro-templating/\n      // and Laura Doktorova's doT.js\n      // https://github.com/olado/doT\n      var settings = lodash.templateSettings;\n      text = String(text || '');\n\n      // avoid missing dependencies when `iteratorTemplate` is not defined\n      options = defaults({}, options, settings);\n\n      var imports = defaults({}, options.imports, settings.imports),\n        importsKeys = keys(imports),\n        importsValues = values(imports);\n\n      var isEvaluating,\n        index = 0,\n        interpolate = options.interpolate || reNoMatch,\n        source = \"__p += '\";\n\n      // compile the regexp to match each delimiter\n      var reDelimiters = RegExp(\n          (options.escape || reNoMatch).source + '|' +\n          interpolate.source + '|' +\n          (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +\n          (options.evaluate || reNoMatch).source + '|$'\n        , 'g');\n\n      text.replace(reDelimiters, function (match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {\n        interpolateValue || (interpolateValue = esTemplateValue);\n\n        // escape characters that cannot be included in string literals\n        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);\n\n        // replace delimiters with snippets\n        if (escapeValue) {\n          source += \"' +\\n__e(\" + escapeValue + \") +\\n'\";\n        }\n        if (evaluateValue) {\n          isEvaluating = true;\n          source += \"';\\n\" + evaluateValue + \";\\n__p += '\";\n        }\n        if (interpolateValue) {\n          source += \"' +\\n((__t = (\" + interpolateValue + \")) == null ? '' : __t) +\\n'\";\n        }\n        index = offset + match.length;\n\n        // the JS engine embedded in Adobe products requires returning the `match`\n        // string in order to produce the correct `offset` value\n        return match;\n      });\n\n      source += \"';\\n\";\n\n      // if `variable` is not specified, wrap a with-statement around the generated\n      // code to add the data object to the top of the scope chain\n      var variable = options.variable,\n        hasVariable = variable;\n\n      if (!hasVariable) {\n        variable = 'obj';\n        source = 'with (' + variable + ') {\\n' + source + '\\n}\\n';\n      }\n      // cleanup code by stripping empty strings\n      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)\n        .replace(reEmptyStringMiddle, '$1')\n        .replace(reEmptyStringTrailing, '$1;');\n\n      // frame code as the function body\n      source = 'function(' + variable + ') {\\n' +\n        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\\n') +\n        \"var __t, __p = '', __e = _.escape\" +\n        (isEvaluating\n          ? ', __j = Array.prototype.join;\\n' +\n          \"function print() { __p += __j.call(arguments, '') }\\n\"\n          : ';\\n'\n          ) +\n        source +\n        'return __p\\n}';\n\n      // Use a sourceURL for easier debugging.\n      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl\n      var sourceURL = '\\n/*\\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\\n*/';\n\n      try {\n        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);\n      } catch (e) {\n        e.source = source;\n        throw e;\n      }\n      if (data) {\n        return result(data);\n      }\n      // provide the compiled function's source by its `toString` method, in\n      // supported environments, or the `source` property as a convenience for\n      // inlining compiled templates during the build process\n      result.source = source;\n      return result;\n    }\n\n    /**\n     * Executes the callback `n` times, returning an array of the results\n     * of each callback execution. The callback is bound to `thisArg` and invoked\n     * with one argument; (index).\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {number} n The number of times to execute the callback.\n     * @param {Function} callback The function called per iteration.\n     * @param {*} [thisArg] The `this` binding of `callback`.\n     * @returns {Array} Returns an array of the results of each `callback` execution.\n     * @example\n     *\n     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));\n     * // => [3, 6, 4]\n     *\n     * _.times(3, function(n) { mage.castSpell(n); });\n     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively\n     *\n     * _.times(3, function(n) { this.cast(n); }, mage);\n     * // => also calls `mage.castSpell(n)` three times\n     */\n    function times(n, callback, thisArg) {\n      n = (n = +n) > -1 ? n : 0;\n      var index = -1,\n        result = Array(n);\n\n      callback = baseCreateCallback(callback, thisArg, 1);\n      while (++index < n) {\n        result[index] = callback(index);\n      }\n      return result;\n    }\n\n    /**\n     * The inverse of `_.escape` this method converts the HTML entities\n     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their\n     * corresponding characters.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} string The string to unescape.\n     * @returns {string} Returns the unescaped string.\n     * @example\n     *\n     * _.unescape('Fred, Barney &amp; Pebbles');\n     * // => 'Fred, Barney & Pebbles'\n     */\n    function unescape(string) {\n      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);\n    }\n\n    /**\n     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.\n     *\n     * @static\n     * @memberOf _\n     * @category Utilities\n     * @param {string} [prefix] The value to prefix the ID with.\n     * @returns {string} Returns the unique ID.\n     * @example\n     *\n     * _.uniqueId('contact_');\n     * // => 'contact_104'\n     *\n     * _.uniqueId();\n     * // => '105'\n     */\n    function uniqueId(prefix) {\n      var id = ++idCounter;\n      return String(prefix == null ? '' : prefix) + id;\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * Creates a `lodash` object that wraps the given value with explicit\n     * method chaining enabled.\n     *\n     * @static\n     * @memberOf _\n     * @category Chaining\n     * @param {*} value The value to wrap.\n     * @returns {Object} Returns the wrapper object.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney',  'age': 36 },\n     *   { 'name': 'fred',    'age': 40 },\n     *   { 'name': 'pebbles', 'age': 1 }\n     * ];\n     *\n     * var youngest = _.chain(characters)\n     *     .sortBy('age')\n     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })\n     *     .first()\n     *     .value();\n     * // => 'pebbles is 1'\n     */\n    function chain(value) {\n      value = new lodashWrapper(value);\n      value.__chain__ = true;\n      return value;\n    }\n\n    /**\n     * Invokes `interceptor` with the `value` as the first argument and then\n     * returns `value`. The purpose of this method is to \"tap into\" a method\n     * chain in order to perform operations on intermediate results within\n     * the chain.\n     *\n     * @static\n     * @memberOf _\n     * @category Chaining\n     * @param {*} value The value to provide to `interceptor`.\n     * @param {Function} interceptor The function to invoke.\n     * @returns {*} Returns `value`.\n     * @example\n     *\n     * _([1, 2, 3, 4])\n     *  .tap(function(array) { array.pop(); })\n     *  .reverse()\n     *  .value();\n     * // => [3, 2, 1]\n     */\n    function tap(value, interceptor) {\n      interceptor(value);\n      return value;\n    }\n\n    /**\n     * Enables explicit method chaining on the wrapper object.\n     *\n     * @name chain\n     * @memberOf _\n     * @category Chaining\n     * @returns {*} Returns the wrapper object.\n     * @example\n     *\n     * var characters = [\n     *   { 'name': 'barney', 'age': 36 },\n     *   { 'name': 'fred',   'age': 40 }\n     * ];\n     *\n     * // without explicit chaining\n     * _(characters).first();\n     * // => { 'name': 'barney', 'age': 36 }\n     *\n     * // with explicit chaining\n     * _(characters).chain()\n     *   .first()\n     *   .pick('age')\n     *   .value();\n     * // => { 'age': 36 }\n     */\n    function wrapperChain() {\n      this.__chain__ = true;\n      return this;\n    }\n\n    /**\n     * Produces the `toString` result of the wrapped value.\n     *\n     * @name toString\n     * @memberOf _\n     * @category Chaining\n     * @returns {string} Returns the string result.\n     * @example\n     *\n     * _([1, 2, 3]).toString();\n     * // => '1,2,3'\n     */\n    function wrapperToString() {\n      return String(this.__wrapped__);\n    }\n\n    /**\n     * Extracts the wrapped value.\n     *\n     * @name valueOf\n     * @memberOf _\n     * @alias value\n     * @category Chaining\n     * @returns {*} Returns the wrapped value.\n     * @example\n     *\n     * _([1, 2, 3]).valueOf();\n     * // => [1, 2, 3]\n     */\n    function wrapperValueOf() {\n      return this.__wrapped__;\n    }\n\n    /*--------------------------------------------------------------------------*/\n\n    // add functions that return wrapped values when chaining\n    lodash.after = after;\n    lodash.assign = assign;\n    lodash.at = at;\n    lodash.bind = bind;\n    lodash.bindAll = bindAll;\n    lodash.bindKey = bindKey;\n    lodash.chain = chain;\n    lodash.compact = compact;\n    lodash.compose = compose;\n    lodash.constant = constant;\n    lodash.countBy = countBy;\n    lodash.create = create;\n    lodash.createCallback = createCallback;\n    lodash.curry = curry;\n    lodash.debounce = debounce;\n    lodash.defaults = defaults;\n    lodash.defer = defer;\n    lodash.delay = delay;\n    lodash.difference = difference;\n    lodash.filter = filter;\n    lodash.flatten = flatten;\n    lodash.forEach = forEach;\n    lodash.forEachRight = forEachRight;\n    lodash.forIn = forIn;\n    lodash.forInRight = forInRight;\n    lodash.forOwn = forOwn;\n    lodash.forOwnRight = forOwnRight;\n    lodash.functions = functions;\n    lodash.groupBy = groupBy;\n    lodash.indexBy = indexBy;\n    lodash.initial = initial;\n    lodash.intersection = intersection;\n    lodash.invert = invert;\n    lodash.invoke = invoke;\n    lodash.keys = keys;\n    lodash.map = map;\n    lodash.mapValues = mapValues;\n    lodash.max = max;\n    lodash.memoize = memoize;\n    lodash.merge = merge;\n    lodash.min = min;\n    lodash.omit = omit;\n    lodash.once = once;\n    lodash.pairs = pairs;\n    lodash.partial = partial;\n    lodash.partialRight = partialRight;\n    lodash.pick = pick;\n    lodash.pluck = pluck;\n    lodash.property = property;\n    lodash.pull = pull;\n    lodash.range = range;\n    lodash.reject = reject;\n    lodash.remove = remove;\n    lodash.rest = rest;\n    lodash.shuffle = shuffle;\n    lodash.sortBy = sortBy;\n    lodash.tap = tap;\n    lodash.throttle = throttle;\n    lodash.times = times;\n    lodash.toArray = toArray;\n    lodash.transform = transform;\n    lodash.union = union;\n    lodash.uniq = uniq;\n    lodash.values = values;\n    lodash.where = where;\n    lodash.without = without;\n    lodash.wrap = wrap;\n    lodash.xor = xor;\n    lodash.zip = zip;\n    lodash.zipObject = zipObject;\n\n    // add aliases\n    lodash.collect = map;\n    lodash.drop = rest;\n    lodash.each = forEach;\n    lodash.eachRight = forEachRight;\n    lodash.extend = assign;\n    lodash.methods = functions;\n    lodash.object = zipObject;\n    lodash.select = filter;\n    lodash.tail = rest;\n    lodash.unique = uniq;\n    lodash.unzip = zip;\n\n    // add functions to `lodash.prototype`\n    mixin(lodash);\n\n    /*--------------------------------------------------------------------------*/\n\n    // add functions that return unwrapped values when chaining\n    lodash.clone = clone;\n    lodash.cloneDeep = cloneDeep;\n    lodash.contains = contains;\n    lodash.escape = escape;\n    lodash.every = every;\n    lodash.find = find;\n    lodash.findIndex = findIndex;\n    lodash.findKey = findKey;\n    lodash.findLast = findLast;\n    lodash.findLastIndex = findLastIndex;\n    lodash.findLastKey = findLastKey;\n    lodash.has = has;\n    lodash.identity = identity;\n    lodash.indexOf = indexOf;\n    lodash.isArguments = isArguments;\n    lodash.isArray = isArray;\n    lodash.isBoolean = isBoolean;\n    lodash.isDate = isDate;\n    lodash.isElement = isElement;\n    lodash.isEmpty = isEmpty;\n    lodash.isEqual = isEqual;\n    lodash.isFinite = isFinite;\n    lodash.isFunction = isFunction;\n    lodash.isNaN = isNaN;\n    lodash.isNull = isNull;\n    lodash.isNumber = isNumber;\n    lodash.isObject = isObject;\n    lodash.isPlainObject = isPlainObject;\n    lodash.isRegExp = isRegExp;\n    lodash.isString = isString;\n    lodash.isUndefined = isUndefined;\n    lodash.lastIndexOf = lastIndexOf;\n    lodash.mixin = mixin;\n    lodash.noConflict = noConflict;\n    lodash.noop = noop;\n    lodash.now = now;\n    lodash.parseInt = parseInt;\n    lodash.random = random;\n    lodash.reduce = reduce;\n    lodash.reduceRight = reduceRight;\n    lodash.result = result;\n    lodash.runInContext = runInContext;\n    lodash.size = size;\n    lodash.some = some;\n    lodash.sortedIndex = sortedIndex;\n    lodash.template = template;\n    lodash.unescape = unescape;\n    lodash.uniqueId = uniqueId;\n\n    // add aliases\n    lodash.all = every;\n    lodash.any = some;\n    lodash.detect = find;\n    lodash.findWhere = find;\n    lodash.foldl = reduce;\n    lodash.foldr = reduceRight;\n    lodash.include = contains;\n    lodash.inject = reduce;\n\n    mixin(function () {\n      var source = {}\n      forOwn(lodash, function (func, methodName) {\n        if (!lodash.prototype[methodName]) {\n          source[methodName] = func;\n        }\n      });\n      return source;\n    }(), false);\n\n    /*--------------------------------------------------------------------------*/\n\n    // add functions capable of returning wrapped and unwrapped values when chaining\n    lodash.first = first;\n    lodash.last = last;\n    lodash.sample = sample;\n\n    // add aliases\n    lodash.take = first;\n    lodash.head = first;\n\n    forOwn(lodash, function (func, methodName) {\n      var callbackable = methodName !== 'sample';\n      if (!lodash.prototype[methodName]) {\n        lodash.prototype[methodName] = function (n, guard) {\n          var chainAll = this.__chain__,\n            result = func(this.__wrapped__, n, guard);\n\n          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))\n            ? result\n            : new lodashWrapper(result, chainAll);\n        };\n      }\n    });\n\n    /*--------------------------------------------------------------------------*/\n\n    /**\n     * The semantic version number.\n     *\n     * @static\n     * @memberOf _\n     * @type string\n     */\n    lodash.VERSION = '2.4.1';\n\n    // add \"Chaining\" functions to the wrapper\n    lodash.prototype.chain = wrapperChain;\n    lodash.prototype.toString = wrapperToString;\n    lodash.prototype.value = wrapperValueOf;\n    lodash.prototype.valueOf = wrapperValueOf;\n\n    // add `Array` functions that return unwrapped values\n    baseEach(['join', 'pop', 'shift'], function (methodName) {\n      var func = arrayRef[methodName];\n      lodash.prototype[methodName] = function () {\n        var chainAll = this.__chain__,\n          result = func.apply(this.__wrapped__, arguments);\n\n        return chainAll\n          ? new lodashWrapper(result, chainAll)\n          : result;\n      };\n    });\n\n    // add `Array` functions that return the existing wrapped value\n    baseEach(['push', 'reverse', 'sort', 'unshift'], function (methodName) {\n      var func = arrayRef[methodName];\n      lodash.prototype[methodName] = function () {\n        func.apply(this.__wrapped__, arguments);\n        return this;\n      };\n    });\n\n    // add `Array` functions that return new wrapped values\n    baseEach(['concat', 'slice', 'splice'], function (methodName) {\n      var func = arrayRef[methodName];\n      lodash.prototype[methodName] = function () {\n        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);\n      };\n    });\n\n    // avoid array-like object bugs with `Array#shift` and `Array#splice`\n    // in IE < 9, Firefox < 10, Narwhal, and RingoJS\n    if (!support.spliceObjects) {\n      baseEach(['pop', 'shift', 'splice'], function (methodName) {\n        var func = arrayRef[methodName],\n          isSplice = methodName == 'splice';\n\n        lodash.prototype[methodName] = function () {\n          var chainAll = this.__chain__,\n            value = this.__wrapped__,\n            result = func.apply(value, arguments);\n\n          if (value.length === 0) {\n            delete value[0];\n          }\n          return (chainAll || isSplice)\n            ? new lodashWrapper(result, chainAll)\n            : result;\n        };\n      });\n    }\n\n    return lodash;\n  }\n\n  /*--------------------------------------------------------------------------*/\n\n  // expose Lo-Dash\n  var _ = runInContext();\n\n  // some AMD build optimizers like r.js check for condition patterns like the following:\n  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {\n    // Expose Lo-Dash to the global object even when an AMD loader is present in\n    // case Lo-Dash is loaded with a RequireJS shim config.\n    // See http://requirejs.org/docs/api.html#config-shim\n    root._ = _;\n\n    // define as an anonymous module so, through path mapping, it can be\n    // referenced as the \"underscore\" module\n    define(function () {\n      return _;\n    });\n  }\n  // check for `exports` after `define` in case a build optimizer adds an `exports` object\n  else if (freeExports && freeModule) {\n    // in Node.js or RingoJS\n    if (moduleExports) {\n      (freeModule.exports = _)._ = _;\n    }\n    // in Narwhal or Rhino -require\n    else {\n      freeExports._ = _;\n    }\n  }\n  else {\n    // in a browser or Rhino\n    root._ = _;\n  }\n}.call(this));\n"
  },
  {
    "path": "public/libs/markdownit.js",
    "content": "/*! markdown-it 3.0.3 https://github.com//markdown-it/markdown-it @license MIT */!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof define&&define.amd)define([],e);else{var f;\"undefined\"!=typeof window?f=window:\"undefined\"!=typeof global?f=global:\"undefined\"!=typeof self&&(f=self),f.markdownit=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n// List of valid entities\n//\n// Generate with ./support/entities.js script\n//\n'use strict';\n\n/*eslint quotes:0*/\nmodule.exports = {\n  \"Aacute\":\"\\u00C1\",\n  \"aacute\":\"\\u00E1\",\n  \"Abreve\":\"\\u0102\",\n  \"abreve\":\"\\u0103\",\n  \"ac\":\"\\u223E\",\n  \"acd\":\"\\u223F\",\n  \"acE\":\"\\u223E\\u0333\",\n  \"Acirc\":\"\\u00C2\",\n  \"acirc\":\"\\u00E2\",\n  \"acute\":\"\\u00B4\",\n  \"Acy\":\"\\u0410\",\n  \"acy\":\"\\u0430\",\n  \"AElig\":\"\\u00C6\",\n  \"aelig\":\"\\u00E6\",\n  \"af\":\"\\u2061\",\n  \"Afr\":\"\\uD835\\uDD04\",\n  \"afr\":\"\\uD835\\uDD1E\",\n  \"Agrave\":\"\\u00C0\",\n  \"agrave\":\"\\u00E0\",\n  \"alefsym\":\"\\u2135\",\n  \"aleph\":\"\\u2135\",\n  \"Alpha\":\"\\u0391\",\n  \"alpha\":\"\\u03B1\",\n  \"Amacr\":\"\\u0100\",\n  \"amacr\":\"\\u0101\",\n  \"amalg\":\"\\u2A3F\",\n  \"AMP\":\"\\u0026\",\n  \"amp\":\"\\u0026\",\n  \"And\":\"\\u2A53\",\n  \"and\":\"\\u2227\",\n  \"andand\":\"\\u2A55\",\n  \"andd\":\"\\u2A5C\",\n  \"andslope\":\"\\u2A58\",\n  \"andv\":\"\\u2A5A\",\n  \"ang\":\"\\u2220\",\n  \"ange\":\"\\u29A4\",\n  \"angle\":\"\\u2220\",\n  \"angmsd\":\"\\u2221\",\n  \"angmsdaa\":\"\\u29A8\",\n  \"angmsdab\":\"\\u29A9\",\n  \"angmsdac\":\"\\u29AA\",\n  \"angmsdad\":\"\\u29AB\",\n  \"angmsdae\":\"\\u29AC\",\n  \"angmsdaf\":\"\\u29AD\",\n  \"angmsdag\":\"\\u29AE\",\n  \"angmsdah\":\"\\u29AF\",\n  \"angrt\":\"\\u221F\",\n  \"angrtvb\":\"\\u22BE\",\n  \"angrtvbd\":\"\\u299D\",\n  \"angsph\":\"\\u2222\",\n  \"angst\":\"\\u00C5\",\n  \"angzarr\":\"\\u237C\",\n  \"Aogon\":\"\\u0104\",\n  \"aogon\":\"\\u0105\",\n  \"Aopf\":\"\\uD835\\uDD38\",\n  \"aopf\":\"\\uD835\\uDD52\",\n  \"ap\":\"\\u2248\",\n  \"apacir\":\"\\u2A6F\",\n  \"apE\":\"\\u2A70\",\n  \"ape\":\"\\u224A\",\n  \"apid\":\"\\u224B\",\n  \"apos\":\"\\u0027\",\n  \"ApplyFunction\":\"\\u2061\",\n  \"approx\":\"\\u2248\",\n  \"approxeq\":\"\\u224A\",\n  \"Aring\":\"\\u00C5\",\n  \"aring\":\"\\u00E5\",\n  \"Ascr\":\"\\uD835\\uDC9C\",\n  \"ascr\":\"\\uD835\\uDCB6\",\n  \"Assign\":\"\\u2254\",\n  \"ast\":\"\\u002A\",\n  \"asymp\":\"\\u2248\",\n  \"asympeq\":\"\\u224D\",\n  \"Atilde\":\"\\u00C3\",\n  \"atilde\":\"\\u00E3\",\n  \"Auml\":\"\\u00C4\",\n  \"auml\":\"\\u00E4\",\n  \"awconint\":\"\\u2233\",\n  \"awint\":\"\\u2A11\",\n  \"backcong\":\"\\u224C\",\n  \"backepsilon\":\"\\u03F6\",\n  \"backprime\":\"\\u2035\",\n  \"backsim\":\"\\u223D\",\n  \"backsimeq\":\"\\u22CD\",\n  \"Backslash\":\"\\u2216\",\n  \"Barv\":\"\\u2AE7\",\n  \"barvee\":\"\\u22BD\",\n  \"Barwed\":\"\\u2306\",\n  \"barwed\":\"\\u2305\",\n  \"barwedge\":\"\\u2305\",\n  \"bbrk\":\"\\u23B5\",\n  \"bbrktbrk\":\"\\u23B6\",\n  \"bcong\":\"\\u224C\",\n  \"Bcy\":\"\\u0411\",\n  \"bcy\":\"\\u0431\",\n  \"bdquo\":\"\\u201E\",\n  \"becaus\":\"\\u2235\",\n  \"Because\":\"\\u2235\",\n  \"because\":\"\\u2235\",\n  \"bemptyv\":\"\\u29B0\",\n  \"bepsi\":\"\\u03F6\",\n  \"bernou\":\"\\u212C\",\n  \"Bernoullis\":\"\\u212C\",\n  \"Beta\":\"\\u0392\",\n  \"beta\":\"\\u03B2\",\n  \"beth\":\"\\u2136\",\n  \"between\":\"\\u226C\",\n  \"Bfr\":\"\\uD835\\uDD05\",\n  \"bfr\":\"\\uD835\\uDD1F\",\n  \"bigcap\":\"\\u22C2\",\n  \"bigcirc\":\"\\u25EF\",\n  \"bigcup\":\"\\u22C3\",\n  \"bigodot\":\"\\u2A00\",\n  \"bigoplus\":\"\\u2A01\",\n  \"bigotimes\":\"\\u2A02\",\n  \"bigsqcup\":\"\\u2A06\",\n  \"bigstar\":\"\\u2605\",\n  \"bigtriangledown\":\"\\u25BD\",\n  \"bigtriangleup\":\"\\u25B3\",\n  \"biguplus\":\"\\u2A04\",\n  \"bigvee\":\"\\u22C1\",\n  \"bigwedge\":\"\\u22C0\",\n  \"bkarow\":\"\\u290D\",\n  \"blacklozenge\":\"\\u29EB\",\n  \"blacksquare\":\"\\u25AA\",\n  \"blacktriangle\":\"\\u25B4\",\n  \"blacktriangledown\":\"\\u25BE\",\n  \"blacktriangleleft\":\"\\u25C2\",\n  \"blacktriangleright\":\"\\u25B8\",\n  \"blank\":\"\\u2423\",\n  \"blk12\":\"\\u2592\",\n  \"blk14\":\"\\u2591\",\n  \"blk34\":\"\\u2593\",\n  \"block\":\"\\u2588\",\n  \"bne\":\"\\u003D\\u20E5\",\n  \"bnequiv\":\"\\u2261\\u20E5\",\n  \"bNot\":\"\\u2AED\",\n  \"bnot\":\"\\u2310\",\n  \"Bopf\":\"\\uD835\\uDD39\",\n  \"bopf\":\"\\uD835\\uDD53\",\n  \"bot\":\"\\u22A5\",\n  \"bottom\":\"\\u22A5\",\n  \"bowtie\":\"\\u22C8\",\n  \"boxbox\":\"\\u29C9\",\n  \"boxDL\":\"\\u2557\",\n  \"boxDl\":\"\\u2556\",\n  \"boxdL\":\"\\u2555\",\n  \"boxdl\":\"\\u2510\",\n  \"boxDR\":\"\\u2554\",\n  \"boxDr\":\"\\u2553\",\n  \"boxdR\":\"\\u2552\",\n  \"boxdr\":\"\\u250C\",\n  \"boxH\":\"\\u2550\",\n  \"boxh\":\"\\u2500\",\n  \"boxHD\":\"\\u2566\",\n  \"boxHd\":\"\\u2564\",\n  \"boxhD\":\"\\u2565\",\n  \"boxhd\":\"\\u252C\",\n  \"boxHU\":\"\\u2569\",\n  \"boxHu\":\"\\u2567\",\n  \"boxhU\":\"\\u2568\",\n  \"boxhu\":\"\\u2534\",\n  \"boxminus\":\"\\u229F\",\n  \"boxplus\":\"\\u229E\",\n  \"boxtimes\":\"\\u22A0\",\n  \"boxUL\":\"\\u255D\",\n  \"boxUl\":\"\\u255C\",\n  \"boxuL\":\"\\u255B\",\n  \"boxul\":\"\\u2518\",\n  \"boxUR\":\"\\u255A\",\n  \"boxUr\":\"\\u2559\",\n  \"boxuR\":\"\\u2558\",\n  \"boxur\":\"\\u2514\",\n  \"boxV\":\"\\u2551\",\n  \"boxv\":\"\\u2502\",\n  \"boxVH\":\"\\u256C\",\n  \"boxVh\":\"\\u256B\",\n  \"boxvH\":\"\\u256A\",\n  \"boxvh\":\"\\u253C\",\n  \"boxVL\":\"\\u2563\",\n  \"boxVl\":\"\\u2562\",\n  \"boxvL\":\"\\u2561\",\n  \"boxvl\":\"\\u2524\",\n  \"boxVR\":\"\\u2560\",\n  \"boxVr\":\"\\u255F\",\n  \"boxvR\":\"\\u255E\",\n  \"boxvr\":\"\\u251C\",\n  \"bprime\":\"\\u2035\",\n  \"Breve\":\"\\u02D8\",\n  \"breve\":\"\\u02D8\",\n  \"brvbar\":\"\\u00A6\",\n  \"Bscr\":\"\\u212C\",\n  \"bscr\":\"\\uD835\\uDCB7\",\n  \"bsemi\":\"\\u204F\",\n  \"bsim\":\"\\u223D\",\n  \"bsime\":\"\\u22CD\",\n  \"bsol\":\"\\u005C\",\n  \"bsolb\":\"\\u29C5\",\n  \"bsolhsub\":\"\\u27C8\",\n  \"bull\":\"\\u2022\",\n  \"bullet\":\"\\u2022\",\n  \"bump\":\"\\u224E\",\n  \"bumpE\":\"\\u2AAE\",\n  \"bumpe\":\"\\u224F\",\n  \"Bumpeq\":\"\\u224E\",\n  \"bumpeq\":\"\\u224F\",\n  \"Cacute\":\"\\u0106\",\n  \"cacute\":\"\\u0107\",\n  \"Cap\":\"\\u22D2\",\n  \"cap\":\"\\u2229\",\n  \"capand\":\"\\u2A44\",\n  \"capbrcup\":\"\\u2A49\",\n  \"capcap\":\"\\u2A4B\",\n  \"capcup\":\"\\u2A47\",\n  \"capdot\":\"\\u2A40\",\n  \"CapitalDifferentialD\":\"\\u2145\",\n  \"caps\":\"\\u2229\\uFE00\",\n  \"caret\":\"\\u2041\",\n  \"caron\":\"\\u02C7\",\n  \"Cayleys\":\"\\u212D\",\n  \"ccaps\":\"\\u2A4D\",\n  \"Ccaron\":\"\\u010C\",\n  \"ccaron\":\"\\u010D\",\n  \"Ccedil\":\"\\u00C7\",\n  \"ccedil\":\"\\u00E7\",\n  \"Ccirc\":\"\\u0108\",\n  \"ccirc\":\"\\u0109\",\n  \"Cconint\":\"\\u2230\",\n  \"ccups\":\"\\u2A4C\",\n  \"ccupssm\":\"\\u2A50\",\n  \"Cdot\":\"\\u010A\",\n  \"cdot\":\"\\u010B\",\n  \"cedil\":\"\\u00B8\",\n  \"Cedilla\":\"\\u00B8\",\n  \"cemptyv\":\"\\u29B2\",\n  \"cent\":\"\\u00A2\",\n  \"CenterDot\":\"\\u00B7\",\n  \"centerdot\":\"\\u00B7\",\n  \"Cfr\":\"\\u212D\",\n  \"cfr\":\"\\uD835\\uDD20\",\n  \"CHcy\":\"\\u0427\",\n  \"chcy\":\"\\u0447\",\n  \"check\":\"\\u2713\",\n  \"checkmark\":\"\\u2713\",\n  \"Chi\":\"\\u03A7\",\n  \"chi\":\"\\u03C7\",\n  \"cir\":\"\\u25CB\",\n  \"circ\":\"\\u02C6\",\n  \"circeq\":\"\\u2257\",\n  \"circlearrowleft\":\"\\u21BA\",\n  \"circlearrowright\":\"\\u21BB\",\n  \"circledast\":\"\\u229B\",\n  \"circledcirc\":\"\\u229A\",\n  \"circleddash\":\"\\u229D\",\n  \"CircleDot\":\"\\u2299\",\n  \"circledR\":\"\\u00AE\",\n  \"circledS\":\"\\u24C8\",\n  \"CircleMinus\":\"\\u2296\",\n  \"CirclePlus\":\"\\u2295\",\n  \"CircleTimes\":\"\\u2297\",\n  \"cirE\":\"\\u29C3\",\n  \"cire\":\"\\u2257\",\n  \"cirfnint\":\"\\u2A10\",\n  \"cirmid\":\"\\u2AEF\",\n  \"cirscir\":\"\\u29C2\",\n  \"ClockwiseContourIntegral\":\"\\u2232\",\n  \"CloseCurlyDoubleQuote\":\"\\u201D\",\n  \"CloseCurlyQuote\":\"\\u2019\",\n  \"clubs\":\"\\u2663\",\n  \"clubsuit\":\"\\u2663\",\n  \"Colon\":\"\\u2237\",\n  \"colon\":\"\\u003A\",\n  \"Colone\":\"\\u2A74\",\n  \"colone\":\"\\u2254\",\n  \"coloneq\":\"\\u2254\",\n  \"comma\":\"\\u002C\",\n  \"commat\":\"\\u0040\",\n  \"comp\":\"\\u2201\",\n  \"compfn\":\"\\u2218\",\n  \"complement\":\"\\u2201\",\n  \"complexes\":\"\\u2102\",\n  \"cong\":\"\\u2245\",\n  \"congdot\":\"\\u2A6D\",\n  \"Congruent\":\"\\u2261\",\n  \"Conint\":\"\\u222F\",\n  \"conint\":\"\\u222E\",\n  \"ContourIntegral\":\"\\u222E\",\n  \"Copf\":\"\\u2102\",\n  \"copf\":\"\\uD835\\uDD54\",\n  \"coprod\":\"\\u2210\",\n  \"Coproduct\":\"\\u2210\",\n  \"COPY\":\"\\u00A9\",\n  \"copy\":\"\\u00A9\",\n  \"copysr\":\"\\u2117\",\n  \"CounterClockwiseContourIntegral\":\"\\u2233\",\n  \"crarr\":\"\\u21B5\",\n  \"Cross\":\"\\u2A2F\",\n  \"cross\":\"\\u2717\",\n  \"Cscr\":\"\\uD835\\uDC9E\",\n  \"cscr\":\"\\uD835\\uDCB8\",\n  \"csub\":\"\\u2ACF\",\n  \"csube\":\"\\u2AD1\",\n  \"csup\":\"\\u2AD0\",\n  \"csupe\":\"\\u2AD2\",\n  \"ctdot\":\"\\u22EF\",\n  \"cudarrl\":\"\\u2938\",\n  \"cudarrr\":\"\\u2935\",\n  \"cuepr\":\"\\u22DE\",\n  \"cuesc\":\"\\u22DF\",\n  \"cularr\":\"\\u21B6\",\n  \"cularrp\":\"\\u293D\",\n  \"Cup\":\"\\u22D3\",\n  \"cup\":\"\\u222A\",\n  \"cupbrcap\":\"\\u2A48\",\n  \"CupCap\":\"\\u224D\",\n  \"cupcap\":\"\\u2A46\",\n  \"cupcup\":\"\\u2A4A\",\n  \"cupdot\":\"\\u228D\",\n  \"cupor\":\"\\u2A45\",\n  \"cups\":\"\\u222A\\uFE00\",\n  \"curarr\":\"\\u21B7\",\n  \"curarrm\":\"\\u293C\",\n  \"curlyeqprec\":\"\\u22DE\",\n  \"curlyeqsucc\":\"\\u22DF\",\n  \"curlyvee\":\"\\u22CE\",\n  \"curlywedge\":\"\\u22CF\",\n  \"curren\":\"\\u00A4\",\n  \"curvearrowleft\":\"\\u21B6\",\n  \"curvearrowright\":\"\\u21B7\",\n  \"cuvee\":\"\\u22CE\",\n  \"cuwed\":\"\\u22CF\",\n  \"cwconint\":\"\\u2232\",\n  \"cwint\":\"\\u2231\",\n  \"cylcty\":\"\\u232D\",\n  \"Dagger\":\"\\u2021\",\n  \"dagger\":\"\\u2020\",\n  \"daleth\":\"\\u2138\",\n  \"Darr\":\"\\u21A1\",\n  \"dArr\":\"\\u21D3\",\n  \"darr\":\"\\u2193\",\n  \"dash\":\"\\u2010\",\n  \"Dashv\":\"\\u2AE4\",\n  \"dashv\":\"\\u22A3\",\n  \"dbkarow\":\"\\u290F\",\n  \"dblac\":\"\\u02DD\",\n  \"Dcaron\":\"\\u010E\",\n  \"dcaron\":\"\\u010F\",\n  \"Dcy\":\"\\u0414\",\n  \"dcy\":\"\\u0434\",\n  \"DD\":\"\\u2145\",\n  \"dd\":\"\\u2146\",\n  \"ddagger\":\"\\u2021\",\n  \"ddarr\":\"\\u21CA\",\n  \"DDotrahd\":\"\\u2911\",\n  \"ddotseq\":\"\\u2A77\",\n  \"deg\":\"\\u00B0\",\n  \"Del\":\"\\u2207\",\n  \"Delta\":\"\\u0394\",\n  \"delta\":\"\\u03B4\",\n  \"demptyv\":\"\\u29B1\",\n  \"dfisht\":\"\\u297F\",\n  \"Dfr\":\"\\uD835\\uDD07\",\n  \"dfr\":\"\\uD835\\uDD21\",\n  \"dHar\":\"\\u2965\",\n  \"dharl\":\"\\u21C3\",\n  \"dharr\":\"\\u21C2\",\n  \"DiacriticalAcute\":\"\\u00B4\",\n  \"DiacriticalDot\":\"\\u02D9\",\n  \"DiacriticalDoubleAcute\":\"\\u02DD\",\n  \"DiacriticalGrave\":\"\\u0060\",\n  \"DiacriticalTilde\":\"\\u02DC\",\n  \"diam\":\"\\u22C4\",\n  \"Diamond\":\"\\u22C4\",\n  \"diamond\":\"\\u22C4\",\n  \"diamondsuit\":\"\\u2666\",\n  \"diams\":\"\\u2666\",\n  \"die\":\"\\u00A8\",\n  \"DifferentialD\":\"\\u2146\",\n  \"digamma\":\"\\u03DD\",\n  \"disin\":\"\\u22F2\",\n  \"div\":\"\\u00F7\",\n  \"divide\":\"\\u00F7\",\n  \"divideontimes\":\"\\u22C7\",\n  \"divonx\":\"\\u22C7\",\n  \"DJcy\":\"\\u0402\",\n  \"djcy\":\"\\u0452\",\n  \"dlcorn\":\"\\u231E\",\n  \"dlcrop\":\"\\u230D\",\n  \"dollar\":\"\\u0024\",\n  \"Dopf\":\"\\uD835\\uDD3B\",\n  \"dopf\":\"\\uD835\\uDD55\",\n  \"Dot\":\"\\u00A8\",\n  \"dot\":\"\\u02D9\",\n  \"DotDot\":\"\\u20DC\",\n  \"doteq\":\"\\u2250\",\n  \"doteqdot\":\"\\u2251\",\n  \"DotEqual\":\"\\u2250\",\n  \"dotminus\":\"\\u2238\",\n  \"dotplus\":\"\\u2214\",\n  \"dotsquare\":\"\\u22A1\",\n  \"doublebarwedge\":\"\\u2306\",\n  \"DoubleContourIntegral\":\"\\u222F\",\n  \"DoubleDot\":\"\\u00A8\",\n  \"DoubleDownArrow\":\"\\u21D3\",\n  \"DoubleLeftArrow\":\"\\u21D0\",\n  \"DoubleLeftRightArrow\":\"\\u21D4\",\n  \"DoubleLeftTee\":\"\\u2AE4\",\n  \"DoubleLongLeftArrow\":\"\\u27F8\",\n  \"DoubleLongLeftRightArrow\":\"\\u27FA\",\n  \"DoubleLongRightArrow\":\"\\u27F9\",\n  \"DoubleRightArrow\":\"\\u21D2\",\n  \"DoubleRightTee\":\"\\u22A8\",\n  \"DoubleUpArrow\":\"\\u21D1\",\n  \"DoubleUpDownArrow\":\"\\u21D5\",\n  \"DoubleVerticalBar\":\"\\u2225\",\n  \"DownArrow\":\"\\u2193\",\n  \"Downarrow\":\"\\u21D3\",\n  \"downarrow\":\"\\u2193\",\n  \"DownArrowBar\":\"\\u2913\",\n  \"DownArrowUpArrow\":\"\\u21F5\",\n  \"DownBreve\":\"\\u0311\",\n  \"downdownarrows\":\"\\u21CA\",\n  \"downharpoonleft\":\"\\u21C3\",\n  \"downharpoonright\":\"\\u21C2\",\n  \"DownLeftRightVector\":\"\\u2950\",\n  \"DownLeftTeeVector\":\"\\u295E\",\n  \"DownLeftVector\":\"\\u21BD\",\n  \"DownLeftVectorBar\":\"\\u2956\",\n  \"DownRightTeeVector\":\"\\u295F\",\n  \"DownRightVector\":\"\\u21C1\",\n  \"DownRightVectorBar\":\"\\u2957\",\n  \"DownTee\":\"\\u22A4\",\n  \"DownTeeArrow\":\"\\u21A7\",\n  \"drbkarow\":\"\\u2910\",\n  \"drcorn\":\"\\u231F\",\n  \"drcrop\":\"\\u230C\",\n  \"Dscr\":\"\\uD835\\uDC9F\",\n  \"dscr\":\"\\uD835\\uDCB9\",\n  \"DScy\":\"\\u0405\",\n  \"dscy\":\"\\u0455\",\n  \"dsol\":\"\\u29F6\",\n  \"Dstrok\":\"\\u0110\",\n  \"dstrok\":\"\\u0111\",\n  \"dtdot\":\"\\u22F1\",\n  \"dtri\":\"\\u25BF\",\n  \"dtrif\":\"\\u25BE\",\n  \"duarr\":\"\\u21F5\",\n  \"duhar\":\"\\u296F\",\n  \"dwangle\":\"\\u29A6\",\n  \"DZcy\":\"\\u040F\",\n  \"dzcy\":\"\\u045F\",\n  \"dzigrarr\":\"\\u27FF\",\n  \"Eacute\":\"\\u00C9\",\n  \"eacute\":\"\\u00E9\",\n  \"easter\":\"\\u2A6E\",\n  \"Ecaron\":\"\\u011A\",\n  \"ecaron\":\"\\u011B\",\n  \"ecir\":\"\\u2256\",\n  \"Ecirc\":\"\\u00CA\",\n  \"ecirc\":\"\\u00EA\",\n  \"ecolon\":\"\\u2255\",\n  \"Ecy\":\"\\u042D\",\n  \"ecy\":\"\\u044D\",\n  \"eDDot\":\"\\u2A77\",\n  \"Edot\":\"\\u0116\",\n  \"eDot\":\"\\u2251\",\n  \"edot\":\"\\u0117\",\n  \"ee\":\"\\u2147\",\n  \"efDot\":\"\\u2252\",\n  \"Efr\":\"\\uD835\\uDD08\",\n  \"efr\":\"\\uD835\\uDD22\",\n  \"eg\":\"\\u2A9A\",\n  \"Egrave\":\"\\u00C8\",\n  \"egrave\":\"\\u00E8\",\n  \"egs\":\"\\u2A96\",\n  \"egsdot\":\"\\u2A98\",\n  \"el\":\"\\u2A99\",\n  \"Element\":\"\\u2208\",\n  \"elinters\":\"\\u23E7\",\n  \"ell\":\"\\u2113\",\n  \"els\":\"\\u2A95\",\n  \"elsdot\":\"\\u2A97\",\n  \"Emacr\":\"\\u0112\",\n  \"emacr\":\"\\u0113\",\n  \"empty\":\"\\u2205\",\n  \"emptyset\":\"\\u2205\",\n  \"EmptySmallSquare\":\"\\u25FB\",\n  \"emptyv\":\"\\u2205\",\n  \"EmptyVerySmallSquare\":\"\\u25AB\",\n  \"emsp\":\"\\u2003\",\n  \"emsp13\":\"\\u2004\",\n  \"emsp14\":\"\\u2005\",\n  \"ENG\":\"\\u014A\",\n  \"eng\":\"\\u014B\",\n  \"ensp\":\"\\u2002\",\n  \"Eogon\":\"\\u0118\",\n  \"eogon\":\"\\u0119\",\n  \"Eopf\":\"\\uD835\\uDD3C\",\n  \"eopf\":\"\\uD835\\uDD56\",\n  \"epar\":\"\\u22D5\",\n  \"eparsl\":\"\\u29E3\",\n  \"eplus\":\"\\u2A71\",\n  \"epsi\":\"\\u03B5\",\n  \"Epsilon\":\"\\u0395\",\n  \"epsilon\":\"\\u03B5\",\n  \"epsiv\":\"\\u03F5\",\n  \"eqcirc\":\"\\u2256\",\n  \"eqcolon\":\"\\u2255\",\n  \"eqsim\":\"\\u2242\",\n  \"eqslantgtr\":\"\\u2A96\",\n  \"eqslantless\":\"\\u2A95\",\n  \"Equal\":\"\\u2A75\",\n  \"equals\":\"\\u003D\",\n  \"EqualTilde\":\"\\u2242\",\n  \"equest\":\"\\u225F\",\n  \"Equilibrium\":\"\\u21CC\",\n  \"equiv\":\"\\u2261\",\n  \"equivDD\":\"\\u2A78\",\n  \"eqvparsl\":\"\\u29E5\",\n  \"erarr\":\"\\u2971\",\n  \"erDot\":\"\\u2253\",\n  \"Escr\":\"\\u2130\",\n  \"escr\":\"\\u212F\",\n  \"esdot\":\"\\u2250\",\n  \"Esim\":\"\\u2A73\",\n  \"esim\":\"\\u2242\",\n  \"Eta\":\"\\u0397\",\n  \"eta\":\"\\u03B7\",\n  \"ETH\":\"\\u00D0\",\n  \"eth\":\"\\u00F0\",\n  \"Euml\":\"\\u00CB\",\n  \"euml\":\"\\u00EB\",\n  \"euro\":\"\\u20AC\",\n  \"excl\":\"\\u0021\",\n  \"exist\":\"\\u2203\",\n  \"Exists\":\"\\u2203\",\n  \"expectation\":\"\\u2130\",\n  \"ExponentialE\":\"\\u2147\",\n  \"exponentiale\":\"\\u2147\",\n  \"fallingdotseq\":\"\\u2252\",\n  \"Fcy\":\"\\u0424\",\n  \"fcy\":\"\\u0444\",\n  \"female\":\"\\u2640\",\n  \"ffilig\":\"\\uFB03\",\n  \"fflig\":\"\\uFB00\",\n  \"ffllig\":\"\\uFB04\",\n  \"Ffr\":\"\\uD835\\uDD09\",\n  \"ffr\":\"\\uD835\\uDD23\",\n  \"filig\":\"\\uFB01\",\n  \"FilledSmallSquare\":\"\\u25FC\",\n  \"FilledVerySmallSquare\":\"\\u25AA\",\n  \"fjlig\":\"\\u0066\\u006A\",\n  \"flat\":\"\\u266D\",\n  \"fllig\":\"\\uFB02\",\n  \"fltns\":\"\\u25B1\",\n  \"fnof\":\"\\u0192\",\n  \"Fopf\":\"\\uD835\\uDD3D\",\n  \"fopf\":\"\\uD835\\uDD57\",\n  \"ForAll\":\"\\u2200\",\n  \"forall\":\"\\u2200\",\n  \"fork\":\"\\u22D4\",\n  \"forkv\":\"\\u2AD9\",\n  \"Fouriertrf\":\"\\u2131\",\n  \"fpartint\":\"\\u2A0D\",\n  \"frac12\":\"\\u00BD\",\n  \"frac13\":\"\\u2153\",\n  \"frac14\":\"\\u00BC\",\n  \"frac15\":\"\\u2155\",\n  \"frac16\":\"\\u2159\",\n  \"frac18\":\"\\u215B\",\n  \"frac23\":\"\\u2154\",\n  \"frac25\":\"\\u2156\",\n  \"frac34\":\"\\u00BE\",\n  \"frac35\":\"\\u2157\",\n  \"frac38\":\"\\u215C\",\n  \"frac45\":\"\\u2158\",\n  \"frac56\":\"\\u215A\",\n  \"frac58\":\"\\u215D\",\n  \"frac78\":\"\\u215E\",\n  \"frasl\":\"\\u2044\",\n  \"frown\":\"\\u2322\",\n  \"Fscr\":\"\\u2131\",\n  \"fscr\":\"\\uD835\\uDCBB\",\n  \"gacute\":\"\\u01F5\",\n  \"Gamma\":\"\\u0393\",\n  \"gamma\":\"\\u03B3\",\n  \"Gammad\":\"\\u03DC\",\n  \"gammad\":\"\\u03DD\",\n  \"gap\":\"\\u2A86\",\n  \"Gbreve\":\"\\u011E\",\n  \"gbreve\":\"\\u011F\",\n  \"Gcedil\":\"\\u0122\",\n  \"Gcirc\":\"\\u011C\",\n  \"gcirc\":\"\\u011D\",\n  \"Gcy\":\"\\u0413\",\n  \"gcy\":\"\\u0433\",\n  \"Gdot\":\"\\u0120\",\n  \"gdot\":\"\\u0121\",\n  \"gE\":\"\\u2267\",\n  \"ge\":\"\\u2265\",\n  \"gEl\":\"\\u2A8C\",\n  \"gel\":\"\\u22DB\",\n  \"geq\":\"\\u2265\",\n  \"geqq\":\"\\u2267\",\n  \"geqslant\":\"\\u2A7E\",\n  \"ges\":\"\\u2A7E\",\n  \"gescc\":\"\\u2AA9\",\n  \"gesdot\":\"\\u2A80\",\n  \"gesdoto\":\"\\u2A82\",\n  \"gesdotol\":\"\\u2A84\",\n  \"gesl\":\"\\u22DB\\uFE00\",\n  \"gesles\":\"\\u2A94\",\n  \"Gfr\":\"\\uD835\\uDD0A\",\n  \"gfr\":\"\\uD835\\uDD24\",\n  \"Gg\":\"\\u22D9\",\n  \"gg\":\"\\u226B\",\n  \"ggg\":\"\\u22D9\",\n  \"gimel\":\"\\u2137\",\n  \"GJcy\":\"\\u0403\",\n  \"gjcy\":\"\\u0453\",\n  \"gl\":\"\\u2277\",\n  \"gla\":\"\\u2AA5\",\n  \"glE\":\"\\u2A92\",\n  \"glj\":\"\\u2AA4\",\n  \"gnap\":\"\\u2A8A\",\n  \"gnapprox\":\"\\u2A8A\",\n  \"gnE\":\"\\u2269\",\n  \"gne\":\"\\u2A88\",\n  \"gneq\":\"\\u2A88\",\n  \"gneqq\":\"\\u2269\",\n  \"gnsim\":\"\\u22E7\",\n  \"Gopf\":\"\\uD835\\uDD3E\",\n  \"gopf\":\"\\uD835\\uDD58\",\n  \"grave\":\"\\u0060\",\n  \"GreaterEqual\":\"\\u2265\",\n  \"GreaterEqualLess\":\"\\u22DB\",\n  \"GreaterFullEqual\":\"\\u2267\",\n  \"GreaterGreater\":\"\\u2AA2\",\n  \"GreaterLess\":\"\\u2277\",\n  \"GreaterSlantEqual\":\"\\u2A7E\",\n  \"GreaterTilde\":\"\\u2273\",\n  \"Gscr\":\"\\uD835\\uDCA2\",\n  \"gscr\":\"\\u210A\",\n  \"gsim\":\"\\u2273\",\n  \"gsime\":\"\\u2A8E\",\n  \"gsiml\":\"\\u2A90\",\n  \"GT\":\"\\u003E\",\n  \"Gt\":\"\\u226B\",\n  \"gt\":\"\\u003E\",\n  \"gtcc\":\"\\u2AA7\",\n  \"gtcir\":\"\\u2A7A\",\n  \"gtdot\":\"\\u22D7\",\n  \"gtlPar\":\"\\u2995\",\n  \"gtquest\":\"\\u2A7C\",\n  \"gtrapprox\":\"\\u2A86\",\n  \"gtrarr\":\"\\u2978\",\n  \"gtrdot\":\"\\u22D7\",\n  \"gtreqless\":\"\\u22DB\",\n  \"gtreqqless\":\"\\u2A8C\",\n  \"gtrless\":\"\\u2277\",\n  \"gtrsim\":\"\\u2273\",\n  \"gvertneqq\":\"\\u2269\\uFE00\",\n  \"gvnE\":\"\\u2269\\uFE00\",\n  \"Hacek\":\"\\u02C7\",\n  \"hairsp\":\"\\u200A\",\n  \"half\":\"\\u00BD\",\n  \"hamilt\":\"\\u210B\",\n  \"HARDcy\":\"\\u042A\",\n  \"hardcy\":\"\\u044A\",\n  \"hArr\":\"\\u21D4\",\n  \"harr\":\"\\u2194\",\n  \"harrcir\":\"\\u2948\",\n  \"harrw\":\"\\u21AD\",\n  \"Hat\":\"\\u005E\",\n  \"hbar\":\"\\u210F\",\n  \"Hcirc\":\"\\u0124\",\n  \"hcirc\":\"\\u0125\",\n  \"hearts\":\"\\u2665\",\n  \"heartsuit\":\"\\u2665\",\n  \"hellip\":\"\\u2026\",\n  \"hercon\":\"\\u22B9\",\n  \"Hfr\":\"\\u210C\",\n  \"hfr\":\"\\uD835\\uDD25\",\n  \"HilbertSpace\":\"\\u210B\",\n  \"hksearow\":\"\\u2925\",\n  \"hkswarow\":\"\\u2926\",\n  \"hoarr\":\"\\u21FF\",\n  \"homtht\":\"\\u223B\",\n  \"hookleftarrow\":\"\\u21A9\",\n  \"hookrightarrow\":\"\\u21AA\",\n  \"Hopf\":\"\\u210D\",\n  \"hopf\":\"\\uD835\\uDD59\",\n  \"horbar\":\"\\u2015\",\n  \"HorizontalLine\":\"\\u2500\",\n  \"Hscr\":\"\\u210B\",\n  \"hscr\":\"\\uD835\\uDCBD\",\n  \"hslash\":\"\\u210F\",\n  \"Hstrok\":\"\\u0126\",\n  \"hstrok\":\"\\u0127\",\n  \"HumpDownHump\":\"\\u224E\",\n  \"HumpEqual\":\"\\u224F\",\n  \"hybull\":\"\\u2043\",\n  \"hyphen\":\"\\u2010\",\n  \"Iacute\":\"\\u00CD\",\n  \"iacute\":\"\\u00ED\",\n  \"ic\":\"\\u2063\",\n  \"Icirc\":\"\\u00CE\",\n  \"icirc\":\"\\u00EE\",\n  \"Icy\":\"\\u0418\",\n  \"icy\":\"\\u0438\",\n  \"Idot\":\"\\u0130\",\n  \"IEcy\":\"\\u0415\",\n  \"iecy\":\"\\u0435\",\n  \"iexcl\":\"\\u00A1\",\n  \"iff\":\"\\u21D4\",\n  \"Ifr\":\"\\u2111\",\n  \"ifr\":\"\\uD835\\uDD26\",\n  \"Igrave\":\"\\u00CC\",\n  \"igrave\":\"\\u00EC\",\n  \"ii\":\"\\u2148\",\n  \"iiiint\":\"\\u2A0C\",\n  \"iiint\":\"\\u222D\",\n  \"iinfin\":\"\\u29DC\",\n  \"iiota\":\"\\u2129\",\n  \"IJlig\":\"\\u0132\",\n  \"ijlig\":\"\\u0133\",\n  \"Im\":\"\\u2111\",\n  \"Imacr\":\"\\u012A\",\n  \"imacr\":\"\\u012B\",\n  \"image\":\"\\u2111\",\n  \"ImaginaryI\":\"\\u2148\",\n  \"imagline\":\"\\u2110\",\n  \"imagpart\":\"\\u2111\",\n  \"imath\":\"\\u0131\",\n  \"imof\":\"\\u22B7\",\n  \"imped\":\"\\u01B5\",\n  \"Implies\":\"\\u21D2\",\n  \"in\":\"\\u2208\",\n  \"incare\":\"\\u2105\",\n  \"infin\":\"\\u221E\",\n  \"infintie\":\"\\u29DD\",\n  \"inodot\":\"\\u0131\",\n  \"Int\":\"\\u222C\",\n  \"int\":\"\\u222B\",\n  \"intcal\":\"\\u22BA\",\n  \"integers\":\"\\u2124\",\n  \"Integral\":\"\\u222B\",\n  \"intercal\":\"\\u22BA\",\n  \"Intersection\":\"\\u22C2\",\n  \"intlarhk\":\"\\u2A17\",\n  \"intprod\":\"\\u2A3C\",\n  \"InvisibleComma\":\"\\u2063\",\n  \"InvisibleTimes\":\"\\u2062\",\n  \"IOcy\":\"\\u0401\",\n  \"iocy\":\"\\u0451\",\n  \"Iogon\":\"\\u012E\",\n  \"iogon\":\"\\u012F\",\n  \"Iopf\":\"\\uD835\\uDD40\",\n  \"iopf\":\"\\uD835\\uDD5A\",\n  \"Iota\":\"\\u0399\",\n  \"iota\":\"\\u03B9\",\n  \"iprod\":\"\\u2A3C\",\n  \"iquest\":\"\\u00BF\",\n  \"Iscr\":\"\\u2110\",\n  \"iscr\":\"\\uD835\\uDCBE\",\n  \"isin\":\"\\u2208\",\n  \"isindot\":\"\\u22F5\",\n  \"isinE\":\"\\u22F9\",\n  \"isins\":\"\\u22F4\",\n  \"isinsv\":\"\\u22F3\",\n  \"isinv\":\"\\u2208\",\n  \"it\":\"\\u2062\",\n  \"Itilde\":\"\\u0128\",\n  \"itilde\":\"\\u0129\",\n  \"Iukcy\":\"\\u0406\",\n  \"iukcy\":\"\\u0456\",\n  \"Iuml\":\"\\u00CF\",\n  \"iuml\":\"\\u00EF\",\n  \"Jcirc\":\"\\u0134\",\n  \"jcirc\":\"\\u0135\",\n  \"Jcy\":\"\\u0419\",\n  \"jcy\":\"\\u0439\",\n  \"Jfr\":\"\\uD835\\uDD0D\",\n  \"jfr\":\"\\uD835\\uDD27\",\n  \"jmath\":\"\\u0237\",\n  \"Jopf\":\"\\uD835\\uDD41\",\n  \"jopf\":\"\\uD835\\uDD5B\",\n  \"Jscr\":\"\\uD835\\uDCA5\",\n  \"jscr\":\"\\uD835\\uDCBF\",\n  \"Jsercy\":\"\\u0408\",\n  \"jsercy\":\"\\u0458\",\n  \"Jukcy\":\"\\u0404\",\n  \"jukcy\":\"\\u0454\",\n  \"Kappa\":\"\\u039A\",\n  \"kappa\":\"\\u03BA\",\n  \"kappav\":\"\\u03F0\",\n  \"Kcedil\":\"\\u0136\",\n  \"kcedil\":\"\\u0137\",\n  \"Kcy\":\"\\u041A\",\n  \"kcy\":\"\\u043A\",\n  \"Kfr\":\"\\uD835\\uDD0E\",\n  \"kfr\":\"\\uD835\\uDD28\",\n  \"kgreen\":\"\\u0138\",\n  \"KHcy\":\"\\u0425\",\n  \"khcy\":\"\\u0445\",\n  \"KJcy\":\"\\u040C\",\n  \"kjcy\":\"\\u045C\",\n  \"Kopf\":\"\\uD835\\uDD42\",\n  \"kopf\":\"\\uD835\\uDD5C\",\n  \"Kscr\":\"\\uD835\\uDCA6\",\n  \"kscr\":\"\\uD835\\uDCC0\",\n  \"lAarr\":\"\\u21DA\",\n  \"Lacute\":\"\\u0139\",\n  \"lacute\":\"\\u013A\",\n  \"laemptyv\":\"\\u29B4\",\n  \"lagran\":\"\\u2112\",\n  \"Lambda\":\"\\u039B\",\n  \"lambda\":\"\\u03BB\",\n  \"Lang\":\"\\u27EA\",\n  \"lang\":\"\\u27E8\",\n  \"langd\":\"\\u2991\",\n  \"langle\":\"\\u27E8\",\n  \"lap\":\"\\u2A85\",\n  \"Laplacetrf\":\"\\u2112\",\n  \"laquo\":\"\\u00AB\",\n  \"Larr\":\"\\u219E\",\n  \"lArr\":\"\\u21D0\",\n  \"larr\":\"\\u2190\",\n  \"larrb\":\"\\u21E4\",\n  \"larrbfs\":\"\\u291F\",\n  \"larrfs\":\"\\u291D\",\n  \"larrhk\":\"\\u21A9\",\n  \"larrlp\":\"\\u21AB\",\n  \"larrpl\":\"\\u2939\",\n  \"larrsim\":\"\\u2973\",\n  \"larrtl\":\"\\u21A2\",\n  \"lat\":\"\\u2AAB\",\n  \"lAtail\":\"\\u291B\",\n  \"latail\":\"\\u2919\",\n  \"late\":\"\\u2AAD\",\n  \"lates\":\"\\u2AAD\\uFE00\",\n  \"lBarr\":\"\\u290E\",\n  \"lbarr\":\"\\u290C\",\n  \"lbbrk\":\"\\u2772\",\n  \"lbrace\":\"\\u007B\",\n  \"lbrack\":\"\\u005B\",\n  \"lbrke\":\"\\u298B\",\n  \"lbrksld\":\"\\u298F\",\n  \"lbrkslu\":\"\\u298D\",\n  \"Lcaron\":\"\\u013D\",\n  \"lcaron\":\"\\u013E\",\n  \"Lcedil\":\"\\u013B\",\n  \"lcedil\":\"\\u013C\",\n  \"lceil\":\"\\u2308\",\n  \"lcub\":\"\\u007B\",\n  \"Lcy\":\"\\u041B\",\n  \"lcy\":\"\\u043B\",\n  \"ldca\":\"\\u2936\",\n  \"ldquo\":\"\\u201C\",\n  \"ldquor\":\"\\u201E\",\n  \"ldrdhar\":\"\\u2967\",\n  \"ldrushar\":\"\\u294B\",\n  \"ldsh\":\"\\u21B2\",\n  \"lE\":\"\\u2266\",\n  \"le\":\"\\u2264\",\n  \"LeftAngleBracket\":\"\\u27E8\",\n  \"LeftArrow\":\"\\u2190\",\n  \"Leftarrow\":\"\\u21D0\",\n  \"leftarrow\":\"\\u2190\",\n  \"LeftArrowBar\":\"\\u21E4\",\n  \"LeftArrowRightArrow\":\"\\u21C6\",\n  \"leftarrowtail\":\"\\u21A2\",\n  \"LeftCeiling\":\"\\u2308\",\n  \"LeftDoubleBracket\":\"\\u27E6\",\n  \"LeftDownTeeVector\":\"\\u2961\",\n  \"LeftDownVector\":\"\\u21C3\",\n  \"LeftDownVectorBar\":\"\\u2959\",\n  \"LeftFloor\":\"\\u230A\",\n  \"leftharpoondown\":\"\\u21BD\",\n  \"leftharpoonup\":\"\\u21BC\",\n  \"leftleftarrows\":\"\\u21C7\",\n  \"LeftRightArrow\":\"\\u2194\",\n  \"Leftrightarrow\":\"\\u21D4\",\n  \"leftrightarrow\":\"\\u2194\",\n  \"leftrightarrows\":\"\\u21C6\",\n  \"leftrightharpoons\":\"\\u21CB\",\n  \"leftrightsquigarrow\":\"\\u21AD\",\n  \"LeftRightVector\":\"\\u294E\",\n  \"LeftTee\":\"\\u22A3\",\n  \"LeftTeeArrow\":\"\\u21A4\",\n  \"LeftTeeVector\":\"\\u295A\",\n  \"leftthreetimes\":\"\\u22CB\",\n  \"LeftTriangle\":\"\\u22B2\",\n  \"LeftTriangleBar\":\"\\u29CF\",\n  \"LeftTriangleEqual\":\"\\u22B4\",\n  \"LeftUpDownVector\":\"\\u2951\",\n  \"LeftUpTeeVector\":\"\\u2960\",\n  \"LeftUpVector\":\"\\u21BF\",\n  \"LeftUpVectorBar\":\"\\u2958\",\n  \"LeftVector\":\"\\u21BC\",\n  \"LeftVectorBar\":\"\\u2952\",\n  \"lEg\":\"\\u2A8B\",\n  \"leg\":\"\\u22DA\",\n  \"leq\":\"\\u2264\",\n  \"leqq\":\"\\u2266\",\n  \"leqslant\":\"\\u2A7D\",\n  \"les\":\"\\u2A7D\",\n  \"lescc\":\"\\u2AA8\",\n  \"lesdot\":\"\\u2A7F\",\n  \"lesdoto\":\"\\u2A81\",\n  \"lesdotor\":\"\\u2A83\",\n  \"lesg\":\"\\u22DA\\uFE00\",\n  \"lesges\":\"\\u2A93\",\n  \"lessapprox\":\"\\u2A85\",\n  \"lessdot\":\"\\u22D6\",\n  \"lesseqgtr\":\"\\u22DA\",\n  \"lesseqqgtr\":\"\\u2A8B\",\n  \"LessEqualGreater\":\"\\u22DA\",\n  \"LessFullEqual\":\"\\u2266\",\n  \"LessGreater\":\"\\u2276\",\n  \"lessgtr\":\"\\u2276\",\n  \"LessLess\":\"\\u2AA1\",\n  \"lesssim\":\"\\u2272\",\n  \"LessSlantEqual\":\"\\u2A7D\",\n  \"LessTilde\":\"\\u2272\",\n  \"lfisht\":\"\\u297C\",\n  \"lfloor\":\"\\u230A\",\n  \"Lfr\":\"\\uD835\\uDD0F\",\n  \"lfr\":\"\\uD835\\uDD29\",\n  \"lg\":\"\\u2276\",\n  \"lgE\":\"\\u2A91\",\n  \"lHar\":\"\\u2962\",\n  \"lhard\":\"\\u21BD\",\n  \"lharu\":\"\\u21BC\",\n  \"lharul\":\"\\u296A\",\n  \"lhblk\":\"\\u2584\",\n  \"LJcy\":\"\\u0409\",\n  \"ljcy\":\"\\u0459\",\n  \"Ll\":\"\\u22D8\",\n  \"ll\":\"\\u226A\",\n  \"llarr\":\"\\u21C7\",\n  \"llcorner\":\"\\u231E\",\n  \"Lleftarrow\":\"\\u21DA\",\n  \"llhard\":\"\\u296B\",\n  \"lltri\":\"\\u25FA\",\n  \"Lmidot\":\"\\u013F\",\n  \"lmidot\":\"\\u0140\",\n  \"lmoust\":\"\\u23B0\",\n  \"lmoustache\":\"\\u23B0\",\n  \"lnap\":\"\\u2A89\",\n  \"lnapprox\":\"\\u2A89\",\n  \"lnE\":\"\\u2268\",\n  \"lne\":\"\\u2A87\",\n  \"lneq\":\"\\u2A87\",\n  \"lneqq\":\"\\u2268\",\n  \"lnsim\":\"\\u22E6\",\n  \"loang\":\"\\u27EC\",\n  \"loarr\":\"\\u21FD\",\n  \"lobrk\":\"\\u27E6\",\n  \"LongLeftArrow\":\"\\u27F5\",\n  \"Longleftarrow\":\"\\u27F8\",\n  \"longleftarrow\":\"\\u27F5\",\n  \"LongLeftRightArrow\":\"\\u27F7\",\n  \"Longleftrightarrow\":\"\\u27FA\",\n  \"longleftrightarrow\":\"\\u27F7\",\n  \"longmapsto\":\"\\u27FC\",\n  \"LongRightArrow\":\"\\u27F6\",\n  \"Longrightarrow\":\"\\u27F9\",\n  \"longrightarrow\":\"\\u27F6\",\n  \"looparrowleft\":\"\\u21AB\",\n  \"looparrowright\":\"\\u21AC\",\n  \"lopar\":\"\\u2985\",\n  \"Lopf\":\"\\uD835\\uDD43\",\n  \"lopf\":\"\\uD835\\uDD5D\",\n  \"loplus\":\"\\u2A2D\",\n  \"lotimes\":\"\\u2A34\",\n  \"lowast\":\"\\u2217\",\n  \"lowbar\":\"\\u005F\",\n  \"LowerLeftArrow\":\"\\u2199\",\n  \"LowerRightArrow\":\"\\u2198\",\n  \"loz\":\"\\u25CA\",\n  \"lozenge\":\"\\u25CA\",\n  \"lozf\":\"\\u29EB\",\n  \"lpar\":\"\\u0028\",\n  \"lparlt\":\"\\u2993\",\n  \"lrarr\":\"\\u21C6\",\n  \"lrcorner\":\"\\u231F\",\n  \"lrhar\":\"\\u21CB\",\n  \"lrhard\":\"\\u296D\",\n  \"lrm\":\"\\u200E\",\n  \"lrtri\":\"\\u22BF\",\n  \"lsaquo\":\"\\u2039\",\n  \"Lscr\":\"\\u2112\",\n  \"lscr\":\"\\uD835\\uDCC1\",\n  \"Lsh\":\"\\u21B0\",\n  \"lsh\":\"\\u21B0\",\n  \"lsim\":\"\\u2272\",\n  \"lsime\":\"\\u2A8D\",\n  \"lsimg\":\"\\u2A8F\",\n  \"lsqb\":\"\\u005B\",\n  \"lsquo\":\"\\u2018\",\n  \"lsquor\":\"\\u201A\",\n  \"Lstrok\":\"\\u0141\",\n  \"lstrok\":\"\\u0142\",\n  \"LT\":\"\\u003C\",\n  \"Lt\":\"\\u226A\",\n  \"lt\":\"\\u003C\",\n  \"ltcc\":\"\\u2AA6\",\n  \"ltcir\":\"\\u2A79\",\n  \"ltdot\":\"\\u22D6\",\n  \"lthree\":\"\\u22CB\",\n  \"ltimes\":\"\\u22C9\",\n  \"ltlarr\":\"\\u2976\",\n  \"ltquest\":\"\\u2A7B\",\n  \"ltri\":\"\\u25C3\",\n  \"ltrie\":\"\\u22B4\",\n  \"ltrif\":\"\\u25C2\",\n  \"ltrPar\":\"\\u2996\",\n  \"lurdshar\":\"\\u294A\",\n  \"luruhar\":\"\\u2966\",\n  \"lvertneqq\":\"\\u2268\\uFE00\",\n  \"lvnE\":\"\\u2268\\uFE00\",\n  \"macr\":\"\\u00AF\",\n  \"male\":\"\\u2642\",\n  \"malt\":\"\\u2720\",\n  \"maltese\":\"\\u2720\",\n  \"Map\":\"\\u2905\",\n  \"map\":\"\\u21A6\",\n  \"mapsto\":\"\\u21A6\",\n  \"mapstodown\":\"\\u21A7\",\n  \"mapstoleft\":\"\\u21A4\",\n  \"mapstoup\":\"\\u21A5\",\n  \"marker\":\"\\u25AE\",\n  \"mcomma\":\"\\u2A29\",\n  \"Mcy\":\"\\u041C\",\n  \"mcy\":\"\\u043C\",\n  \"mdash\":\"\\u2014\",\n  \"mDDot\":\"\\u223A\",\n  \"measuredangle\":\"\\u2221\",\n  \"MediumSpace\":\"\\u205F\",\n  \"Mellintrf\":\"\\u2133\",\n  \"Mfr\":\"\\uD835\\uDD10\",\n  \"mfr\":\"\\uD835\\uDD2A\",\n  \"mho\":\"\\u2127\",\n  \"micro\":\"\\u00B5\",\n  \"mid\":\"\\u2223\",\n  \"midast\":\"\\u002A\",\n  \"midcir\":\"\\u2AF0\",\n  \"middot\":\"\\u00B7\",\n  \"minus\":\"\\u2212\",\n  \"minusb\":\"\\u229F\",\n  \"minusd\":\"\\u2238\",\n  \"minusdu\":\"\\u2A2A\",\n  \"MinusPlus\":\"\\u2213\",\n  \"mlcp\":\"\\u2ADB\",\n  \"mldr\":\"\\u2026\",\n  \"mnplus\":\"\\u2213\",\n  \"models\":\"\\u22A7\",\n  \"Mopf\":\"\\uD835\\uDD44\",\n  \"mopf\":\"\\uD835\\uDD5E\",\n  \"mp\":\"\\u2213\",\n  \"Mscr\":\"\\u2133\",\n  \"mscr\":\"\\uD835\\uDCC2\",\n  \"mstpos\":\"\\u223E\",\n  \"Mu\":\"\\u039C\",\n  \"mu\":\"\\u03BC\",\n  \"multimap\":\"\\u22B8\",\n  \"mumap\":\"\\u22B8\",\n  \"nabla\":\"\\u2207\",\n  \"Nacute\":\"\\u0143\",\n  \"nacute\":\"\\u0144\",\n  \"nang\":\"\\u2220\\u20D2\",\n  \"nap\":\"\\u2249\",\n  \"napE\":\"\\u2A70\\u0338\",\n  \"napid\":\"\\u224B\\u0338\",\n  \"napos\":\"\\u0149\",\n  \"napprox\":\"\\u2249\",\n  \"natur\":\"\\u266E\",\n  \"natural\":\"\\u266E\",\n  \"naturals\":\"\\u2115\",\n  \"nbsp\":\"\\u00A0\",\n  \"nbump\":\"\\u224E\\u0338\",\n  \"nbumpe\":\"\\u224F\\u0338\",\n  \"ncap\":\"\\u2A43\",\n  \"Ncaron\":\"\\u0147\",\n  \"ncaron\":\"\\u0148\",\n  \"Ncedil\":\"\\u0145\",\n  \"ncedil\":\"\\u0146\",\n  \"ncong\":\"\\u2247\",\n  \"ncongdot\":\"\\u2A6D\\u0338\",\n  \"ncup\":\"\\u2A42\",\n  \"Ncy\":\"\\u041D\",\n  \"ncy\":\"\\u043D\",\n  \"ndash\":\"\\u2013\",\n  \"ne\":\"\\u2260\",\n  \"nearhk\":\"\\u2924\",\n  \"neArr\":\"\\u21D7\",\n  \"nearr\":\"\\u2197\",\n  \"nearrow\":\"\\u2197\",\n  \"nedot\":\"\\u2250\\u0338\",\n  \"NegativeMediumSpace\":\"\\u200B\",\n  \"NegativeThickSpace\":\"\\u200B\",\n  \"NegativeThinSpace\":\"\\u200B\",\n  \"NegativeVeryThinSpace\":\"\\u200B\",\n  \"nequiv\":\"\\u2262\",\n  \"nesear\":\"\\u2928\",\n  \"nesim\":\"\\u2242\\u0338\",\n  \"NestedGreaterGreater\":\"\\u226B\",\n  \"NestedLessLess\":\"\\u226A\",\n  \"NewLine\":\"\\u000A\",\n  \"nexist\":\"\\u2204\",\n  \"nexists\":\"\\u2204\",\n  \"Nfr\":\"\\uD835\\uDD11\",\n  \"nfr\":\"\\uD835\\uDD2B\",\n  \"ngE\":\"\\u2267\\u0338\",\n  \"nge\":\"\\u2271\",\n  \"ngeq\":\"\\u2271\",\n  \"ngeqq\":\"\\u2267\\u0338\",\n  \"ngeqslant\":\"\\u2A7E\\u0338\",\n  \"nges\":\"\\u2A7E\\u0338\",\n  \"nGg\":\"\\u22D9\\u0338\",\n  \"ngsim\":\"\\u2275\",\n  \"nGt\":\"\\u226B\\u20D2\",\n  \"ngt\":\"\\u226F\",\n  \"ngtr\":\"\\u226F\",\n  \"nGtv\":\"\\u226B\\u0338\",\n  \"nhArr\":\"\\u21CE\",\n  \"nharr\":\"\\u21AE\",\n  \"nhpar\":\"\\u2AF2\",\n  \"ni\":\"\\u220B\",\n  \"nis\":\"\\u22FC\",\n  \"nisd\":\"\\u22FA\",\n  \"niv\":\"\\u220B\",\n  \"NJcy\":\"\\u040A\",\n  \"njcy\":\"\\u045A\",\n  \"nlArr\":\"\\u21CD\",\n  \"nlarr\":\"\\u219A\",\n  \"nldr\":\"\\u2025\",\n  \"nlE\":\"\\u2266\\u0338\",\n  \"nle\":\"\\u2270\",\n  \"nLeftarrow\":\"\\u21CD\",\n  \"nleftarrow\":\"\\u219A\",\n  \"nLeftrightarrow\":\"\\u21CE\",\n  \"nleftrightarrow\":\"\\u21AE\",\n  \"nleq\":\"\\u2270\",\n  \"nleqq\":\"\\u2266\\u0338\",\n  \"nleqslant\":\"\\u2A7D\\u0338\",\n  \"nles\":\"\\u2A7D\\u0338\",\n  \"nless\":\"\\u226E\",\n  \"nLl\":\"\\u22D8\\u0338\",\n  \"nlsim\":\"\\u2274\",\n  \"nLt\":\"\\u226A\\u20D2\",\n  \"nlt\":\"\\u226E\",\n  \"nltri\":\"\\u22EA\",\n  \"nltrie\":\"\\u22EC\",\n  \"nLtv\":\"\\u226A\\u0338\",\n  \"nmid\":\"\\u2224\",\n  \"NoBreak\":\"\\u2060\",\n  \"NonBreakingSpace\":\"\\u00A0\",\n  \"Nopf\":\"\\u2115\",\n  \"nopf\":\"\\uD835\\uDD5F\",\n  \"Not\":\"\\u2AEC\",\n  \"not\":\"\\u00AC\",\n  \"NotCongruent\":\"\\u2262\",\n  \"NotCupCap\":\"\\u226D\",\n  \"NotDoubleVerticalBar\":\"\\u2226\",\n  \"NotElement\":\"\\u2209\",\n  \"NotEqual\":\"\\u2260\",\n  \"NotEqualTilde\":\"\\u2242\\u0338\",\n  \"NotExists\":\"\\u2204\",\n  \"NotGreater\":\"\\u226F\",\n  \"NotGreaterEqual\":\"\\u2271\",\n  \"NotGreaterFullEqual\":\"\\u2267\\u0338\",\n  \"NotGreaterGreater\":\"\\u226B\\u0338\",\n  \"NotGreaterLess\":\"\\u2279\",\n  \"NotGreaterSlantEqual\":\"\\u2A7E\\u0338\",\n  \"NotGreaterTilde\":\"\\u2275\",\n  \"NotHumpDownHump\":\"\\u224E\\u0338\",\n  \"NotHumpEqual\":\"\\u224F\\u0338\",\n  \"notin\":\"\\u2209\",\n  \"notindot\":\"\\u22F5\\u0338\",\n  \"notinE\":\"\\u22F9\\u0338\",\n  \"notinva\":\"\\u2209\",\n  \"notinvb\":\"\\u22F7\",\n  \"notinvc\":\"\\u22F6\",\n  \"NotLeftTriangle\":\"\\u22EA\",\n  \"NotLeftTriangleBar\":\"\\u29CF\\u0338\",\n  \"NotLeftTriangleEqual\":\"\\u22EC\",\n  \"NotLess\":\"\\u226E\",\n  \"NotLessEqual\":\"\\u2270\",\n  \"NotLessGreater\":\"\\u2278\",\n  \"NotLessLess\":\"\\u226A\\u0338\",\n  \"NotLessSlantEqual\":\"\\u2A7D\\u0338\",\n  \"NotLessTilde\":\"\\u2274\",\n  \"NotNestedGreaterGreater\":\"\\u2AA2\\u0338\",\n  \"NotNestedLessLess\":\"\\u2AA1\\u0338\",\n  \"notni\":\"\\u220C\",\n  \"notniva\":\"\\u220C\",\n  \"notnivb\":\"\\u22FE\",\n  \"notnivc\":\"\\u22FD\",\n  \"NotPrecedes\":\"\\u2280\",\n  \"NotPrecedesEqual\":\"\\u2AAF\\u0338\",\n  \"NotPrecedesSlantEqual\":\"\\u22E0\",\n  \"NotReverseElement\":\"\\u220C\",\n  \"NotRightTriangle\":\"\\u22EB\",\n  \"NotRightTriangleBar\":\"\\u29D0\\u0338\",\n  \"NotRightTriangleEqual\":\"\\u22ED\",\n  \"NotSquareSubset\":\"\\u228F\\u0338\",\n  \"NotSquareSubsetEqual\":\"\\u22E2\",\n  \"NotSquareSuperset\":\"\\u2290\\u0338\",\n  \"NotSquareSupersetEqual\":\"\\u22E3\",\n  \"NotSubset\":\"\\u2282\\u20D2\",\n  \"NotSubsetEqual\":\"\\u2288\",\n  \"NotSucceeds\":\"\\u2281\",\n  \"NotSucceedsEqual\":\"\\u2AB0\\u0338\",\n  \"NotSucceedsSlantEqual\":\"\\u22E1\",\n  \"NotSucceedsTilde\":\"\\u227F\\u0338\",\n  \"NotSuperset\":\"\\u2283\\u20D2\",\n  \"NotSupersetEqual\":\"\\u2289\",\n  \"NotTilde\":\"\\u2241\",\n  \"NotTildeEqual\":\"\\u2244\",\n  \"NotTildeFullEqual\":\"\\u2247\",\n  \"NotTildeTilde\":\"\\u2249\",\n  \"NotVerticalBar\":\"\\u2224\",\n  \"npar\":\"\\u2226\",\n  \"nparallel\":\"\\u2226\",\n  \"nparsl\":\"\\u2AFD\\u20E5\",\n  \"npart\":\"\\u2202\\u0338\",\n  \"npolint\":\"\\u2A14\",\n  \"npr\":\"\\u2280\",\n  \"nprcue\":\"\\u22E0\",\n  \"npre\":\"\\u2AAF\\u0338\",\n  \"nprec\":\"\\u2280\",\n  \"npreceq\":\"\\u2AAF\\u0338\",\n  \"nrArr\":\"\\u21CF\",\n  \"nrarr\":\"\\u219B\",\n  \"nrarrc\":\"\\u2933\\u0338\",\n  \"nrarrw\":\"\\u219D\\u0338\",\n  \"nRightarrow\":\"\\u21CF\",\n  \"nrightarrow\":\"\\u219B\",\n  \"nrtri\":\"\\u22EB\",\n  \"nrtrie\":\"\\u22ED\",\n  \"nsc\":\"\\u2281\",\n  \"nsccue\":\"\\u22E1\",\n  \"nsce\":\"\\u2AB0\\u0338\",\n  \"Nscr\":\"\\uD835\\uDCA9\",\n  \"nscr\":\"\\uD835\\uDCC3\",\n  \"nshortmid\":\"\\u2224\",\n  \"nshortparallel\":\"\\u2226\",\n  \"nsim\":\"\\u2241\",\n  \"nsime\":\"\\u2244\",\n  \"nsimeq\":\"\\u2244\",\n  \"nsmid\":\"\\u2224\",\n  \"nspar\":\"\\u2226\",\n  \"nsqsube\":\"\\u22E2\",\n  \"nsqsupe\":\"\\u22E3\",\n  \"nsub\":\"\\u2284\",\n  \"nsubE\":\"\\u2AC5\\u0338\",\n  \"nsube\":\"\\u2288\",\n  \"nsubset\":\"\\u2282\\u20D2\",\n  \"nsubseteq\":\"\\u2288\",\n  \"nsubseteqq\":\"\\u2AC5\\u0338\",\n  \"nsucc\":\"\\u2281\",\n  \"nsucceq\":\"\\u2AB0\\u0338\",\n  \"nsup\":\"\\u2285\",\n  \"nsupE\":\"\\u2AC6\\u0338\",\n  \"nsupe\":\"\\u2289\",\n  \"nsupset\":\"\\u2283\\u20D2\",\n  \"nsupseteq\":\"\\u2289\",\n  \"nsupseteqq\":\"\\u2AC6\\u0338\",\n  \"ntgl\":\"\\u2279\",\n  \"Ntilde\":\"\\u00D1\",\n  \"ntilde\":\"\\u00F1\",\n  \"ntlg\":\"\\u2278\",\n  \"ntriangleleft\":\"\\u22EA\",\n  \"ntrianglelefteq\":\"\\u22EC\",\n  \"ntriangleright\":\"\\u22EB\",\n  \"ntrianglerighteq\":\"\\u22ED\",\n  \"Nu\":\"\\u039D\",\n  \"nu\":\"\\u03BD\",\n  \"num\":\"\\u0023\",\n  \"numero\":\"\\u2116\",\n  \"numsp\":\"\\u2007\",\n  \"nvap\":\"\\u224D\\u20D2\",\n  \"nVDash\":\"\\u22AF\",\n  \"nVdash\":\"\\u22AE\",\n  \"nvDash\":\"\\u22AD\",\n  \"nvdash\":\"\\u22AC\",\n  \"nvge\":\"\\u2265\\u20D2\",\n  \"nvgt\":\"\\u003E\\u20D2\",\n  \"nvHarr\":\"\\u2904\",\n  \"nvinfin\":\"\\u29DE\",\n  \"nvlArr\":\"\\u2902\",\n  \"nvle\":\"\\u2264\\u20D2\",\n  \"nvlt\":\"\\u003C\\u20D2\",\n  \"nvltrie\":\"\\u22B4\\u20D2\",\n  \"nvrArr\":\"\\u2903\",\n  \"nvrtrie\":\"\\u22B5\\u20D2\",\n  \"nvsim\":\"\\u223C\\u20D2\",\n  \"nwarhk\":\"\\u2923\",\n  \"nwArr\":\"\\u21D6\",\n  \"nwarr\":\"\\u2196\",\n  \"nwarrow\":\"\\u2196\",\n  \"nwnear\":\"\\u2927\",\n  \"Oacute\":\"\\u00D3\",\n  \"oacute\":\"\\u00F3\",\n  \"oast\":\"\\u229B\",\n  \"ocir\":\"\\u229A\",\n  \"Ocirc\":\"\\u00D4\",\n  \"ocirc\":\"\\u00F4\",\n  \"Ocy\":\"\\u041E\",\n  \"ocy\":\"\\u043E\",\n  \"odash\":\"\\u229D\",\n  \"Odblac\":\"\\u0150\",\n  \"odblac\":\"\\u0151\",\n  \"odiv\":\"\\u2A38\",\n  \"odot\":\"\\u2299\",\n  \"odsold\":\"\\u29BC\",\n  \"OElig\":\"\\u0152\",\n  \"oelig\":\"\\u0153\",\n  \"ofcir\":\"\\u29BF\",\n  \"Ofr\":\"\\uD835\\uDD12\",\n  \"ofr\":\"\\uD835\\uDD2C\",\n  \"ogon\":\"\\u02DB\",\n  \"Ograve\":\"\\u00D2\",\n  \"ograve\":\"\\u00F2\",\n  \"ogt\":\"\\u29C1\",\n  \"ohbar\":\"\\u29B5\",\n  \"ohm\":\"\\u03A9\",\n  \"oint\":\"\\u222E\",\n  \"olarr\":\"\\u21BA\",\n  \"olcir\":\"\\u29BE\",\n  \"olcross\":\"\\u29BB\",\n  \"oline\":\"\\u203E\",\n  \"olt\":\"\\u29C0\",\n  \"Omacr\":\"\\u014C\",\n  \"omacr\":\"\\u014D\",\n  \"Omega\":\"\\u03A9\",\n  \"omega\":\"\\u03C9\",\n  \"Omicron\":\"\\u039F\",\n  \"omicron\":\"\\u03BF\",\n  \"omid\":\"\\u29B6\",\n  \"ominus\":\"\\u2296\",\n  \"Oopf\":\"\\uD835\\uDD46\",\n  \"oopf\":\"\\uD835\\uDD60\",\n  \"opar\":\"\\u29B7\",\n  \"OpenCurlyDoubleQuote\":\"\\u201C\",\n  \"OpenCurlyQuote\":\"\\u2018\",\n  \"operp\":\"\\u29B9\",\n  \"oplus\":\"\\u2295\",\n  \"Or\":\"\\u2A54\",\n  \"or\":\"\\u2228\",\n  \"orarr\":\"\\u21BB\",\n  \"ord\":\"\\u2A5D\",\n  \"order\":\"\\u2134\",\n  \"orderof\":\"\\u2134\",\n  \"ordf\":\"\\u00AA\",\n  \"ordm\":\"\\u00BA\",\n  \"origof\":\"\\u22B6\",\n  \"oror\":\"\\u2A56\",\n  \"orslope\":\"\\u2A57\",\n  \"orv\":\"\\u2A5B\",\n  \"oS\":\"\\u24C8\",\n  \"Oscr\":\"\\uD835\\uDCAA\",\n  \"oscr\":\"\\u2134\",\n  \"Oslash\":\"\\u00D8\",\n  \"oslash\":\"\\u00F8\",\n  \"osol\":\"\\u2298\",\n  \"Otilde\":\"\\u00D5\",\n  \"otilde\":\"\\u00F5\",\n  \"Otimes\":\"\\u2A37\",\n  \"otimes\":\"\\u2297\",\n  \"otimesas\":\"\\u2A36\",\n  \"Ouml\":\"\\u00D6\",\n  \"ouml\":\"\\u00F6\",\n  \"ovbar\":\"\\u233D\",\n  \"OverBar\":\"\\u203E\",\n  \"OverBrace\":\"\\u23DE\",\n  \"OverBracket\":\"\\u23B4\",\n  \"OverParenthesis\":\"\\u23DC\",\n  \"par\":\"\\u2225\",\n  \"para\":\"\\u00B6\",\n  \"parallel\":\"\\u2225\",\n  \"parsim\":\"\\u2AF3\",\n  \"parsl\":\"\\u2AFD\",\n  \"part\":\"\\u2202\",\n  \"PartialD\":\"\\u2202\",\n  \"Pcy\":\"\\u041F\",\n  \"pcy\":\"\\u043F\",\n  \"percnt\":\"\\u0025\",\n  \"period\":\"\\u002E\",\n  \"permil\":\"\\u2030\",\n  \"perp\":\"\\u22A5\",\n  \"pertenk\":\"\\u2031\",\n  \"Pfr\":\"\\uD835\\uDD13\",\n  \"pfr\":\"\\uD835\\uDD2D\",\n  \"Phi\":\"\\u03A6\",\n  \"phi\":\"\\u03C6\",\n  \"phiv\":\"\\u03D5\",\n  \"phmmat\":\"\\u2133\",\n  \"phone\":\"\\u260E\",\n  \"Pi\":\"\\u03A0\",\n  \"pi\":\"\\u03C0\",\n  \"pitchfork\":\"\\u22D4\",\n  \"piv\":\"\\u03D6\",\n  \"planck\":\"\\u210F\",\n  \"planckh\":\"\\u210E\",\n  \"plankv\":\"\\u210F\",\n  \"plus\":\"\\u002B\",\n  \"plusacir\":\"\\u2A23\",\n  \"plusb\":\"\\u229E\",\n  \"pluscir\":\"\\u2A22\",\n  \"plusdo\":\"\\u2214\",\n  \"plusdu\":\"\\u2A25\",\n  \"pluse\":\"\\u2A72\",\n  \"PlusMinus\":\"\\u00B1\",\n  \"plusmn\":\"\\u00B1\",\n  \"plussim\":\"\\u2A26\",\n  \"plustwo\":\"\\u2A27\",\n  \"pm\":\"\\u00B1\",\n  \"Poincareplane\":\"\\u210C\",\n  \"pointint\":\"\\u2A15\",\n  \"Popf\":\"\\u2119\",\n  \"popf\":\"\\uD835\\uDD61\",\n  \"pound\":\"\\u00A3\",\n  \"Pr\":\"\\u2ABB\",\n  \"pr\":\"\\u227A\",\n  \"prap\":\"\\u2AB7\",\n  \"prcue\":\"\\u227C\",\n  \"prE\":\"\\u2AB3\",\n  \"pre\":\"\\u2AAF\",\n  \"prec\":\"\\u227A\",\n  \"precapprox\":\"\\u2AB7\",\n  \"preccurlyeq\":\"\\u227C\",\n  \"Precedes\":\"\\u227A\",\n  \"PrecedesEqual\":\"\\u2AAF\",\n  \"PrecedesSlantEqual\":\"\\u227C\",\n  \"PrecedesTilde\":\"\\u227E\",\n  \"preceq\":\"\\u2AAF\",\n  \"precnapprox\":\"\\u2AB9\",\n  \"precneqq\":\"\\u2AB5\",\n  \"precnsim\":\"\\u22E8\",\n  \"precsim\":\"\\u227E\",\n  \"Prime\":\"\\u2033\",\n  \"prime\":\"\\u2032\",\n  \"primes\":\"\\u2119\",\n  \"prnap\":\"\\u2AB9\",\n  \"prnE\":\"\\u2AB5\",\n  \"prnsim\":\"\\u22E8\",\n  \"prod\":\"\\u220F\",\n  \"Product\":\"\\u220F\",\n  \"profalar\":\"\\u232E\",\n  \"profline\":\"\\u2312\",\n  \"profsurf\":\"\\u2313\",\n  \"prop\":\"\\u221D\",\n  \"Proportion\":\"\\u2237\",\n  \"Proportional\":\"\\u221D\",\n  \"propto\":\"\\u221D\",\n  \"prsim\":\"\\u227E\",\n  \"prurel\":\"\\u22B0\",\n  \"Pscr\":\"\\uD835\\uDCAB\",\n  \"pscr\":\"\\uD835\\uDCC5\",\n  \"Psi\":\"\\u03A8\",\n  \"psi\":\"\\u03C8\",\n  \"puncsp\":\"\\u2008\",\n  \"Qfr\":\"\\uD835\\uDD14\",\n  \"qfr\":\"\\uD835\\uDD2E\",\n  \"qint\":\"\\u2A0C\",\n  \"Qopf\":\"\\u211A\",\n  \"qopf\":\"\\uD835\\uDD62\",\n  \"qprime\":\"\\u2057\",\n  \"Qscr\":\"\\uD835\\uDCAC\",\n  \"qscr\":\"\\uD835\\uDCC6\",\n  \"quaternions\":\"\\u210D\",\n  \"quatint\":\"\\u2A16\",\n  \"quest\":\"\\u003F\",\n  \"questeq\":\"\\u225F\",\n  \"QUOT\":\"\\u0022\",\n  \"quot\":\"\\u0022\",\n  \"rAarr\":\"\\u21DB\",\n  \"race\":\"\\u223D\\u0331\",\n  \"Racute\":\"\\u0154\",\n  \"racute\":\"\\u0155\",\n  \"radic\":\"\\u221A\",\n  \"raemptyv\":\"\\u29B3\",\n  \"Rang\":\"\\u27EB\",\n  \"rang\":\"\\u27E9\",\n  \"rangd\":\"\\u2992\",\n  \"range\":\"\\u29A5\",\n  \"rangle\":\"\\u27E9\",\n  \"raquo\":\"\\u00BB\",\n  \"Rarr\":\"\\u21A0\",\n  \"rArr\":\"\\u21D2\",\n  \"rarr\":\"\\u2192\",\n  \"rarrap\":\"\\u2975\",\n  \"rarrb\":\"\\u21E5\",\n  \"rarrbfs\":\"\\u2920\",\n  \"rarrc\":\"\\u2933\",\n  \"rarrfs\":\"\\u291E\",\n  \"rarrhk\":\"\\u21AA\",\n  \"rarrlp\":\"\\u21AC\",\n  \"rarrpl\":\"\\u2945\",\n  \"rarrsim\":\"\\u2974\",\n  \"Rarrtl\":\"\\u2916\",\n  \"rarrtl\":\"\\u21A3\",\n  \"rarrw\":\"\\u219D\",\n  \"rAtail\":\"\\u291C\",\n  \"ratail\":\"\\u291A\",\n  \"ratio\":\"\\u2236\",\n  \"rationals\":\"\\u211A\",\n  \"RBarr\":\"\\u2910\",\n  \"rBarr\":\"\\u290F\",\n  \"rbarr\":\"\\u290D\",\n  \"rbbrk\":\"\\u2773\",\n  \"rbrace\":\"\\u007D\",\n  \"rbrack\":\"\\u005D\",\n  \"rbrke\":\"\\u298C\",\n  \"rbrksld\":\"\\u298E\",\n  \"rbrkslu\":\"\\u2990\",\n  \"Rcaron\":\"\\u0158\",\n  \"rcaron\":\"\\u0159\",\n  \"Rcedil\":\"\\u0156\",\n  \"rcedil\":\"\\u0157\",\n  \"rceil\":\"\\u2309\",\n  \"rcub\":\"\\u007D\",\n  \"Rcy\":\"\\u0420\",\n  \"rcy\":\"\\u0440\",\n  \"rdca\":\"\\u2937\",\n  \"rdldhar\":\"\\u2969\",\n  \"rdquo\":\"\\u201D\",\n  \"rdquor\":\"\\u201D\",\n  \"rdsh\":\"\\u21B3\",\n  \"Re\":\"\\u211C\",\n  \"real\":\"\\u211C\",\n  \"realine\":\"\\u211B\",\n  \"realpart\":\"\\u211C\",\n  \"reals\":\"\\u211D\",\n  \"rect\":\"\\u25AD\",\n  \"REG\":\"\\u00AE\",\n  \"reg\":\"\\u00AE\",\n  \"ReverseElement\":\"\\u220B\",\n  \"ReverseEquilibrium\":\"\\u21CB\",\n  \"ReverseUpEquilibrium\":\"\\u296F\",\n  \"rfisht\":\"\\u297D\",\n  \"rfloor\":\"\\u230B\",\n  \"Rfr\":\"\\u211C\",\n  \"rfr\":\"\\uD835\\uDD2F\",\n  \"rHar\":\"\\u2964\",\n  \"rhard\":\"\\u21C1\",\n  \"rharu\":\"\\u21C0\",\n  \"rharul\":\"\\u296C\",\n  \"Rho\":\"\\u03A1\",\n  \"rho\":\"\\u03C1\",\n  \"rhov\":\"\\u03F1\",\n  \"RightAngleBracket\":\"\\u27E9\",\n  \"RightArrow\":\"\\u2192\",\n  \"Rightarrow\":\"\\u21D2\",\n  \"rightarrow\":\"\\u2192\",\n  \"RightArrowBar\":\"\\u21E5\",\n  \"RightArrowLeftArrow\":\"\\u21C4\",\n  \"rightarrowtail\":\"\\u21A3\",\n  \"RightCeiling\":\"\\u2309\",\n  \"RightDoubleBracket\":\"\\u27E7\",\n  \"RightDownTeeVector\":\"\\u295D\",\n  \"RightDownVector\":\"\\u21C2\",\n  \"RightDownVectorBar\":\"\\u2955\",\n  \"RightFloor\":\"\\u230B\",\n  \"rightharpoondown\":\"\\u21C1\",\n  \"rightharpoonup\":\"\\u21C0\",\n  \"rightleftarrows\":\"\\u21C4\",\n  \"rightleftharpoons\":\"\\u21CC\",\n  \"rightrightarrows\":\"\\u21C9\",\n  \"rightsquigarrow\":\"\\u219D\",\n  \"RightTee\":\"\\u22A2\",\n  \"RightTeeArrow\":\"\\u21A6\",\n  \"RightTeeVector\":\"\\u295B\",\n  \"rightthreetimes\":\"\\u22CC\",\n  \"RightTriangle\":\"\\u22B3\",\n  \"RightTriangleBar\":\"\\u29D0\",\n  \"RightTriangleEqual\":\"\\u22B5\",\n  \"RightUpDownVector\":\"\\u294F\",\n  \"RightUpTeeVector\":\"\\u295C\",\n  \"RightUpVector\":\"\\u21BE\",\n  \"RightUpVectorBar\":\"\\u2954\",\n  \"RightVector\":\"\\u21C0\",\n  \"RightVectorBar\":\"\\u2953\",\n  \"ring\":\"\\u02DA\",\n  \"risingdotseq\":\"\\u2253\",\n  \"rlarr\":\"\\u21C4\",\n  \"rlhar\":\"\\u21CC\",\n  \"rlm\":\"\\u200F\",\n  \"rmoust\":\"\\u23B1\",\n  \"rmoustache\":\"\\u23B1\",\n  \"rnmid\":\"\\u2AEE\",\n  \"roang\":\"\\u27ED\",\n  \"roarr\":\"\\u21FE\",\n  \"robrk\":\"\\u27E7\",\n  \"ropar\":\"\\u2986\",\n  \"Ropf\":\"\\u211D\",\n  \"ropf\":\"\\uD835\\uDD63\",\n  \"roplus\":\"\\u2A2E\",\n  \"rotimes\":\"\\u2A35\",\n  \"RoundImplies\":\"\\u2970\",\n  \"rpar\":\"\\u0029\",\n  \"rpargt\":\"\\u2994\",\n  \"rppolint\":\"\\u2A12\",\n  \"rrarr\":\"\\u21C9\",\n  \"Rrightarrow\":\"\\u21DB\",\n  \"rsaquo\":\"\\u203A\",\n  \"Rscr\":\"\\u211B\",\n  \"rscr\":\"\\uD835\\uDCC7\",\n  \"Rsh\":\"\\u21B1\",\n  \"rsh\":\"\\u21B1\",\n  \"rsqb\":\"\\u005D\",\n  \"rsquo\":\"\\u2019\",\n  \"rsquor\":\"\\u2019\",\n  \"rthree\":\"\\u22CC\",\n  \"rtimes\":\"\\u22CA\",\n  \"rtri\":\"\\u25B9\",\n  \"rtrie\":\"\\u22B5\",\n  \"rtrif\":\"\\u25B8\",\n  \"rtriltri\":\"\\u29CE\",\n  \"RuleDelayed\":\"\\u29F4\",\n  \"ruluhar\":\"\\u2968\",\n  \"rx\":\"\\u211E\",\n  \"Sacute\":\"\\u015A\",\n  \"sacute\":\"\\u015B\",\n  \"sbquo\":\"\\u201A\",\n  \"Sc\":\"\\u2ABC\",\n  \"sc\":\"\\u227B\",\n  \"scap\":\"\\u2AB8\",\n  \"Scaron\":\"\\u0160\",\n  \"scaron\":\"\\u0161\",\n  \"sccue\":\"\\u227D\",\n  \"scE\":\"\\u2AB4\",\n  \"sce\":\"\\u2AB0\",\n  \"Scedil\":\"\\u015E\",\n  \"scedil\":\"\\u015F\",\n  \"Scirc\":\"\\u015C\",\n  \"scirc\":\"\\u015D\",\n  \"scnap\":\"\\u2ABA\",\n  \"scnE\":\"\\u2AB6\",\n  \"scnsim\":\"\\u22E9\",\n  \"scpolint\":\"\\u2A13\",\n  \"scsim\":\"\\u227F\",\n  \"Scy\":\"\\u0421\",\n  \"scy\":\"\\u0441\",\n  \"sdot\":\"\\u22C5\",\n  \"sdotb\":\"\\u22A1\",\n  \"sdote\":\"\\u2A66\",\n  \"searhk\":\"\\u2925\",\n  \"seArr\":\"\\u21D8\",\n  \"searr\":\"\\u2198\",\n  \"searrow\":\"\\u2198\",\n  \"sect\":\"\\u00A7\",\n  \"semi\":\"\\u003B\",\n  \"seswar\":\"\\u2929\",\n  \"setminus\":\"\\u2216\",\n  \"setmn\":\"\\u2216\",\n  \"sext\":\"\\u2736\",\n  \"Sfr\":\"\\uD835\\uDD16\",\n  \"sfr\":\"\\uD835\\uDD30\",\n  \"sfrown\":\"\\u2322\",\n  \"sharp\":\"\\u266F\",\n  \"SHCHcy\":\"\\u0429\",\n  \"shchcy\":\"\\u0449\",\n  \"SHcy\":\"\\u0428\",\n  \"shcy\":\"\\u0448\",\n  \"ShortDownArrow\":\"\\u2193\",\n  \"ShortLeftArrow\":\"\\u2190\",\n  \"shortmid\":\"\\u2223\",\n  \"shortparallel\":\"\\u2225\",\n  \"ShortRightArrow\":\"\\u2192\",\n  \"ShortUpArrow\":\"\\u2191\",\n  \"shy\":\"\\u00AD\",\n  \"Sigma\":\"\\u03A3\",\n  \"sigma\":\"\\u03C3\",\n  \"sigmaf\":\"\\u03C2\",\n  \"sigmav\":\"\\u03C2\",\n  \"sim\":\"\\u223C\",\n  \"simdot\":\"\\u2A6A\",\n  \"sime\":\"\\u2243\",\n  \"simeq\":\"\\u2243\",\n  \"simg\":\"\\u2A9E\",\n  \"simgE\":\"\\u2AA0\",\n  \"siml\":\"\\u2A9D\",\n  \"simlE\":\"\\u2A9F\",\n  \"simne\":\"\\u2246\",\n  \"simplus\":\"\\u2A24\",\n  \"simrarr\":\"\\u2972\",\n  \"slarr\":\"\\u2190\",\n  \"SmallCircle\":\"\\u2218\",\n  \"smallsetminus\":\"\\u2216\",\n  \"smashp\":\"\\u2A33\",\n  \"smeparsl\":\"\\u29E4\",\n  \"smid\":\"\\u2223\",\n  \"smile\":\"\\u2323\",\n  \"smt\":\"\\u2AAA\",\n  \"smte\":\"\\u2AAC\",\n  \"smtes\":\"\\u2AAC\\uFE00\",\n  \"SOFTcy\":\"\\u042C\",\n  \"softcy\":\"\\u044C\",\n  \"sol\":\"\\u002F\",\n  \"solb\":\"\\u29C4\",\n  \"solbar\":\"\\u233F\",\n  \"Sopf\":\"\\uD835\\uDD4A\",\n  \"sopf\":\"\\uD835\\uDD64\",\n  \"spades\":\"\\u2660\",\n  \"spadesuit\":\"\\u2660\",\n  \"spar\":\"\\u2225\",\n  \"sqcap\":\"\\u2293\",\n  \"sqcaps\":\"\\u2293\\uFE00\",\n  \"sqcup\":\"\\u2294\",\n  \"sqcups\":\"\\u2294\\uFE00\",\n  \"Sqrt\":\"\\u221A\",\n  \"sqsub\":\"\\u228F\",\n  \"sqsube\":\"\\u2291\",\n  \"sqsubset\":\"\\u228F\",\n  \"sqsubseteq\":\"\\u2291\",\n  \"sqsup\":\"\\u2290\",\n  \"sqsupe\":\"\\u2292\",\n  \"sqsupset\":\"\\u2290\",\n  \"sqsupseteq\":\"\\u2292\",\n  \"squ\":\"\\u25A1\",\n  \"Square\":\"\\u25A1\",\n  \"square\":\"\\u25A1\",\n  \"SquareIntersection\":\"\\u2293\",\n  \"SquareSubset\":\"\\u228F\",\n  \"SquareSubsetEqual\":\"\\u2291\",\n  \"SquareSuperset\":\"\\u2290\",\n  \"SquareSupersetEqual\":\"\\u2292\",\n  \"SquareUnion\":\"\\u2294\",\n  \"squarf\":\"\\u25AA\",\n  \"squf\":\"\\u25AA\",\n  \"srarr\":\"\\u2192\",\n  \"Sscr\":\"\\uD835\\uDCAE\",\n  \"sscr\":\"\\uD835\\uDCC8\",\n  \"ssetmn\":\"\\u2216\",\n  \"ssmile\":\"\\u2323\",\n  \"sstarf\":\"\\u22C6\",\n  \"Star\":\"\\u22C6\",\n  \"star\":\"\\u2606\",\n  \"starf\":\"\\u2605\",\n  \"straightepsilon\":\"\\u03F5\",\n  \"straightphi\":\"\\u03D5\",\n  \"strns\":\"\\u00AF\",\n  \"Sub\":\"\\u22D0\",\n  \"sub\":\"\\u2282\",\n  \"subdot\":\"\\u2ABD\",\n  \"subE\":\"\\u2AC5\",\n  \"sube\":\"\\u2286\",\n  \"subedot\":\"\\u2AC3\",\n  \"submult\":\"\\u2AC1\",\n  \"subnE\":\"\\u2ACB\",\n  \"subne\":\"\\u228A\",\n  \"subplus\":\"\\u2ABF\",\n  \"subrarr\":\"\\u2979\",\n  \"Subset\":\"\\u22D0\",\n  \"subset\":\"\\u2282\",\n  \"subseteq\":\"\\u2286\",\n  \"subseteqq\":\"\\u2AC5\",\n  \"SubsetEqual\":\"\\u2286\",\n  \"subsetneq\":\"\\u228A\",\n  \"subsetneqq\":\"\\u2ACB\",\n  \"subsim\":\"\\u2AC7\",\n  \"subsub\":\"\\u2AD5\",\n  \"subsup\":\"\\u2AD3\",\n  \"succ\":\"\\u227B\",\n  \"succapprox\":\"\\u2AB8\",\n  \"succcurlyeq\":\"\\u227D\",\n  \"Succeeds\":\"\\u227B\",\n  \"SucceedsEqual\":\"\\u2AB0\",\n  \"SucceedsSlantEqual\":\"\\u227D\",\n  \"SucceedsTilde\":\"\\u227F\",\n  \"succeq\":\"\\u2AB0\",\n  \"succnapprox\":\"\\u2ABA\",\n  \"succneqq\":\"\\u2AB6\",\n  \"succnsim\":\"\\u22E9\",\n  \"succsim\":\"\\u227F\",\n  \"SuchThat\":\"\\u220B\",\n  \"Sum\":\"\\u2211\",\n  \"sum\":\"\\u2211\",\n  \"sung\":\"\\u266A\",\n  \"Sup\":\"\\u22D1\",\n  \"sup\":\"\\u2283\",\n  \"sup1\":\"\\u00B9\",\n  \"sup2\":\"\\u00B2\",\n  \"sup3\":\"\\u00B3\",\n  \"supdot\":\"\\u2ABE\",\n  \"supdsub\":\"\\u2AD8\",\n  \"supE\":\"\\u2AC6\",\n  \"supe\":\"\\u2287\",\n  \"supedot\":\"\\u2AC4\",\n  \"Superset\":\"\\u2283\",\n  \"SupersetEqual\":\"\\u2287\",\n  \"suphsol\":\"\\u27C9\",\n  \"suphsub\":\"\\u2AD7\",\n  \"suplarr\":\"\\u297B\",\n  \"supmult\":\"\\u2AC2\",\n  \"supnE\":\"\\u2ACC\",\n  \"supne\":\"\\u228B\",\n  \"supplus\":\"\\u2AC0\",\n  \"Supset\":\"\\u22D1\",\n  \"supset\":\"\\u2283\",\n  \"supseteq\":\"\\u2287\",\n  \"supseteqq\":\"\\u2AC6\",\n  \"supsetneq\":\"\\u228B\",\n  \"supsetneqq\":\"\\u2ACC\",\n  \"supsim\":\"\\u2AC8\",\n  \"supsub\":\"\\u2AD4\",\n  \"supsup\":\"\\u2AD6\",\n  \"swarhk\":\"\\u2926\",\n  \"swArr\":\"\\u21D9\",\n  \"swarr\":\"\\u2199\",\n  \"swarrow\":\"\\u2199\",\n  \"swnwar\":\"\\u292A\",\n  \"szlig\":\"\\u00DF\",\n  \"Tab\":\"\\u0009\",\n  \"target\":\"\\u2316\",\n  \"Tau\":\"\\u03A4\",\n  \"tau\":\"\\u03C4\",\n  \"tbrk\":\"\\u23B4\",\n  \"Tcaron\":\"\\u0164\",\n  \"tcaron\":\"\\u0165\",\n  \"Tcedil\":\"\\u0162\",\n  \"tcedil\":\"\\u0163\",\n  \"Tcy\":\"\\u0422\",\n  \"tcy\":\"\\u0442\",\n  \"tdot\":\"\\u20DB\",\n  \"telrec\":\"\\u2315\",\n  \"Tfr\":\"\\uD835\\uDD17\",\n  \"tfr\":\"\\uD835\\uDD31\",\n  \"there4\":\"\\u2234\",\n  \"Therefore\":\"\\u2234\",\n  \"therefore\":\"\\u2234\",\n  \"Theta\":\"\\u0398\",\n  \"theta\":\"\\u03B8\",\n  \"thetasym\":\"\\u03D1\",\n  \"thetav\":\"\\u03D1\",\n  \"thickapprox\":\"\\u2248\",\n  \"thicksim\":\"\\u223C\",\n  \"ThickSpace\":\"\\u205F\\u200A\",\n  \"thinsp\":\"\\u2009\",\n  \"ThinSpace\":\"\\u2009\",\n  \"thkap\":\"\\u2248\",\n  \"thksim\":\"\\u223C\",\n  \"THORN\":\"\\u00DE\",\n  \"thorn\":\"\\u00FE\",\n  \"Tilde\":\"\\u223C\",\n  \"tilde\":\"\\u02DC\",\n  \"TildeEqual\":\"\\u2243\",\n  \"TildeFullEqual\":\"\\u2245\",\n  \"TildeTilde\":\"\\u2248\",\n  \"times\":\"\\u00D7\",\n  \"timesb\":\"\\u22A0\",\n  \"timesbar\":\"\\u2A31\",\n  \"timesd\":\"\\u2A30\",\n  \"tint\":\"\\u222D\",\n  \"toea\":\"\\u2928\",\n  \"top\":\"\\u22A4\",\n  \"topbot\":\"\\u2336\",\n  \"topcir\":\"\\u2AF1\",\n  \"Topf\":\"\\uD835\\uDD4B\",\n  \"topf\":\"\\uD835\\uDD65\",\n  \"topfork\":\"\\u2ADA\",\n  \"tosa\":\"\\u2929\",\n  \"tprime\":\"\\u2034\",\n  \"TRADE\":\"\\u2122\",\n  \"trade\":\"\\u2122\",\n  \"triangle\":\"\\u25B5\",\n  \"triangledown\":\"\\u25BF\",\n  \"triangleleft\":\"\\u25C3\",\n  \"trianglelefteq\":\"\\u22B4\",\n  \"triangleq\":\"\\u225C\",\n  \"triangleright\":\"\\u25B9\",\n  \"trianglerighteq\":\"\\u22B5\",\n  \"tridot\":\"\\u25EC\",\n  \"trie\":\"\\u225C\",\n  \"triminus\":\"\\u2A3A\",\n  \"TripleDot\":\"\\u20DB\",\n  \"triplus\":\"\\u2A39\",\n  \"trisb\":\"\\u29CD\",\n  \"tritime\":\"\\u2A3B\",\n  \"trpezium\":\"\\u23E2\",\n  \"Tscr\":\"\\uD835\\uDCAF\",\n  \"tscr\":\"\\uD835\\uDCC9\",\n  \"TScy\":\"\\u0426\",\n  \"tscy\":\"\\u0446\",\n  \"TSHcy\":\"\\u040B\",\n  \"tshcy\":\"\\u045B\",\n  \"Tstrok\":\"\\u0166\",\n  \"tstrok\":\"\\u0167\",\n  \"twixt\":\"\\u226C\",\n  \"twoheadleftarrow\":\"\\u219E\",\n  \"twoheadrightarrow\":\"\\u21A0\",\n  \"Uacute\":\"\\u00DA\",\n  \"uacute\":\"\\u00FA\",\n  \"Uarr\":\"\\u219F\",\n  \"uArr\":\"\\u21D1\",\n  \"uarr\":\"\\u2191\",\n  \"Uarrocir\":\"\\u2949\",\n  \"Ubrcy\":\"\\u040E\",\n  \"ubrcy\":\"\\u045E\",\n  \"Ubreve\":\"\\u016C\",\n  \"ubreve\":\"\\u016D\",\n  \"Ucirc\":\"\\u00DB\",\n  \"ucirc\":\"\\u00FB\",\n  \"Ucy\":\"\\u0423\",\n  \"ucy\":\"\\u0443\",\n  \"udarr\":\"\\u21C5\",\n  \"Udblac\":\"\\u0170\",\n  \"udblac\":\"\\u0171\",\n  \"udhar\":\"\\u296E\",\n  \"ufisht\":\"\\u297E\",\n  \"Ufr\":\"\\uD835\\uDD18\",\n  \"ufr\":\"\\uD835\\uDD32\",\n  \"Ugrave\":\"\\u00D9\",\n  \"ugrave\":\"\\u00F9\",\n  \"uHar\":\"\\u2963\",\n  \"uharl\":\"\\u21BF\",\n  \"uharr\":\"\\u21BE\",\n  \"uhblk\":\"\\u2580\",\n  \"ulcorn\":\"\\u231C\",\n  \"ulcorner\":\"\\u231C\",\n  \"ulcrop\":\"\\u230F\",\n  \"ultri\":\"\\u25F8\",\n  \"Umacr\":\"\\u016A\",\n  \"umacr\":\"\\u016B\",\n  \"uml\":\"\\u00A8\",\n  \"UnderBar\":\"\\u005F\",\n  \"UnderBrace\":\"\\u23DF\",\n  \"UnderBracket\":\"\\u23B5\",\n  \"UnderParenthesis\":\"\\u23DD\",\n  \"Union\":\"\\u22C3\",\n  \"UnionPlus\":\"\\u228E\",\n  \"Uogon\":\"\\u0172\",\n  \"uogon\":\"\\u0173\",\n  \"Uopf\":\"\\uD835\\uDD4C\",\n  \"uopf\":\"\\uD835\\uDD66\",\n  \"UpArrow\":\"\\u2191\",\n  \"Uparrow\":\"\\u21D1\",\n  \"uparrow\":\"\\u2191\",\n  \"UpArrowBar\":\"\\u2912\",\n  \"UpArrowDownArrow\":\"\\u21C5\",\n  \"UpDownArrow\":\"\\u2195\",\n  \"Updownarrow\":\"\\u21D5\",\n  \"updownarrow\":\"\\u2195\",\n  \"UpEquilibrium\":\"\\u296E\",\n  \"upharpoonleft\":\"\\u21BF\",\n  \"upharpoonright\":\"\\u21BE\",\n  \"uplus\":\"\\u228E\",\n  \"UpperLeftArrow\":\"\\u2196\",\n  \"UpperRightArrow\":\"\\u2197\",\n  \"Upsi\":\"\\u03D2\",\n  \"upsi\":\"\\u03C5\",\n  \"upsih\":\"\\u03D2\",\n  \"Upsilon\":\"\\u03A5\",\n  \"upsilon\":\"\\u03C5\",\n  \"UpTee\":\"\\u22A5\",\n  \"UpTeeArrow\":\"\\u21A5\",\n  \"upuparrows\":\"\\u21C8\",\n  \"urcorn\":\"\\u231D\",\n  \"urcorner\":\"\\u231D\",\n  \"urcrop\":\"\\u230E\",\n  \"Uring\":\"\\u016E\",\n  \"uring\":\"\\u016F\",\n  \"urtri\":\"\\u25F9\",\n  \"Uscr\":\"\\uD835\\uDCB0\",\n  \"uscr\":\"\\uD835\\uDCCA\",\n  \"utdot\":\"\\u22F0\",\n  \"Utilde\":\"\\u0168\",\n  \"utilde\":\"\\u0169\",\n  \"utri\":\"\\u25B5\",\n  \"utrif\":\"\\u25B4\",\n  \"uuarr\":\"\\u21C8\",\n  \"Uuml\":\"\\u00DC\",\n  \"uuml\":\"\\u00FC\",\n  \"uwangle\":\"\\u29A7\",\n  \"vangrt\":\"\\u299C\",\n  \"varepsilon\":\"\\u03F5\",\n  \"varkappa\":\"\\u03F0\",\n  \"varnothing\":\"\\u2205\",\n  \"varphi\":\"\\u03D5\",\n  \"varpi\":\"\\u03D6\",\n  \"varpropto\":\"\\u221D\",\n  \"vArr\":\"\\u21D5\",\n  \"varr\":\"\\u2195\",\n  \"varrho\":\"\\u03F1\",\n  \"varsigma\":\"\\u03C2\",\n  \"varsubsetneq\":\"\\u228A\\uFE00\",\n  \"varsubsetneqq\":\"\\u2ACB\\uFE00\",\n  \"varsupsetneq\":\"\\u228B\\uFE00\",\n  \"varsupsetneqq\":\"\\u2ACC\\uFE00\",\n  \"vartheta\":\"\\u03D1\",\n  \"vartriangleleft\":\"\\u22B2\",\n  \"vartriangleright\":\"\\u22B3\",\n  \"Vbar\":\"\\u2AEB\",\n  \"vBar\":\"\\u2AE8\",\n  \"vBarv\":\"\\u2AE9\",\n  \"Vcy\":\"\\u0412\",\n  \"vcy\":\"\\u0432\",\n  \"VDash\":\"\\u22AB\",\n  \"Vdash\":\"\\u22A9\",\n  \"vDash\":\"\\u22A8\",\n  \"vdash\":\"\\u22A2\",\n  \"Vdashl\":\"\\u2AE6\",\n  \"Vee\":\"\\u22C1\",\n  \"vee\":\"\\u2228\",\n  \"veebar\":\"\\u22BB\",\n  \"veeeq\":\"\\u225A\",\n  \"vellip\":\"\\u22EE\",\n  \"Verbar\":\"\\u2016\",\n  \"verbar\":\"\\u007C\",\n  \"Vert\":\"\\u2016\",\n  \"vert\":\"\\u007C\",\n  \"VerticalBar\":\"\\u2223\",\n  \"VerticalLine\":\"\\u007C\",\n  \"VerticalSeparator\":\"\\u2758\",\n  \"VerticalTilde\":\"\\u2240\",\n  \"VeryThinSpace\":\"\\u200A\",\n  \"Vfr\":\"\\uD835\\uDD19\",\n  \"vfr\":\"\\uD835\\uDD33\",\n  \"vltri\":\"\\u22B2\",\n  \"vnsub\":\"\\u2282\\u20D2\",\n  \"vnsup\":\"\\u2283\\u20D2\",\n  \"Vopf\":\"\\uD835\\uDD4D\",\n  \"vopf\":\"\\uD835\\uDD67\",\n  \"vprop\":\"\\u221D\",\n  \"vrtri\":\"\\u22B3\",\n  \"Vscr\":\"\\uD835\\uDCB1\",\n  \"vscr\":\"\\uD835\\uDCCB\",\n  \"vsubnE\":\"\\u2ACB\\uFE00\",\n  \"vsubne\":\"\\u228A\\uFE00\",\n  \"vsupnE\":\"\\u2ACC\\uFE00\",\n  \"vsupne\":\"\\u228B\\uFE00\",\n  \"Vvdash\":\"\\u22AA\",\n  \"vzigzag\":\"\\u299A\",\n  \"Wcirc\":\"\\u0174\",\n  \"wcirc\":\"\\u0175\",\n  \"wedbar\":\"\\u2A5F\",\n  \"Wedge\":\"\\u22C0\",\n  \"wedge\":\"\\u2227\",\n  \"wedgeq\":\"\\u2259\",\n  \"weierp\":\"\\u2118\",\n  \"Wfr\":\"\\uD835\\uDD1A\",\n  \"wfr\":\"\\uD835\\uDD34\",\n  \"Wopf\":\"\\uD835\\uDD4E\",\n  \"wopf\":\"\\uD835\\uDD68\",\n  \"wp\":\"\\u2118\",\n  \"wr\":\"\\u2240\",\n  \"wreath\":\"\\u2240\",\n  \"Wscr\":\"\\uD835\\uDCB2\",\n  \"wscr\":\"\\uD835\\uDCCC\",\n  \"xcap\":\"\\u22C2\",\n  \"xcirc\":\"\\u25EF\",\n  \"xcup\":\"\\u22C3\",\n  \"xdtri\":\"\\u25BD\",\n  \"Xfr\":\"\\uD835\\uDD1B\",\n  \"xfr\":\"\\uD835\\uDD35\",\n  \"xhArr\":\"\\u27FA\",\n  \"xharr\":\"\\u27F7\",\n  \"Xi\":\"\\u039E\",\n  \"xi\":\"\\u03BE\",\n  \"xlArr\":\"\\u27F8\",\n  \"xlarr\":\"\\u27F5\",\n  \"xmap\":\"\\u27FC\",\n  \"xnis\":\"\\u22FB\",\n  \"xodot\":\"\\u2A00\",\n  \"Xopf\":\"\\uD835\\uDD4F\",\n  \"xopf\":\"\\uD835\\uDD69\",\n  \"xoplus\":\"\\u2A01\",\n  \"xotime\":\"\\u2A02\",\n  \"xrArr\":\"\\u27F9\",\n  \"xrarr\":\"\\u27F6\",\n  \"Xscr\":\"\\uD835\\uDCB3\",\n  \"xscr\":\"\\uD835\\uDCCD\",\n  \"xsqcup\":\"\\u2A06\",\n  \"xuplus\":\"\\u2A04\",\n  \"xutri\":\"\\u25B3\",\n  \"xvee\":\"\\u22C1\",\n  \"xwedge\":\"\\u22C0\",\n  \"Yacute\":\"\\u00DD\",\n  \"yacute\":\"\\u00FD\",\n  \"YAcy\":\"\\u042F\",\n  \"yacy\":\"\\u044F\",\n  \"Ycirc\":\"\\u0176\",\n  \"ycirc\":\"\\u0177\",\n  \"Ycy\":\"\\u042B\",\n  \"ycy\":\"\\u044B\",\n  \"yen\":\"\\u00A5\",\n  \"Yfr\":\"\\uD835\\uDD1C\",\n  \"yfr\":\"\\uD835\\uDD36\",\n  \"YIcy\":\"\\u0407\",\n  \"yicy\":\"\\u0457\",\n  \"Yopf\":\"\\uD835\\uDD50\",\n  \"yopf\":\"\\uD835\\uDD6A\",\n  \"Yscr\":\"\\uD835\\uDCB4\",\n  \"yscr\":\"\\uD835\\uDCCE\",\n  \"YUcy\":\"\\u042E\",\n  \"yucy\":\"\\u044E\",\n  \"Yuml\":\"\\u0178\",\n  \"yuml\":\"\\u00FF\",\n  \"Zacute\":\"\\u0179\",\n  \"zacute\":\"\\u017A\",\n  \"Zcaron\":\"\\u017D\",\n  \"zcaron\":\"\\u017E\",\n  \"Zcy\":\"\\u0417\",\n  \"zcy\":\"\\u0437\",\n  \"Zdot\":\"\\u017B\",\n  \"zdot\":\"\\u017C\",\n  \"zeetrf\":\"\\u2128\",\n  \"ZeroWidthSpace\":\"\\u200B\",\n  \"Zeta\":\"\\u0396\",\n  \"zeta\":\"\\u03B6\",\n  \"Zfr\":\"\\u2128\",\n  \"zfr\":\"\\uD835\\uDD37\",\n  \"ZHcy\":\"\\u0416\",\n  \"zhcy\":\"\\u0436\",\n  \"zigrarr\":\"\\u21DD\",\n  \"Zopf\":\"\\u2124\",\n  \"zopf\":\"\\uD835\\uDD6B\",\n  \"Zscr\":\"\\uD835\\uDCB5\",\n  \"zscr\":\"\\uD835\\uDCCF\",\n  \"zwj\":\"\\u200D\",\n  \"zwnj\":\"\\u200C\"\n};\n\n},{}],2:[function(require,module,exports){\n// List of valid html blocks names, accorting to commonmark spec\n// http://jgm.github.io/CommonMark/spec.html#html-blocks\n\n'use strict';\n\nvar html_blocks = {};\n\n[\n  'article',\n  'aside',\n  'button',\n  'blockquote',\n  'body',\n  'canvas',\n  'caption',\n  'col',\n  'colgroup',\n  'dd',\n  'div',\n  'dl',\n  'dt',\n  'embed',\n  'fieldset',\n  'figcaption',\n  'figure',\n  'footer',\n  'form',\n  'h1',\n  'h2',\n  'h3',\n  'h4',\n  'h5',\n  'h6',\n  'header',\n  'hgroup',\n  'hr',\n  'iframe',\n  'li',\n  'map',\n  'object',\n  'ol',\n  'output',\n  'p',\n  'pre',\n  'progress',\n  'script',\n  'section',\n  'style',\n  'table',\n  'tbody',\n  'td',\n  'textarea',\n  'tfoot',\n  'th',\n  'tr',\n  'thead',\n  'ul',\n  'video'\n].forEach(function (name) { html_blocks[name] = true; });\n\n\nmodule.exports = html_blocks;\n\n},{}],3:[function(require,module,exports){\n// Regexps to match html elements\n\n'use strict';\n\n\nfunction replace(regex, options) {\n  regex = regex.source;\n  options = options || '';\n\n  return function self(name, val) {\n    if (!name) {\n      return new RegExp(regex, options);\n    }\n    val = val.source;\n    regex = regex.replace(name, val);\n    return self;\n  };\n}\n\n\nvar attr_name     = /[a-zA-Z_:][a-zA-Z0-9:._-]*/;\n\nvar unquoted      = /[^\"'=<>`\\x00-\\x20]+/;\nvar single_quoted = /'[^']*'/;\nvar double_quoted = /\"[^\"]*\"/;\n\n/*eslint no-spaced-func:0*/\nvar attr_value  = replace(/(?:unquoted|single_quoted|double_quoted)/)\n                    ('unquoted', unquoted)\n                    ('single_quoted', single_quoted)\n                    ('double_quoted', double_quoted)\n                    ();\n\nvar attribute   = replace(/(?:\\s+attr_name(?:\\s*=\\s*attr_value)?)/)\n                    ('attr_name', attr_name)\n                    ('attr_value', attr_value)\n                    ();\n\nvar open_tag    = replace(/<[A-Za-z][A-Za-z0-9\\-]*attribute*\\s*\\/?>/)\n                    ('attribute', attribute)\n                    ();\n\nvar close_tag   = /<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>/;\n// That's less strict than http://www.w3.org/TR/html5/syntax.html#comments\n// but we do the rest of check in \"inline\" rule.\nvar comment     = /<!--[\\s\\S]*?-->/;\nvar processing  = /<[?].*?[?]>/;\nvar declaration = /<![A-Z]+\\s+[^>]*>/;\nvar cdata       = /<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/;\n\nvar HTML_TAG_RE = replace(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/)\n  ('open_tag', open_tag)\n  ('close_tag', close_tag)\n  ('comment', comment)\n  ('processing', processing)\n  ('declaration', declaration)\n  ('cdata', cdata)\n  ();\n\n\nmodule.exports.HTML_TAG_RE = HTML_TAG_RE;\n\n},{}],4:[function(require,module,exports){\n// List of valid url schemas, accorting to commonmark spec\n// http://jgm.github.io/CommonMark/spec.html#autolinks\n\n'use strict';\n\n\nmodule.exports = [\n  'coap',\n  'doi',\n  'javascript',\n  'aaa',\n  'aaas',\n  'about',\n  'acap',\n  'cap',\n  'cid',\n  'crid',\n  'data',\n  'dav',\n  'dict',\n  'dns',\n  'file',\n  'ftp',\n  'geo',\n  'go',\n  'gopher',\n  'h323',\n  'http',\n  'https',\n  'iax',\n  'icap',\n  'im',\n  'imap',\n  'info',\n  'ipp',\n  'iris',\n  'iris.beep',\n  'iris.xpc',\n  'iris.xpcs',\n  'iris.lwz',\n  'ldap',\n  'mailto',\n  'mid',\n  'msrp',\n  'msrps',\n  'mtqp',\n  'mupdate',\n  'news',\n  'nfs',\n  'ni',\n  'nih',\n  'nntp',\n  'opaquelocktoken',\n  'pop',\n  'pres',\n  'rtsp',\n  'service',\n  'session',\n  'shttp',\n  'sieve',\n  'sip',\n  'sips',\n  'sms',\n  'snmp',\n  'soap.beep',\n  'soap.beeps',\n  'tag',\n  'tel',\n  'telnet',\n  'tftp',\n  'thismessage',\n  'tn3270',\n  'tip',\n  'tv',\n  'urn',\n  'vemmi',\n  'ws',\n  'wss',\n  'xcon',\n  'xcon-userid',\n  'xmlrpc.beep',\n  'xmlrpc.beeps',\n  'xmpp',\n  'z39.50r',\n  'z39.50s',\n  'adiumxtra',\n  'afp',\n  'afs',\n  'aim',\n  'apt',\n  'attachment',\n  'aw',\n  'beshare',\n  'bitcoin',\n  'bolo',\n  'callto',\n  'chrome',\n  'chrome-extension',\n  'com-eventbrite-attendee',\n  'content',\n  'cvs',\n  'dlna-playsingle',\n  'dlna-playcontainer',\n  'dtn',\n  'dvb',\n  'ed2k',\n  'facetime',\n  'feed',\n  'finger',\n  'fish',\n  'gg',\n  'git',\n  'gizmoproject',\n  'gtalk',\n  'hcp',\n  'icon',\n  'ipn',\n  'irc',\n  'irc6',\n  'ircs',\n  'itms',\n  'jar',\n  'jms',\n  'keyparc',\n  'lastfm',\n  'ldaps',\n  'magnet',\n  'maps',\n  'market',\n  'message',\n  'mms',\n  'ms-help',\n  'msnim',\n  'mumble',\n  'mvn',\n  'notes',\n  'oid',\n  'palm',\n  'paparazzi',\n  'platform',\n  'proxy',\n  'psyc',\n  'query',\n  'res',\n  'resource',\n  'rmi',\n  'rsync',\n  'rtmp',\n  'secondlife',\n  'sftp',\n  'sgn',\n  'skype',\n  'smb',\n  'soldat',\n  'spotify',\n  'ssh',\n  'steam',\n  'svn',\n  'teamspeak',\n  'things',\n  'udp',\n  'unreal',\n  'ut2004',\n  'ventrilo',\n  'view-source',\n  'webcal',\n  'wtai',\n  'wyciwyg',\n  'xfire',\n  'xri',\n  'ymsgr'\n];\n\n},{}],5:[function(require,module,exports){\n// Utilities\n//\n'use strict';\n\n\nfunction _class(obj) { return Object.prototype.toString.call(obj); }\n\nfunction isString(obj) { return _class(obj) === '[object String]'; }\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction has(object, key) {\n  return _hasOwnProperty.call(object, key);\n}\n\n// Merge objects\n//\nfunction assign(obj /*from1, from2, from3, ...*/) {\n  var sources = Array.prototype.slice.call(arguments, 1);\n\n  sources.forEach(function (source) {\n    if (!source) { return; }\n\n    if (typeof source !== 'object') {\n      throw new TypeError(source + 'must be object');\n    }\n\n    Object.keys(source).forEach(function (key) {\n      obj[key] = source[key];\n    });\n  });\n\n  return obj;\n}\n\n// Remove element from array and put another array at those position.\n// Useful for some operations with tokens\nfunction arrayReplaceAt(src, pos, newElements) {\n  return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1));\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar UNESCAPE_MD_RE = /\\\\([!\"#$%&'()*+,\\-.\\/:;<=>?@[\\\\\\]^_`{|}~])/g;\n\nfunction unescapeMd(str) {\n  if (str.indexOf('\\\\') < 0) { return str; }\n  return str.replace(UNESCAPE_MD_RE, '$1');\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nfunction isValidEntityCode(c) {\n  /*eslint no-bitwise:0*/\n  // broken sequence\n  if (c >= 0xD800 && c <= 0xDFFF) { return false; }\n  // never used\n  if (c >= 0xFDD0 && c <= 0xFDEF) { return false; }\n  if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; }\n  // control codes\n  if (c >= 0x00 && c <= 0x08) { return false; }\n  if (c === 0x0B) { return false; }\n  if (c >= 0x0E && c <= 0x1F) { return false; }\n  if (c >= 0x7F && c <= 0x9F) { return false; }\n  // out of range\n  if (c > 0x10FFFF) { return false; }\n  return true;\n}\n\nfunction fromCodePoint(c) {\n  /*eslint no-bitwise:0*/\n  if (c > 0xffff) {\n    c -= 0x10000;\n    var surrogate1 = 0xd800 + (c >> 10),\n        surrogate2 = 0xdc00 + (c & 0x3ff);\n\n    return String.fromCharCode(surrogate1, surrogate2);\n  }\n  return String.fromCharCode(c);\n}\n\nvar NAMED_ENTITY_RE   = /&([a-z#][a-z0-9]{1,31});/gi;\nvar DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i;\nvar entities = require('./entities');\n\nfunction replaceEntityPattern(match, name) {\n  var code = 0;\n\n  if (has(entities, name)) {\n    return entities[name];\n  } else if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) {\n    code = name[1].toLowerCase() === 'x' ?\n      parseInt(name.slice(2), 16)\n    :\n      parseInt(name.slice(1), 10);\n    if (isValidEntityCode(code)) {\n      return fromCodePoint(code);\n    }\n  }\n  return match;\n}\n\nfunction replaceEntities(str) {\n  if (str.indexOf('&') < 0) { return str; }\n\n  return str.replace(NAMED_ENTITY_RE, replaceEntityPattern);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar HTML_ESCAPE_TEST_RE = /[&<>\"]/;\nvar HTML_ESCAPE_REPLACE_RE = /[&<>\"]/g;\nvar HTML_REPLACEMENTS = {\n  '&': '&amp;',\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;'\n};\n\nfunction replaceUnsafeChar(ch) {\n  return HTML_REPLACEMENTS[ch];\n}\n\nfunction escapeHtml(str) {\n  if (HTML_ESCAPE_TEST_RE.test(str)) {\n    return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);\n  }\n  return str;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar SURRORATE_TEST_RE   = /[\\uD800-\\uDFFF]/;\nvar SURRORATE_SEARCH_RE = /[\\uD800-\\uDFFF]/g;\n\nfunction replaceBadSurrogate(ch, pos, orig) {\n  var code = ch.charCodeAt(0);\n\n  if (code >= 0xD800 && code <= 0xDBFF) {\n    // high surrogate\n    if (pos >= orig.length - 1) { return '\\uFFFD'; }\n    code = orig.charCodeAt(pos + 1);\n    if (code < 0xDC00 || code > 0xDFFF) { return '\\uFFFD'; }\n\n    return ch;\n  }\n\n  // low surrogate\n  if (pos === 0) { return '\\uFFFD'; }\n  code = orig.charCodeAt(pos - 1);\n  if (code < 0xD900 || code > 0xDBFF) { return '\\uFFFD'; }\n  return ch;\n}\n\nfunction fixBrokenSurrogates(str) {\n  if (!SURRORATE_TEST_RE.test(str)) { return str; }\n\n  return str.replace(SURRORATE_SEARCH_RE, replaceBadSurrogate);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\n\n// Incoming link can be partially encoded. Convert possible combinations to\n// unified form.\n//\n// TODO: Rewrite it. Should use:\n//\n// - encodeURIComponent for query\n// - encodeURI for path\n// - (?) punicode for domain mame (but encodeURI seems to work in real world)\n//\nfunction normalizeLink(url) {\n  var normalized = replaceEntities(url);\n\n  // We don't care much about result of mailformed URIs,\n  // but shoud not throw exception.\n  try {\n    normalized = decodeURI(normalized);\n  } catch (__) {}\n\n  // Encoder throws exception on broken surrogate pairs.\n  // Fix those first.\n  return encodeURI(fixBrokenSurrogates(normalized));\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar REGEXP_ESCAPE_RE = /[.?*+^$[\\]\\\\(){}|-]/g;\n\nfunction escapeRE (str) {\n  return str.replace(REGEXP_ESCAPE_RE, '\\\\$&');\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\n// Zs (unicode class) || 09, 0A, 0D, 0C\nfunction isWhiteSpace(code) {\n  if (code >= 0x2000 && code <= 0x200A) { return true; }\n  switch (code) {\n    case 0x09:\n    case 0x0A:\n    case 0x0D:\n    case 0x0C:\n    case 0x20:\n    case 0xA0:\n    case 0x1680:\n    case 0x202F:\n    case 0x205F:\n    case 0x3000:\n      return true;\n  }\n  return false;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\n/*eslint-disable max-len*/\nvar BMP_PUNCT_RE = /[\\x21-\\x23\\x25-\\x2A\\x2C-\\x2F\\x3A\\x3B\\x3F\\x40\\x5B-\\x5D\\x5F\\x7B\\x7D\\xA1\\xA7\\xAB\\xB6\\xB7\\xBB\\xBF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2308-\\u230B\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E42\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]/;\n\n// Currently without astral characters support.\nfunction isPunctChar(char) {\n  return BMP_PUNCT_RE.test(char);\n}\n\n\n// Markdown ASCII punctuation characters.\n//\n// !, \", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, _, `, {, |, }, or ~\n// http://spec.commonmark.org/0.15/#ascii-punctuation-character\n//\n// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range.\n//\nfunction isMdAsciiPunct(ch) {\n  switch (ch) {\n    case 0x21/* ! */:\n    case 0x22/* \" */:\n    case 0x23/* # */:\n    case 0x24/* $ */:\n    case 0x25/* % */:\n    case 0x26/* & */:\n    case 0x27/* ' */:\n    case 0x28/* ( */:\n    case 0x29/* ) */:\n    case 0x2A/* * */:\n    case 0x2B/* + */:\n    case 0x2C/* , */:\n    case 0x2D/* - */:\n    case 0x2E/* . */:\n    case 0x2F/* / */:\n    case 0x3A/* : */:\n    case 0x3B/* ; */:\n    case 0x3C/* < */:\n    case 0x3D/* = */:\n    case 0x3E/* > */:\n    case 0x3F/* ? */:\n    case 0x40/* @ */:\n    case 0x5B/* [ */:\n    case 0x5C/* \\ */:\n    case 0x5D/* ] */:\n    case 0x5E/* ^ */:\n    case 0x5F/* _ */:\n    case 0x60/* ` */:\n    case 0x7B/* { */:\n    case 0x7C/* | */:\n    case 0x7D/* } */:\n    case 0x7E/* ~ */:\n      return true;\n    default:\n      return false;\n  }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n\nexports.assign            = assign;\nexports.isString          = isString;\nexports.has               = has;\nexports.unescapeMd        = unescapeMd;\nexports.isValidEntityCode = isValidEntityCode;\nexports.fromCodePoint     = fromCodePoint;\nexports.replaceEntities   = replaceEntities;\nexports.escapeHtml        = escapeHtml;\nexports.arrayReplaceAt    = arrayReplaceAt;\nexports.normalizeLink     = normalizeLink;\nexports.isWhiteSpace      = isWhiteSpace;\nexports.isMdAsciiPunct    = isMdAsciiPunct;\nexports.isPunctChar       = isPunctChar;\nexports.escapeRE          = escapeRE;\n\n},{\"./entities\":1}],6:[function(require,module,exports){\n// Just a shortcut for bulk export\n'use strict';\n\n\nexports.parseLinkLabel       = require('./parse_link_label');\nexports.parseLinkDestination = require('./parse_link_destination');\nexports.parseLinkTitle       = require('./parse_link_title');\n\n},{\"./parse_link_destination\":8,\"./parse_link_label\":9,\"./parse_link_title\":10}],7:[function(require,module,exports){\n'use strict';\n\n\n// Hepler to [reference labels]. No better place for this code :)\n// It's only for refs/links and should not be exported anywhere.\nmodule.exports = function normalizeReference(str) {\n  // use .toUpperCase() instead of .toLowerCase()\n  // here to avoid a conflict with Object.prototype\n  // members (most notably, `__proto__`)\n  return str.trim().replace(/\\s+/g, ' ').toUpperCase();\n};\n\n},{}],8:[function(require,module,exports){\n// Parse link destination\n//\n'use strict';\n\n\nvar normalizeLink = require('../common/utils').normalizeLink;\nvar unescapeMd    = require('../common/utils').unescapeMd;\n\n\nmodule.exports = function parseLinkDestination(str, pos, max) {\n  var code, level,\n      lines = 0,\n      start = pos,\n      result = {\n        ok: false,\n        pos: 0,\n        lines: 0,\n        str: ''\n      };\n\n  if (str.charCodeAt(pos) === 0x3C /* < */) {\n    pos++;\n    while (pos < max) {\n      code = str.charCodeAt(pos);\n      if (code === 0x0A /* \\n */) { return result; }\n      if (code === 0x3E /* > */) {\n        result.pos = pos + 1;\n        result.str = normalizeLink(unescapeMd(str.slice(start + 1, pos)));\n        result.ok = true;\n        return result;\n      }\n      if (code === 0x5C /* \\ */ && pos + 1 < max) {\n        pos += 2;\n        continue;\n      }\n\n      pos++;\n    }\n\n    // no closing '>'\n    return result;\n  }\n\n  // this should be ... } else { ... branch\n\n  level = 0;\n  while (pos < max) {\n    code = str.charCodeAt(pos);\n\n    if (code === 0x20) { break; }\n\n    // ascii control characters\n    if (code < 0x20 || code === 0x7F) { break; }\n\n    if (code === 0x5C /* \\ */ && pos + 1 < max) {\n      pos += 2;\n      continue;\n    }\n\n    if (code === 0x28 /* ( */) {\n      level++;\n      if (level > 1) { break; }\n    }\n\n    if (code === 0x29 /* ) */) {\n      level--;\n      if (level < 0) { break; }\n    }\n\n    pos++;\n  }\n\n  if (start === pos) { return result; }\n\n  result.str = normalizeLink(unescapeMd(str.slice(start, pos)));\n  result.lines = lines;\n  result.pos = pos;\n  result.ok = true;\n  return result;\n};\n\n},{\"../common/utils\":5}],9:[function(require,module,exports){\n// Parse link label\n//\n// this function assumes that first character (\"[\") already matches;\n// returns the end of the label\n//\n'use strict';\n\nmodule.exports = function parseLinkLabel(state, start, disableNested) {\n  var level, found, marker, prevPos,\n      labelEnd = -1,\n      max = state.posMax,\n      oldPos = state.pos;\n\n  if (state.labelUnmatchedScopes) {\n    state.labelUnmatchedScopes--;\n    return -1;\n  }\n\n  state.pos = start + 1;\n  level = 1;\n\n  while (state.pos < max) {\n    marker = state.src.charCodeAt(state.pos);\n    if (marker === 0x5D /* ] */) {\n      level--;\n      if (level === 0) {\n        found = true;\n        break;\n      }\n    }\n\n    prevPos = state.pos;\n    state.md.inline.skipToken(state);\n    if (marker === 0x5B /* [ */) {\n      if (prevPos === state.pos - 1) {\n        // increase level if we find text `[`, which is not a part of any token\n        level++;\n      } else if (disableNested) {\n        state.pos = oldPos;\n        return -1;\n      }\n    }\n  }\n\n  if (found) {\n    labelEnd = state.pos;\n    state.labelUnmatchedScopes = 0;\n  } else {\n    state.labelUnmatchedScopes = level - 1;\n  }\n\n  // restore old state\n  state.pos = oldPos;\n\n  return labelEnd;\n};\n\n},{}],10:[function(require,module,exports){\n// Parse link title\n//\n'use strict';\n\n\nvar unescapeMd = require('../common/utils').unescapeMd;\n\n\nmodule.exports = function parseLinkTitle(str, pos, max) {\n  var code,\n      marker,\n      lines = 0,\n      start = pos,\n      result = {\n        ok: false,\n        pos: 0,\n        lines: 0,\n        str: ''\n      };\n\n  if (pos >= max) { return result; }\n\n  marker = str.charCodeAt(pos);\n\n  if (marker !== 0x22 /* \" */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; }\n\n  pos++;\n\n  // if opening marker is \"(\", switch it to closing marker \")\"\n  if (marker === 0x28) { marker = 0x29; }\n\n  while (pos < max) {\n    code = str.charCodeAt(pos);\n    if (code === marker) {\n      result.pos = pos + 1;\n      result.lines = lines;\n      result.str = unescapeMd(str.slice(start + 1, pos));\n      result.ok = true;\n      return result;\n    } else if (code === 0x0A) {\n      lines++;\n    } else if (code === 0x5C /* \\ */ && pos + 1 < max) {\n      pos++;\n      if (str.charCodeAt(pos) === 0x0A) {\n        lines++;\n      }\n    }\n\n    pos++;\n  }\n\n  return result;\n};\n\n},{\"../common/utils\":5}],11:[function(require,module,exports){\n// Main perser class\n\n'use strict';\n\n\nvar utils        = require('./common/utils');\nvar helpers      = require('./helpers');\nvar Renderer     = require('./renderer');\nvar ParserCore   = require('./parser_core');\nvar ParserBlock  = require('./parser_block');\nvar ParserInline = require('./parser_inline');\n\nvar config = {\n  'default': require('./presets/default'),\n  zero: require('./presets/zero'),\n  commonmark: require('./presets/commonmark')\n};\n\n\n/**\n * class MarkdownIt\n *\n * Main parser/renderer class.\n *\n * ##### Usage\n *\n * ```javascript\n * // node.js, \"classic\" way:\n * var MarkdownIt = require('markdown-it'),\n *     md = new MarkdownIt();\n * var result = md.render('# markdown-it rulezz!');\n *\n * // node.js, the same, but with sugar:\n * var md = require('markdown-it')();\n * var result = md.render('# markdown-it rulezz!');\n *\n * // browser without AMD, added to \"window\" on script load\n * // Note, there are no dash.\n * var md = window.markdownit();\n * var result = md.render('# markdown-it rulezz!');\n * ```\n *\n * Single line rendering, without paragraph wrap:\n *\n * ```javascript\n * var md = require('markdown-it')();\n * var result = md.renderInline('__markdown-it__ rulezz!');\n * ```\n **/\n\n/**\n * new MarkdownIt([presetName, options])\n * - presetName (String): optional, `commonmark` / `zero`\n * - options (Object)\n *\n * Creates parser instanse with given config. Can be called without `new`.\n *\n * ##### presetName\n *\n * MarkdownIt provides named presets as a convenience to quickly\n * enable/disable active syntax rules and options for common use cases.\n *\n * - [\"commonmark\"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) -\n *   configures parser to strict [CommonMark](http://commonmark.org/) mode.\n * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) -\n *   similar to GFM, used when no preset name given. Enables all available rules,\n *   but still without html, typographer & autolinker.\n * - [\"zero\"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) -\n *   all rules disabled. Useful to quickly setup your config via `.enable()`.\n *   For example, when you need only `bold` and `italic` markup and nothing else.\n *\n * ##### options:\n *\n * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful!\n *   That's not safe! You may need external sanitizer to protect output from XSS.\n *   It's better to extend features via plugins, instead of enabling HTML.\n * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags\n *   (`<br />`). This is needed only for full CommonMark compatibility. In real\n *   world you will need HTML output.\n * - __breaks__ - `false`. Set `true` to convert `\\n` in paragraphs into `<br>`.\n * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks.\n *   Can be useful for external highlighters.\n * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links.\n * - __typographer__  - `false`. Set `true` to enable [some language-neutral\n *   replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) +\n *   quotes beautification (smartquotes).\n * - __quotes__ - `“”‘’`, string. Double + single quotes replacement pairs, when\n *   typographer enabled and smartquotes on. Set doubles to '«»' for Russian,\n *   '„“' for German.\n * - __highlight__ - `null`. Highlighter function for fenced code blocks.\n *   Highlighter `function (str, lang)` should return escaped HTML. It can also\n *   return empty string if the source was not changed and should be escaped externaly.\n *\n * ##### Example\n *\n * ```javascript\n * // commonmark mode\n * var md = require('markdown-it')('commonmark');\n *\n * // default mode\n * var md = require('markdown-it')();\n *\n * // enable everything\n * var md = require('markdown-it')({\n *   html: true,\n *   linkify: true,\n *   typographer: true\n * });\n * ```\n *\n * ##### Syntax highlighting\n *\n * ```js\n * var hljs = require('highlight.js') // https://highlightjs.org/\n *\n * var md = require('markdown-it')({\n *   highlight: function (str, lang) {\n *     if (lang && hljs.getLanguage(lang)) {\n *       try {\n *         return hljs.highlight(lang, str).value;\n *       } catch (__) {}\n *     }\n *\n *     try {\n *       return hljs.highlightAuto(str).value;\n *     } catch (__) {}\n *\n *     return ''; // use external default escaping\n *   }\n * });\n * ```\n **/\nfunction MarkdownIt(presetName, options) {\n  if (!(this instanceof MarkdownIt)) {\n    return new MarkdownIt(presetName, options);\n  }\n\n  if (!options) {\n    if (!utils.isString(presetName)) {\n      options = presetName || {};\n      presetName = 'default';\n    }\n  }\n\n  /**\n   * MarkdownIt#inline -> ParserInline\n   *\n   * Instance of [[ParserInline]]. You may need it to add new rules when\n   * writing plugins. For simple rules control use [[MarkdownIt.disable]] and\n   * [[MarkdownIt.enable]].\n   **/\n  this.inline = new ParserInline();\n\n  /**\n   * MarkdownIt#block -> ParserBlock\n   *\n   * Instance of [[ParserBlock]]. You may need it to add new rules when\n   * writing plugins. For simple rules control use [[MarkdownIt.disable]] and\n   * [[MarkdownIt.enable]].\n   **/\n  this.block = new ParserBlock();\n\n  /**\n   * MarkdownIt#core -> Core\n   *\n   * Instance of [[Core]] chain executor. You may need it to add new rules when\n   * writing plugins. For simple rules control use [[MarkdownIt.disable]] and\n   * [[MarkdownIt.enable]].\n   **/\n  this.core = new ParserCore();\n\n  /**\n   * MarkdownIt#renderer -> Renderer\n   *\n   * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering\n   * rules for new token types, generated by plugins.\n   *\n   * ##### Example\n   *\n   * ```javascript\n   * var md = require('markdown-it')();\n   *\n   * function myToken(tokens, idx, options, env, self) {\n   *   //...\n   *   return result;\n   * };\n   *\n   * md.renderer.rules['my_token'] = myToken\n   * ```\n   *\n   * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js).\n   **/\n  this.renderer = new Renderer();\n\n  // Expose utils & helpers for easy acces from plugins\n\n  /**\n   * MarkdownIt#utils -> utils\n   *\n   * Assorted utility functions, useful to write plugins. See details\n   * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js).\n   **/\n  this.utils = utils;\n\n  /**\n   * MarkdownIt#helpers -> helpers\n   *\n   * Link components parser functions, useful to write plugins. See details\n   * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers).\n   **/\n  this.helpers = helpers;\n\n\n  this.options = {};\n  this.configure(presetName);\n\n  if (options) { this.set(options); }\n}\n\n\n/** chainable\n * MarkdownIt.set(options)\n *\n * Set parser options (in the same format as in constructor). Probably, you\n * will never need it, but you can change options after constructor call.\n *\n * ##### Example\n *\n * ```javascript\n * var md = require('markdown-it')()\n *             .set({ html: true, breaks: true })\n *             .set({ typographer, true });\n * ```\n *\n * __Note:__ To achieve the best possible performance, don't modify a\n * `markdown-it` instance options on the fly. If you need multiple configurations\n * it's best to create multiple instances and initialize each with separate\n * config.\n **/\nMarkdownIt.prototype.set = function (options) {\n  utils.assign(this.options, options);\n  return this;\n};\n\n\n/** chainable, internal\n * MarkdownIt.configure(presets)\n *\n * Batch load of all options and compenent settings. This is internal method,\n * and you probably will not need it. But if you with - see available presets\n * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)\n *\n * We strongly recommend to use presets instead of direct config loads. That\n * will give better compatibility with next versions.\n **/\nMarkdownIt.prototype.configure = function (presets) {\n  var self = this, presetName;\n\n  if (utils.isString(presets)) {\n    presetName = presets;\n    presets = config[presetName];\n    if (!presets) { throw new Error('Wrong `markdown-it` preset \"' + presetName + '\", check name'); }\n  }\n\n  if (!presets) { throw new Error('Wrong `markdown-it` preset, can\\'t be empty'); }\n\n  if (presets.options) { self.set(presets.options); }\n\n  if (presets.components) {\n    Object.keys(presets.components).forEach(function (name) {\n      if (presets.components[name].rules) {\n        self[name].ruler.enableOnly(presets.components[name].rules);\n      }\n    });\n  }\n  return this;\n};\n\n\n/** chainable\n * MarkdownIt.enable(list, ignoreInvalid)\n * - list (String|Array): rule name or list of rule names to enable\n * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.\n *\n * Enable list or rules. It will automatically find appropriate components,\n * containing rules with given names. If rule not found, and `ignoreInvalid`\n * not set - throws exception.\n *\n * ##### Example\n *\n * ```javascript\n * var md = require('markdown-it')()\n *             .enable(['sub', 'sup'])\n *             .disable('smartquotes');\n * ```\n **/\nMarkdownIt.prototype.enable = function (list, ignoreInvalid) {\n  var result = [];\n\n  if (!Array.isArray(list)) { list = [ list ]; }\n\n  [ 'core', 'block', 'inline' ].forEach(function (chain) {\n    result = result.concat(this[chain].ruler.enable(list, true));\n  }, this);\n\n  var missed = list.filter(function (name) { return result.indexOf(name) < 0; });\n\n  if (missed.length && !ignoreInvalid) {\n    throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed);\n  }\n\n  return this;\n};\n\n\n/** chainable\n * MarkdownIt.disable(list, ignoreInvalid)\n * - list (String|Array): rule name or list of rule names to disable.\n * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.\n *\n * The same as [[MarkdownIt.enable]], but turn specified rules off.\n **/\nMarkdownIt.prototype.disable = function (list, ignoreInvalid) {\n  var result = [];\n\n  if (!Array.isArray(list)) { list = [ list ]; }\n\n  [ 'core', 'block', 'inline' ].forEach(function (chain) {\n    result = result.concat(this[chain].ruler.disable(list, true));\n  }, this);\n\n  var missed = list.filter(function (name) { return result.indexOf(name) < 0; });\n\n  if (missed.length && !ignoreInvalid) {\n    throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed);\n  }\n  return this;\n};\n\n\n/** chainable\n * MarkdownIt.use(plugin, params)\n *\n * Load specified plugin with given params into current parser instance.\n * It's just a sugar to call `plugin(md, params)` with curring.\n *\n * ##### Example\n *\n * ```javascript\n * var iterator = require('markdown-it-for-inline');\n * var md = require('markdown-it')()\n *             .use(iterator, 'foo_replace', 'text', function (tokens, idx) {\n *               tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar');\n *             });\n * ```\n **/\nMarkdownIt.prototype.use = function (plugin /*, params, ... */) {\n  var args = [ this ].concat(Array.prototype.slice.call(arguments, 1));\n  plugin.apply(plugin, args);\n  return this;\n};\n\n\n/** internal\n * MarkdownIt.parse(src, env) -> Array\n * - src (String): source string\n * - env (Object): environment sandbox\n *\n * Parse input string and returns list of block tokens (special token type\n * \"inline\" will contain list of inline tokens). You should not call this\n * method directly, until you write custom renderer (for example, to produce\n * AST).\n *\n * `env` is used to pass data between \"distributed\" rules (`{}` by default).\n * For example, references are parsed in different chains, and need sandbox\n * to store intermediate results. Can be used to inject data in specific cases.\n * You will not need it with high probability.\n **/\nMarkdownIt.prototype.parse = function (src, env) {\n  var state = new this.core.State(src, this, env);\n\n  this.core.process(state);\n\n  return state.tokens;\n};\n\n\n/**\n * MarkdownIt.render(src [, env]) -> String\n * - src (String): source string\n * - env (Object): environment sandbox\n *\n * Render markdown string into html. It does all magic for you :).\n *\n * `env` can be used to inject additional metadata (`{}` by default).\n * But you will not need it with high probability. See also comment\n * in [[MarkdownIt.parse]].\n **/\nMarkdownIt.prototype.render = function (src, env) {\n  env = env || {};\n\n  return this.renderer.render(this.parse(src, env), this.options, env);\n};\n\n\n/** internal\n * MarkdownIt.parseInline(src, env) -> Array\n * - src (String): source string\n * - env (Object): environment sandbox\n *\n * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the\n * block tokens list with th single `inline` element, containing parsed inline\n * tokens in `children` property.\n **/\nMarkdownIt.prototype.parseInline = function (src, env) {\n  var state = new this.core.State(src, this, env);\n\n  state.inlineMode = true;\n  this.core.process(state);\n\n  return state.tokens;\n};\n\n\n/**\n * MarkdownIt.renderInline(src [, env]) -> String\n * - src (String): source string\n * - env (Object): environment sandbox\n *\n * Similar to [[MarkdownIt.render]] but for single paragraph content. Result\n * will NOT be wrapped into `<p>` tags.\n **/\nMarkdownIt.prototype.renderInline = function (src, env) {\n  env = env || {};\n\n  return this.renderer.render(this.parseInline(src, env), this.options, env);\n};\n\n\nmodule.exports = MarkdownIt;\n\n},{\"./common/utils\":5,\"./helpers\":6,\"./parser_block\":12,\"./parser_core\":13,\"./parser_inline\":14,\"./presets/commonmark\":15,\"./presets/default\":16,\"./presets/zero\":17,\"./renderer\":18}],12:[function(require,module,exports){\n/** internal\n * class ParserBlock\n *\n * Block-level tokenizer.\n **/\n'use strict';\n\n\nvar Ruler           = require('./ruler');\n\n\nvar _rules = [\n  [ 'code',       require('./rules_block/code') ],\n  [ 'fence',      require('./rules_block/fence'),      [ 'paragraph', 'reference', 'blockquote', 'list' ] ],\n  [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ],\n  [ 'hr',         require('./rules_block/hr'),         [ 'paragraph', 'reference', 'blockquote', 'list' ] ],\n  [ 'list',       require('./rules_block/list'),       [ 'paragraph', 'reference', 'blockquote' ] ],\n  [ 'reference',  require('./rules_block/reference'),  [ 'reference' ] ],\n  [ 'heading',    require('./rules_block/heading'),    [ 'paragraph', 'reference', 'blockquote' ] ],\n  [ 'lheading',   require('./rules_block/lheading') ],\n  [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ],\n  [ 'table',      require('./rules_block/table'),      [ 'paragraph', 'reference' ] ],\n  [ 'paragraph',  require('./rules_block/paragraph') ]\n];\n\n\n/**\n * new ParserBlock()\n **/\nfunction ParserBlock() {\n  /**\n   * ParserBlock#ruler -> Ruler\n   *\n   * [[Ruler]] instance. Keep configuration of block rules.\n   **/\n  this.ruler = new Ruler();\n\n  for (var i = 0; i < _rules.length; i++) {\n    this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() });\n  }\n}\n\n\n// Generate tokens for input range\n//\nParserBlock.prototype.tokenize = function (state, startLine, endLine) {\n  var ok, i,\n      rules = this.ruler.getRules(''),\n      len = rules.length,\n      line = startLine,\n      hasEmptyLines = false,\n      maxNesting = state.md.options.maxNesting;\n\n  while (line < endLine) {\n    state.line = line = state.skipEmptyLines(line);\n    if (line >= endLine) { break; }\n\n    // Termination condition for nested calls.\n    // Nested calls currently used for blockquotes & lists\n    if (state.tShift[line] < state.blkIndent) { break; }\n\n    // If nesting level exceeded - skip tail to the end. That's not ordinary\n    // situation and we should not care about content.\n    if (state.level >= maxNesting) {\n      state.line = endLine;\n      break;\n    }\n\n    // Try all possible rules.\n    // On success, rule should:\n    //\n    // - update `state.line`\n    // - update `state.tokens`\n    // - return true\n\n    for (i = 0; i < len; i++) {\n      ok = rules[i](state, line, endLine, false);\n      if (ok) { break; }\n    }\n\n    // set state.tight iff we had an empty line before current tag\n    // i.e. latest empty line should not count\n    state.tight = !hasEmptyLines;\n\n    // paragraph might \"eat\" one newline after it in nested lists\n    if (state.isEmpty(state.line - 1)) {\n      hasEmptyLines = true;\n    }\n\n    line = state.line;\n\n    if (line < endLine && state.isEmpty(line)) {\n      hasEmptyLines = true;\n      line++;\n\n      // two empty lines should stop the parser in list mode\n      if (line < endLine && state.parentType === 'list' && state.isEmpty(line)) { break; }\n      state.line = line;\n    }\n  }\n};\n\n\n/**\n * ParserBlock.parse(str, md, env, outTokens)\n *\n * Process input string and push block tokens into `outTokens`\n **/\nParserBlock.prototype.parse = function (src, md, env, outTokens) {\n  var state;\n\n  if (!src) { return []; }\n\n  state = new this.State(src, md, env, outTokens);\n\n  this.tokenize(state, state.line, state.lineMax);\n};\n\n\nParserBlock.prototype.State = require('./rules_block/state_block');\n\n\nmodule.exports = ParserBlock;\n\n},{\"./ruler\":19,\"./rules_block/blockquote\":20,\"./rules_block/code\":21,\"./rules_block/fence\":22,\"./rules_block/heading\":23,\"./rules_block/hr\":24,\"./rules_block/html_block\":25,\"./rules_block/lheading\":26,\"./rules_block/list\":27,\"./rules_block/paragraph\":28,\"./rules_block/reference\":29,\"./rules_block/state_block\":30,\"./rules_block/table\":31}],13:[function(require,module,exports){\n/** internal\n * class Core\n *\n * Top-level rules executor. Glues block/inline parsers and does intermediate\n * transformations.\n **/\n'use strict';\n\n\nvar Ruler  = require('./ruler');\n\n\nvar _rules = [\n  [ 'normalize',      require('./rules_core/normalize')      ],\n  [ 'block',          require('./rules_core/block')          ],\n  [ 'inline',         require('./rules_core/inline')         ],\n  [ 'replacements',   require('./rules_core/replacements')   ],\n  [ 'smartquotes',    require('./rules_core/smartquotes')    ],\n  [ 'linkify',        require('./rules_core/linkify')        ]\n];\n\n\n/**\n * new Core()\n **/\nfunction Core() {\n  /**\n   * Core#ruler -> Ruler\n   *\n   * [[Ruler]] instance. Keep configuration of core rules.\n   **/\n  this.ruler = new Ruler();\n\n  for (var i = 0; i < _rules.length; i++) {\n    this.ruler.push(_rules[i][0], _rules[i][1]);\n  }\n}\n\n\n/**\n * Core.process(state)\n *\n * Executes core chain rules.\n **/\nCore.prototype.process = function (state) {\n  var i, l, rules;\n\n  rules = this.ruler.getRules('');\n\n  for (i = 0, l = rules.length; i < l; i++) {\n    rules[i](state);\n  }\n};\n\nCore.prototype.State = require('./rules_core/state_core');\n\n\nmodule.exports = Core;\n\n},{\"./ruler\":19,\"./rules_core/block\":32,\"./rules_core/inline\":33,\"./rules_core/linkify\":34,\"./rules_core/normalize\":35,\"./rules_core/replacements\":36,\"./rules_core/smartquotes\":37,\"./rules_core/state_core\":38}],14:[function(require,module,exports){\n/** internal\n * class ParserInline\n *\n * Tokenizes paragraph content.\n **/\n'use strict';\n\n\nvar Ruler           = require('./ruler');\nvar replaceEntities = require('./common/utils').replaceEntities;\n\n////////////////////////////////////////////////////////////////////////////////\n// Parser rules\n\nvar _rules = [\n  [ 'text',            require('./rules_inline/text') ],\n  [ 'newline',         require('./rules_inline/newline') ],\n  [ 'escape',          require('./rules_inline/escape') ],\n  [ 'backticks',       require('./rules_inline/backticks') ],\n  [ 'strikethrough',   require('./rules_inline/strikethrough') ],\n  [ 'emphasis',        require('./rules_inline/emphasis') ],\n  [ 'link',            require('./rules_inline/link') ],\n  [ 'image',           require('./rules_inline/image') ],\n  [ 'autolink',        require('./rules_inline/autolink') ],\n  [ 'html_inline',     require('./rules_inline/html_inline') ],\n  [ 'entity',          require('./rules_inline/entity') ]\n];\n\n\nvar BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file' ];\n\nfunction validateLink(url) {\n  var str = url.trim().toLowerCase();\n\n  // Care about digital entities \"javascript&#x3A;alert(1)\"\n  str = replaceEntities(str);\n\n  if (str.indexOf(':') >= 0 && BAD_PROTOCOLS.indexOf(str.split(':')[0]) >= 0) {\n    return false;\n  }\n  return true;\n}\n\n\n/**\n * new ParserInline()\n **/\nfunction ParserInline() {\n  /**\n   * ParserInline#validateLink(url) -> Boolean\n   *\n   * Link validation function. CommonMark allows too much in links. By default\n   * we disable `javascript:` and `vbscript:` schemas. You can change this\n   * behaviour.\n   *\n   * ```javascript\n   * var md = require('markdown-it')();\n   * // enable everything\n   * md.inline.validateLink = function () { return true; }\n   * ```\n   **/\n  this.validateLink = validateLink;\n\n  /**\n   * ParserInline#ruler -> Ruler\n   *\n   * [[Ruler]] instance. Keep configuration of inline rules.\n   **/\n  this.ruler = new Ruler();\n\n  for (var i = 0; i < _rules.length; i++) {\n    this.ruler.push(_rules[i][0], _rules[i][1]);\n  }\n}\n\n\n// Skip single token by running all rules in validation mode;\n// returns `true` if any rule reported success\n//\nParserInline.prototype.skipToken = function (state) {\n  var i, cached_pos, pos = state.pos,\n      rules = this.ruler.getRules(''),\n      len = rules.length,\n      maxNesting = state.md.options.maxNesting;\n\n\n  if ((cached_pos = state.cacheGet(pos)) > 0) {\n    state.pos = cached_pos;\n    return;\n  }\n\n  if (state.level < maxNesting) {\n    for (i = 0; i < len; i++) {\n      if (rules[i](state, true)) {\n        state.cacheSet(pos, state.pos);\n        return;\n      }\n    }\n    state.pos++;\n\n  } else {\n    // If nesting level exceeded - skip tail to the end. That's not ordinary\n    // situation and we should not care about content.\n    state.pos = state.max;\n  }\n\n  state.cacheSet(pos, state.pos);\n};\n\n\n// Generate tokens for input range\n//\nParserInline.prototype.tokenize = function (state) {\n  var ok, i,\n      rules = this.ruler.getRules(''),\n      len = rules.length,\n      end = state.posMax,\n      maxNesting = state.md.options.maxNesting;\n\n  while (state.pos < end) {\n\n    // If nesting level exceeded - skip tail to the end. That's not ordinary\n    // situation and we should not care about content.\n    if (state.level >= maxNesting) {\n      state.pos = end;\n      break;\n    }\n    // Try all possible rules.\n    // On success, rule should:\n    //\n    // - update `state.pos`\n    // - update `state.tokens`\n    // - return true\n\n    for (i = 0; i < len; i++) {\n      ok = rules[i](state, false);\n      if (ok) { break; }\n    }\n\n    if (ok) {\n      if (state.pos >= end) { break; }\n      continue;\n    }\n\n    state.pending += state.src[state.pos++];\n  }\n\n  if (state.pending) {\n    state.pushPending();\n  }\n};\n\n\n/**\n * ParserInline.parse(str, md, env, outTokens)\n *\n * Process input string and push inline tokens into `outTokens`\n **/\nParserInline.prototype.parse = function (str, md, env, outTokens) {\n  var state = new this.State(str, md, env, outTokens);\n\n  this.tokenize(state);\n};\n\n\nParserInline.prototype.State = require('./rules_inline/state_inline');\n\n\nmodule.exports = ParserInline;\n\n},{\"./common/utils\":5,\"./ruler\":19,\"./rules_inline/autolink\":39,\"./rules_inline/backticks\":40,\"./rules_inline/emphasis\":41,\"./rules_inline/entity\":42,\"./rules_inline/escape\":43,\"./rules_inline/html_inline\":44,\"./rules_inline/image\":45,\"./rules_inline/link\":46,\"./rules_inline/newline\":47,\"./rules_inline/state_inline\":48,\"./rules_inline/strikethrough\":49,\"./rules_inline/text\":50}],15:[function(require,module,exports){\n// Commonmark default options\n\n'use strict';\n\n\nmodule.exports = {\n  options: {\n    html:         true,         // Enable HTML tags in source\n    xhtmlOut:     true,         // Use '/' to close single tags (<br />)\n    breaks:       false,        // Convert '\\n' in paragraphs into <br>\n    langPrefix:   'language-',  // CSS language prefix for fenced blocks\n    linkify:      false,        // autoconvert URL-like texts to links\n\n    // Enable some language-neutral replacements + quotes beautification\n    typographer:  false,\n\n    // Double + single quotes replacement pairs, when typographer enabled,\n    // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.\n    quotes: '\\u201c\\u201d\\u2018\\u2019' /* “”‘’ */,\n\n    // Highlighter function. Should return escaped HTML,\n    // or '' if input not changed\n    //\n    // function (/*str, lang*/) { return ''; }\n    //\n    highlight: null,\n\n    maxNesting:   20            // Internal protection, recursion limit\n  },\n\n  components: {\n\n    core: {\n      rules: [\n        'normalize',\n        'block',\n        'inline'\n      ]\n    },\n\n    block: {\n      rules: [\n        'blockquote',\n        'code',\n        'fence',\n        'heading',\n        'hr',\n        'html_block',\n        'lheading',\n        'list',\n        'reference',\n        'paragraph'\n      ]\n    },\n\n    inline: {\n      rules: [\n        'autolink',\n        'backticks',\n        'emphasis',\n        'entity',\n        'escape',\n        'html_inline',\n        'image',\n        'link',\n        'newline',\n        'text'\n      ]\n    }\n  }\n};\n\n},{}],16:[function(require,module,exports){\n// markdown-it default options\n\n'use strict';\n\n\nmodule.exports = {\n  options: {\n    html:         false,        // Enable HTML tags in source\n    xhtmlOut:     false,        // Use '/' to close single tags (<br />)\n    breaks:       false,        // Convert '\\n' in paragraphs into <br>\n    langPrefix:   'language-',  // CSS language prefix for fenced blocks\n    linkify:      false,        // autoconvert URL-like texts to links\n\n    // Enable some language-neutral replacements + quotes beautification\n    typographer:  false,\n\n    // Double + single quotes replacement pairs, when typographer enabled,\n    // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.\n    quotes: '\\u201c\\u201d\\u2018\\u2019' /* “”‘’ */,\n\n    // Highlighter function. Should return escaped HTML,\n    // or '' if input not changed\n    //\n    // function (/*str, lang*/) { return ''; }\n    //\n    highlight: null,\n\n    maxNesting:   20            // Internal protection, recursion limit\n  },\n\n  components: {\n\n    core: {},\n    block: {},\n    inline: {}\n  }\n};\n\n},{}],17:[function(require,module,exports){\n// \"Zero\" preset, with nothing enabled. Useful for manual configuring of simple\n// modes. For example, to parse bold/italic only.\n\n'use strict';\n\n\nmodule.exports = {\n  options: {\n    html:         false,        // Enable HTML tags in source\n    xhtmlOut:     false,        // Use '/' to close single tags (<br />)\n    breaks:       false,        // Convert '\\n' in paragraphs into <br>\n    langPrefix:   'language-',  // CSS language prefix for fenced blocks\n    linkify:      false,        // autoconvert URL-like texts to links\n\n    // Enable some language-neutral replacements + quotes beautification\n    typographer:  false,\n\n    // Double + single quotes replacement pairs, when typographer enabled,\n    // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.\n    quotes: '\\u201c\\u201d\\u2018\\u2019' /* “”‘’ */,\n\n    // Highlighter function. Should return escaped HTML,\n    // or '' if input not changed\n    //\n    // function (/*str, lang*/) { return ''; }\n    //\n    highlight: null,\n\n    maxNesting:   20            // Internal protection, recursion limit\n  },\n\n  components: {\n\n    core: {\n      rules: [\n        'normalize',\n        'block',\n        'inline'\n      ]\n    },\n\n    block: {\n      rules: [\n        'paragraph'\n      ]\n    },\n\n    inline: {\n      rules: [\n        'text'\n      ]\n    }\n  }\n};\n\n},{}],18:[function(require,module,exports){\n/**\n * class Renderer\n *\n * Generates HTML from parsed token stream. Each instance has independent\n * copy of rules. Those can be rewritten with ease. Also, you can add new\n * rules if you create plugin and adds new token types.\n **/\n'use strict';\n\n\nvar assign          = require('./common/utils').assign;\nvar unescapeMd      = require('./common/utils').unescapeMd;\nvar replaceEntities = require('./common/utils').replaceEntities;\nvar escapeHtml      = require('./common/utils').escapeHtml;\n\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar rules = {};\n\n\nrules.blockquote_open  = function () { return '<blockquote>\\n'; };\nrules.blockquote_close = function () { return '</blockquote>\\n'; };\n\n\nrules.code_block = function (tokens, idx /*, options, env */) {\n  return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>\\n';\n};\nrules.code_inline = function (tokens, idx /*, options, env */) {\n  return '<code>' + escapeHtml(tokens[idx].content) + '</code>';\n};\n\n\nrules.fence = function (tokens, idx, options /*, env, self*/) {\n  var token = tokens[idx];\n  var langClass = '';\n  var langPrefix = options.langPrefix;\n  var langName = '';\n  var highlighted;\n\n  if (token.params) {\n    langName = escapeHtml(replaceEntities(unescapeMd(token.params.split(/\\s+/g)[0])));\n    langClass = ' class=\"' + langPrefix + langName + '\"';\n  }\n\n  if (options.highlight) {\n    highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);\n  } else {\n    highlighted = escapeHtml(token.content);\n  }\n\n\n  return  '<pre><code' + langClass + '>'\n        + highlighted\n        + '</code></pre>\\n';\n};\n\n\nrules.heading_open = function (tokens, idx /*, options, env */) {\n  return '<h' + tokens[idx].hLevel + '>';\n};\nrules.heading_close = function (tokens, idx /*, options, env */) {\n  return '</h' + tokens[idx].hLevel + '>\\n';\n};\n\n\nrules.hr = function (tokens, idx, options /*, env */) {\n  return (options.xhtmlOut ? '<hr />\\n' : '<hr>\\n');\n};\n\n\nrules.bullet_list_open   = function () { return '<ul>\\n'; };\nrules.bullet_list_close  = function () { return '</ul>\\n'; };\nrules.list_item_open     = function (tokens, idx /*, options, env */) {\n  var next = tokens[idx + 1];\n  if ((next.type === 'list_item_close') ||\n      (next.type === 'paragraph_open' && next.tight)) {\n    return '<li>';\n  }\n  return '<li>\\n';\n};\nrules.list_item_close    = function () { return '</li>\\n'; };\nrules.ordered_list_open  = function (tokens, idx /*, options, env */) {\n  if (tokens[idx].order > 1) {\n    return '<ol start=\"' + tokens[idx].order + '\">\\n';\n  }\n  return '<ol>\\n';\n};\nrules.ordered_list_close = function () { return '</ol>\\n'; };\n\n\nrules.paragraph_open = function (tokens, idx /*, options, env */) {\n  return tokens[idx].tight ? '' : '<p>';\n};\nrules.paragraph_close = function (tokens, idx /*, options, env */) {\n  if (tokens[idx].tight === true) {\n    return tokens[idx + 1].type.slice(-5) === 'close' ? '' : '\\n';\n  }\n  return '</p>\\n';\n};\n\n\nrules.link_open = function (tokens, idx /*, options, env */) {\n  var title = tokens[idx].title ? (' title=\"' + escapeHtml(replaceEntities(tokens[idx].title)) + '\"') : '';\n  var target = tokens[idx].target ? (' target=\"' + escapeHtml(tokens[idx].target) + '\"') : '';\n  return '<a href=\"' + escapeHtml(tokens[idx].href) + '\"' + title + target + '>';\n};\nrules.link_close = function (/* tokens, idx, options, env */) {\n  return '</a>';\n};\n\n\nrules.image = function (tokens, idx, options, env, self) {\n  var src = ' src=\"' + escapeHtml(tokens[idx].src) + '\"';\n  var title = tokens[idx].title ? (' title=\"' + escapeHtml(replaceEntities(tokens[idx].title)) + '\"') : '';\n  var alt = ' alt=\"' + self.renderInlineAsText(tokens[idx].tokens, options, env) + '\"';\n  var suffix = options.xhtmlOut ? ' /' : '';\n  return '<img' + src + alt + title + suffix + '>';\n};\n\n\nrules.table_open  = function () { return '<table>\\n'; };\nrules.table_close = function () { return '</table>\\n'; };\nrules.thead_open  = function () { return '<thead>\\n'; };\nrules.thead_close = function () { return '</thead>\\n'; };\nrules.tbody_open  = function () { return '<tbody>\\n'; };\nrules.tbody_close = function () { return '</tbody>\\n'; };\nrules.tr_open     = function () { return '<tr>'; };\nrules.tr_close    = function () { return '</tr>\\n'; };\nrules.th_open     = function (tokens, idx /*, options, env */) {\n  if (tokens[idx].align) {\n    return '<th style=\"text-align:' + tokens[idx].align + '\">';\n  }\n  return '<th>';\n};\nrules.th_close    = function () { return '</th>'; };\nrules.td_open     = function (tokens, idx /*, options, env */) {\n  if (tokens[idx].align) {\n    return '<td style=\"text-align:' + tokens[idx].align + '\">';\n  }\n  return '<td>';\n};\nrules.td_close    = function () { return '</td>'; };\n\n\nrules.strong_open  = function () { return '<strong>'; };\nrules.strong_close = function () { return '</strong>'; };\n\n\nrules.em_open  = function () { return '<em>'; };\nrules.em_close = function () { return '</em>'; };\n\n\nrules.s_open  = function () { return '<s>'; };\nrules.s_close = function () { return '</s>'; };\n\n\nrules.hardbreak = function (tokens, idx, options /*, env */) {\n  return options.xhtmlOut ? '<br />\\n' : '<br>\\n';\n};\nrules.softbreak = function (tokens, idx, options /*, env */) {\n  return options.breaks ? (options.xhtmlOut ? '<br />\\n' : '<br>\\n') : '\\n';\n};\n\n\nrules.text = function (tokens, idx /*, options, env */) {\n  return escapeHtml(tokens[idx].content);\n};\n\n\nrules.html_block = function (tokens, idx /*, options, env */) {\n  return tokens[idx].content;\n};\nrules.html_inline = function (tokens, idx /*, options, env */) {\n  return tokens[idx].content;\n};\n\n\n/**\n * new Renderer()\n *\n * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.\n **/\nfunction Renderer() {\n\n  /**\n   * Renderer#rules -> Object\n   *\n   * Contains render rules for tokens. Can be updated and extended.\n   *\n   * ##### Example\n   *\n   * ```javascript\n   * var md = require('markdown-it')();\n   *\n   * md.renderer.rules.strong_open  = function () { return '<b>'; };\n   * md.renderer.rules.strong_close = function () { return '</b>'; };\n   *\n   * var result = md.renderInline(...);\n   * ```\n   *\n   * Each rule is called as independed static function with fixed signature:\n   *\n   * ```javascript\n   * function my_token_render(tokens, idx, options, env, renderer) {\n   *   // ...\n   *   return renderedHTML;\n   * }\n   * ```\n   *\n   * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)\n   * for more details and examples.\n   **/\n  this.rules = assign({}, rules);\n}\n\n\n/**\n * Renderer.renderInline(tokens, options, env) -> String\n * - tokens (Array): list on block tokens to renter\n * - options (Object): params of parser instance\n * - env (Object): additional data from parsed input (references, for example)\n *\n * The same as [[Renderer.render]], but for single token of `inline` type.\n **/\nRenderer.prototype.renderInline = function (tokens, options, env) {\n  var result = '',\n      _rules = this.rules;\n\n  for (var i = 0, len = tokens.length; i < len; i++) {\n    result += _rules[tokens[i].type](tokens, i, options, env, this);\n  }\n\n  return result;\n};\n\n\n/** internal\n * Renderer.renderInlineAsText(tokens, options, env) -> String\n * - tokens (Array): list on block tokens to renter\n * - options (Object): params of parser instance\n * - env (Object): additional data from parsed input (references, for example)\n *\n * Special kludge for image `alt` attributes to conform CommonMark spec.\n * Don't try to use it! Spec requires to show `alt` content with stripped markup,\n * instead of simple escaping.\n **/\nRenderer.prototype.renderInlineAsText = function (tokens, options, env) {\n  var result = '',\n      _rules = this.rules;\n\n  for (var i = 0, len = tokens.length; i < len; i++) {\n    if (tokens[i].type === 'text') {\n      result += _rules.text(tokens, i, options, env, this);\n    } else if (tokens[i].type === 'image') {\n      result += this.renderInlineAsText(tokens[i].tokens, options, env);\n    }\n  }\n\n  return result;\n};\n\n\n/**\n * Renderer.render(tokens, options, env) -> String\n * - tokens (Array): list on block tokens to renter\n * - options (Object): params of parser instance\n * - env (Object): additional data from parsed input (references, for example)\n *\n * Takes token stream and generates HTML. Probably, you will never need to call\n * this method directly.\n **/\nRenderer.prototype.render = function (tokens, options, env) {\n  var i, len,\n      result = '',\n      _rules = this.rules;\n\n  for (i = 0, len = tokens.length; i < len; i++) {\n    if (tokens[i].type === 'inline') {\n      result += this.renderInline(tokens[i].children, options, env);\n    } else {\n      result += _rules[tokens[i].type](tokens, i, options, env, this);\n    }\n  }\n\n  return result;\n};\n\nmodule.exports = Renderer;\n\n},{\"./common/utils\":5}],19:[function(require,module,exports){\n/**\n * class Ruler\n *\n * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and\n * [[MarkdownIt#inline]] to manage sequences of functions (rules):\n *\n * - keep rules in defined order\n * - assign the name to each rule\n * - enable/disable rules\n * - add/replace rules\n * - allow assign rules to additional named chains (in the same)\n * - cacheing lists of active rules\n *\n * You will not need use this class directly until write plugins. For simple\n * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and\n * [[MarkdownIt.use]].\n **/\n'use strict';\n\n\n/**\n * new Ruler()\n **/\nfunction Ruler() {\n  // List of added rules. Each element is:\n  //\n  // {\n  //   name: XXX,\n  //   enabled: Boolean,\n  //   fn: Function(),\n  //   alt: [ name2, name3 ]\n  // }\n  //\n  this.__rules__ = [];\n\n  // Cached rule chains.\n  //\n  // First level - chain name, '' for default.\n  // Second level - diginal anchor for fast filtering by charcodes.\n  //\n  this.__cache__ = null;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Helper methods, should not be used directly\n\n\n// Find rule index by name\n//\nRuler.prototype.__find__ = function (name) {\n  for (var i = 0; i < this.__rules__.length; i++) {\n    if (this.__rules__[i].name === name) {\n      return i;\n    }\n  }\n  return -1;\n};\n\n\n// Build rules lookup cache\n//\nRuler.prototype.__compile__ = function () {\n  var self = this;\n  var chains = [ '' ];\n\n  // collect unique names\n  self.__rules__.forEach(function (rule) {\n    if (!rule.enabled) { return; }\n\n    rule.alt.forEach(function (altName) {\n      if (chains.indexOf(altName) < 0) {\n        chains.push(altName);\n      }\n    });\n  });\n\n  self.__cache__ = {};\n\n  chains.forEach(function (chain) {\n    self.__cache__[chain] = [];\n    self.__rules__.forEach(function (rule) {\n      if (!rule.enabled) { return; }\n\n      if (chain && rule.alt.indexOf(chain) < 0) { return; }\n\n      self.__cache__[chain].push(rule.fn);\n    });\n  });\n};\n\n\n/**\n * Ruler.at(name, fn [, options])\n * - name (String): rule name to replace.\n * - fn (Function): new rule function.\n * - options (Object): new rule options (not mandatory).\n *\n * Replace rule by name with new function & options. Throws error if name not\n * found.\n *\n * ##### Options:\n *\n * - __alt__ - array with names of \"alternate\" chains.\n *\n * ##### Example\n *\n * Replace existing typorgapher replacement rule with new one:\n *\n * ```javascript\n * var md = require('markdown-it')();\n *\n * md.core.ruler.at('replacements', function replace(state) {\n *   //...\n * });\n * ```\n **/\nRuler.prototype.at = function (name, fn, options) {\n  var index = this.__find__(name);\n  var opt = options || {};\n\n  if (index === -1) { throw new Error('Parser rule not found: ' + name); }\n\n  this.__rules__[index].fn = fn;\n  this.__rules__[index].alt = opt.alt || [];\n  this.__cache__ = null;\n};\n\n\n/**\n * Ruler.before(beforeName, ruleName, fn [, options])\n * - beforeName (String): new rule will be added before this one.\n * - ruleName (String): name of added rule.\n * - fn (Function): rule function.\n * - options (Object): rule options (not mandatory).\n *\n * Add new rule to chain before one with given name. See also\n * [[Ruler.after]], [[Ruler.push]].\n *\n * ##### Options:\n *\n * - __alt__ - array with names of \"alternate\" chains.\n *\n * ##### Example\n *\n * ```javascript\n * var md = require('markdown-it')();\n *\n * md.block.ruler.before('paragraph', 'my_rule', function replace(state) {\n *   //...\n * });\n * ```\n **/\nRuler.prototype.before = function (beforeName, ruleName, fn, options) {\n  var index = this.__find__(beforeName);\n  var opt = options || {};\n\n  if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); }\n\n  this.__rules__.splice(index, 0, {\n    name: ruleName,\n    enabled: true,\n    fn: fn,\n    alt: opt.alt || []\n  });\n\n  this.__cache__ = null;\n};\n\n\n/**\n * Ruler.after(afterName, ruleName, fn [, options])\n * - afterName (String): new rule will be added after this one.\n * - ruleName (String): name of added rule.\n * - fn (Function): rule function.\n * - options (Object): rule options (not mandatory).\n *\n * Add new rule to chain after one with given name. See also\n * [[Ruler.before]], [[Ruler.push]].\n *\n * ##### Options:\n *\n * - __alt__ - array with names of \"alternate\" chains.\n *\n * ##### Example\n *\n * ```javascript\n * var md = require('markdown-it')();\n *\n * md.inline.ruler.after('text', 'my_rule', function replace(state) {\n *   //...\n * });\n * ```\n **/\nRuler.prototype.after = function (afterName, ruleName, fn, options) {\n  var index = this.__find__(afterName);\n  var opt = options || {};\n\n  if (index === -1) { throw new Error('Parser rule not found: ' + afterName); }\n\n  this.__rules__.splice(index + 1, 0, {\n    name: ruleName,\n    enabled: true,\n    fn: fn,\n    alt: opt.alt || []\n  });\n\n  this.__cache__ = null;\n};\n\n/**\n * Ruler.push(ruleName, fn [, options])\n * - ruleName (String): name of added rule.\n * - fn (Function): rule function.\n * - options (Object): rule options (not mandatory).\n *\n * Push new rule to the end of chain. See also\n * [[Ruler.before]], [[Ruler.after]].\n *\n * ##### Options:\n *\n * - __alt__ - array with names of \"alternate\" chains.\n *\n * ##### Example\n *\n * ```javascript\n * var md = require('markdown-it')();\n *\n * md.core.ruler.push('emphasis', 'my_rule', function replace(state) {\n *   //...\n * });\n * ```\n **/\nRuler.prototype.push = function (ruleName, fn, options) {\n  var opt = options || {};\n\n  this.__rules__.push({\n    name: ruleName,\n    enabled: true,\n    fn: fn,\n    alt: opt.alt || []\n  });\n\n  this.__cache__ = null;\n};\n\n\n/**\n * Ruler.enable(list [, ignoreInvalid]) -> Array\n * - list (String|Array): list of rule names to enable.\n * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.\n *\n * Enable rules with given names. If any rule name not found - throw Error.\n * Errors can be disabled by second param.\n *\n * Returns list of found rule names (if no exception happened).\n *\n * See also [[Ruler.disable]], [[Ruler.enableOnly]].\n **/\nRuler.prototype.enable = function (list, ignoreInvalid) {\n  if (!Array.isArray(list)) { list = [ list ]; }\n\n  var result = [];\n\n  // Search by name and enable\n  list.forEach(function (name) {\n    var idx = this.__find__(name);\n\n    if (idx < 0) {\n      if (ignoreInvalid) { return; }\n      throw new Error('Rules manager: invalid rule name ' + name);\n    }\n    this.__rules__[idx].enabled = true;\n    result.push(name);\n  }, this);\n\n  this.__cache__ = null;\n  return result;\n};\n\n\n/**\n * Ruler.enableOnly(list [, ignoreInvalid])\n * - list (String|Array): list of rule names to enable (whitelist).\n * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.\n *\n * Enable rules with given names, and disable everything else. If any rule name\n * not found - throw Error. Errors can be disabled by second param.\n *\n * See also [[Ruler.disable]], [[Ruler.enable]].\n **/\nRuler.prototype.enableOnly = function (list, ignoreInvalid) {\n  if (!Array.isArray(list)) { list = [ list ]; }\n\n  this.__rules__.forEach(function (rule) { rule.enabled = false; });\n\n  this.enable(list, ignoreInvalid);\n};\n\n\n/**\n * Ruler.disable(list [, ignoreInvalid]) -> Array\n * - list (String|Array): list of rule names to disable.\n * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.\n *\n * Disable rules with given names. If any rule name not found - throw Error.\n * Errors can be disabled by second param.\n *\n * Returns list of found rule names (if no exception happened).\n *\n * See also [[Ruler.enable]], [[Ruler.enableOnly]].\n **/\nRuler.prototype.disable = function (list, ignoreInvalid) {\n  if (!Array.isArray(list)) { list = [ list ]; }\n\n  var result = [];\n\n  // Search by name and disable\n  list.forEach(function (name) {\n    var idx = this.__find__(name);\n\n    if (idx < 0) {\n      if (ignoreInvalid) { return; }\n      throw new Error('Rules manager: invalid rule name ' + name);\n    }\n    this.__rules__[idx].enabled = false;\n    result.push(name);\n  }, this);\n\n  this.__cache__ = null;\n  return result;\n};\n\n\n/**\n * Ruler.getRules(chainName) -> Array\n *\n * Return array of active functions (rules) for given chain name. It analyzes\n * rules configuration, compiles caches if not exists and returns result.\n *\n * Default chain name is `''` (empty string). It can't be skipped. That's\n * done intentionally, to keep signature monomorphic for high speed.\n **/\nRuler.prototype.getRules = function (chainName) {\n  if (this.__cache__ === null) {\n    this.__compile__();\n  }\n\n  // Chain can be empty, if rules disabled. But we still have to return Array.\n  return this.__cache__[chainName] || [];\n};\n\nmodule.exports = Ruler;\n\n},{}],20:[function(require,module,exports){\n// Block quotes\n\n'use strict';\n\n\nmodule.exports = function blockquote(state, startLine, endLine, silent) {\n  var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines,\n      terminatorRules,\n      i, l, terminate,\n      pos = state.bMarks[startLine] + state.tShift[startLine],\n      max = state.eMarks[startLine];\n\n  if (pos > max) { return false; }\n\n  // check the block quote marker\n  if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; }\n\n  // we know that it's going to be a valid blockquote,\n  // so no point trying to find the end of it in silent mode\n  if (silent) { return true; }\n\n  // skip one optional space after '>'\n  if (state.src.charCodeAt(pos) === 0x20) { pos++; }\n\n  oldIndent = state.blkIndent;\n  state.blkIndent = 0;\n\n  oldBMarks = [ state.bMarks[startLine] ];\n  state.bMarks[startLine] = pos;\n\n  // check if we have an empty blockquote\n  pos = pos < max ? state.skipSpaces(pos) : pos;\n  lastLineEmpty = pos >= max;\n\n  oldTShift = [ state.tShift[startLine] ];\n  state.tShift[startLine] = pos - state.bMarks[startLine];\n\n  terminatorRules = state.md.block.ruler.getRules('blockquote');\n\n  // Search the end of the block\n  //\n  // Block ends with either:\n  //  1. an empty line outside:\n  //     ```\n  //     > test\n  //\n  //     ```\n  //  2. an empty line inside:\n  //     ```\n  //     >\n  //     test\n  //     ```\n  //  3. another tag\n  //     ```\n  //     > test\n  //      - - -\n  //     ```\n  for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {\n    pos = state.bMarks[nextLine] + state.tShift[nextLine];\n    max = state.eMarks[nextLine];\n\n    if (pos >= max) {\n      // Case 1: line is not inside the blockquote, and this line is empty.\n      break;\n    }\n\n    if (state.src.charCodeAt(pos++) === 0x3E/* > */) {\n      // This line is inside the blockquote.\n\n      // skip one optional space after '>'\n      if (state.src.charCodeAt(pos) === 0x20) { pos++; }\n\n      oldBMarks.push(state.bMarks[nextLine]);\n      state.bMarks[nextLine] = pos;\n\n      pos = pos < max ? state.skipSpaces(pos) : pos;\n      lastLineEmpty = pos >= max;\n\n      oldTShift.push(state.tShift[nextLine]);\n      state.tShift[nextLine] = pos - state.bMarks[nextLine];\n      continue;\n    }\n\n    // Case 2: line is not inside the blockquote, and the last line was empty.\n    if (lastLineEmpty) { break; }\n\n    // Case 3: another tag found.\n    terminate = false;\n    for (i = 0, l = terminatorRules.length; i < l; i++) {\n      if (terminatorRules[i](state, nextLine, endLine, true)) {\n        terminate = true;\n        break;\n      }\n    }\n    if (terminate) { break; }\n\n    oldBMarks.push(state.bMarks[nextLine]);\n    oldTShift.push(state.tShift[nextLine]);\n\n    // A negative number means that this is a paragraph continuation;\n    //\n    // Any negative number will do the job here, but it's better for it\n    // to be large enough to make any bugs obvious.\n    state.tShift[nextLine] = -1337;\n  }\n\n  oldParentType = state.parentType;\n  state.parentType = 'blockquote';\n  state.tokens.push({\n    type: 'blockquote_open',\n    lines: lines = [ startLine, 0 ],\n    level: state.level++\n  });\n  state.md.block.tokenize(state, startLine, nextLine);\n  state.tokens.push({\n    type: 'blockquote_close',\n    level: --state.level\n  });\n  state.parentType = oldParentType;\n  lines[1] = state.line;\n\n  // Restore original tShift; this might not be necessary since the parser\n  // has already been here, but just to make sure we can do that.\n  for (i = 0; i < oldTShift.length; i++) {\n    state.bMarks[i + startLine] = oldBMarks[i];\n    state.tShift[i + startLine] = oldTShift[i];\n  }\n  state.blkIndent = oldIndent;\n\n  return true;\n};\n\n},{}],21:[function(require,module,exports){\n// Code block (4 spaces padded)\n\n'use strict';\n\n\nmodule.exports = function code(state, startLine, endLine/*, silent*/) {\n  var nextLine, last;\n\n  if (state.tShift[startLine] - state.blkIndent < 4) { return false; }\n\n  last = nextLine = startLine + 1;\n\n  while (nextLine < endLine) {\n    if (state.isEmpty(nextLine)) {\n      nextLine++;\n      continue;\n    }\n    if (state.tShift[nextLine] - state.blkIndent >= 4) {\n      nextLine++;\n      last = nextLine;\n      continue;\n    }\n    break;\n  }\n\n  state.line = nextLine;\n  state.tokens.push({\n    type: 'code_block',\n    content: state.getLines(startLine, last, 4 + state.blkIndent, true),\n    lines: [ startLine, state.line ],\n    level: state.level\n  });\n\n  return true;\n};\n\n},{}],22:[function(require,module,exports){\n// fences (``` lang, ~~~ lang)\n\n'use strict';\n\n\nmodule.exports = function fence(state, startLine, endLine, silent) {\n  var marker, len, params, nextLine, mem,\n      haveEndMarker = false,\n      pos = state.bMarks[startLine] + state.tShift[startLine],\n      max = state.eMarks[startLine];\n\n  if (pos + 3 > max) { return false; }\n\n  marker = state.src.charCodeAt(pos);\n\n  if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {\n    return false;\n  }\n\n  // scan marker length\n  mem = pos;\n  pos = state.skipChars(pos, marker);\n\n  len = pos - mem;\n\n  if (len < 3) { return false; }\n\n  params = state.src.slice(pos, max).trim();\n\n  if (params.indexOf('`') >= 0) { return false; }\n\n  // Since start is found, we can report success here in validation mode\n  if (silent) { return true; }\n\n  // search end of block\n  nextLine = startLine;\n\n  for (;;) {\n    nextLine++;\n    if (nextLine >= endLine) {\n      // unclosed block should be autoclosed by end of document.\n      // also block seems to be autoclosed by end of parent\n      break;\n    }\n\n    pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];\n    max = state.eMarks[nextLine];\n\n    if (pos < max && state.tShift[nextLine] < state.blkIndent) {\n      // non-empty line with negative indent should stop the list:\n      // - ```\n      //  test\n      break;\n    }\n\n    if (state.src.charCodeAt(pos) !== marker) { continue; }\n\n    if (state.tShift[nextLine] - state.blkIndent >= 4) {\n      // closing fence should be indented less than 4 spaces\n      continue;\n    }\n\n    pos = state.skipChars(pos, marker);\n\n    // closing code fence must be at least as long as the opening one\n    if (pos - mem < len) { continue; }\n\n    // make sure tail has spaces only\n    pos = state.skipSpaces(pos);\n\n    if (pos < max) { continue; }\n\n    haveEndMarker = true;\n    // found!\n    break;\n  }\n\n  // If a fence has heading spaces, they should be removed from its inner block\n  len = state.tShift[startLine];\n\n  state.line = nextLine + (haveEndMarker ? 1 : 0);\n  state.tokens.push({\n    type: 'fence',\n    params: params,\n    content: state.getLines(startLine + 1, nextLine, len, true),\n    lines: [ startLine, state.line ],\n    level: state.level\n  });\n\n  return true;\n};\n\n},{}],23:[function(require,module,exports){\n// heading (#, ##, ...)\n\n'use strict';\n\n\nmodule.exports = function heading(state, startLine, endLine, silent) {\n  var ch, level, tmp,\n      pos = state.bMarks[startLine] + state.tShift[startLine],\n      max = state.eMarks[startLine];\n\n  if (pos >= max) { return false; }\n\n  ch  = state.src.charCodeAt(pos);\n\n  if (ch !== 0x23/* # */ || pos >= max) { return false; }\n\n  // count heading level\n  level = 1;\n  ch = state.src.charCodeAt(++pos);\n  while (ch === 0x23/* # */ && pos < max && level <= 6) {\n    level++;\n    ch = state.src.charCodeAt(++pos);\n  }\n\n  if (level > 6 || (pos < max && ch !== 0x20/* space */)) { return false; }\n\n  if (silent) { return true; }\n\n  // Let's cut tails like '    ###  ' from the end of string\n\n  max = state.skipCharsBack(max, 0x20, pos); // space\n  tmp = state.skipCharsBack(max, 0x23, pos); // #\n  if (tmp > pos && state.src.charCodeAt(tmp - 1) === 0x20/* space */) {\n    max = tmp;\n  }\n\n  state.line = startLine + 1;\n\n  state.tokens.push({ type: 'heading_open',\n    hLevel: level,\n    lines: [ startLine, state.line ],\n    level: state.level\n  });\n\n  // only if header is not empty\n  if (pos < max) {\n    state.tokens.push({\n      type: 'inline',\n      content: state.src.slice(pos, max).trim(),\n      level: state.level + 1,\n      lines: [ startLine, state.line ],\n      children: []\n    });\n  }\n  state.tokens.push({ type: 'heading_close', hLevel: level, level: state.level });\n\n  return true;\n};\n\n},{}],24:[function(require,module,exports){\n// Horizontal rule\n\n'use strict';\n\n\nmodule.exports = function hr(state, startLine, endLine, silent) {\n  var marker, cnt, ch,\n      pos = state.bMarks[startLine],\n      max = state.eMarks[startLine];\n\n  pos += state.tShift[startLine];\n\n  if (pos > max) { return false; }\n\n  marker = state.src.charCodeAt(pos++);\n\n  // Check hr marker\n  if (marker !== 0x2A/* * */ &&\n      marker !== 0x2D/* - */ &&\n      marker !== 0x5F/* _ */) {\n    return false;\n  }\n\n  // markers can be mixed with spaces, but there should be at least 3 one\n\n  cnt = 1;\n  while (pos < max) {\n    ch = state.src.charCodeAt(pos++);\n    if (ch !== marker && ch !== 0x20/* space */) { return false; }\n    if (ch === marker) { cnt++; }\n  }\n\n  if (cnt < 3) { return false; }\n\n  if (silent) { return true; }\n\n  state.line = startLine + 1;\n  state.tokens.push({\n    type: 'hr',\n    lines: [ startLine, state.line ],\n    level: state.level\n  });\n\n  return true;\n};\n\n},{}],25:[function(require,module,exports){\n// HTML block\n\n'use strict';\n\n\nvar block_names = require('../common/html_blocks');\n\n\nvar HTML_TAG_OPEN_RE = /^<([a-zA-Z]{1,15})[\\s\\/>]/;\nvar HTML_TAG_CLOSE_RE = /^<\\/([a-zA-Z]{1,15})[\\s>]/;\n\nfunction isLetter(ch) {\n  /*eslint no-bitwise:0*/\n  var lc = ch | 0x20; // to lower case\n  return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */);\n}\n\nmodule.exports = function html_block(state, startLine, endLine, silent) {\n  var ch, match, nextLine,\n      pos = state.bMarks[startLine],\n      max = state.eMarks[startLine],\n      shift = state.tShift[startLine];\n\n  pos += shift;\n\n  if (!state.md.options.html) { return false; }\n\n  if (shift > 3 || pos + 2 >= max) { return false; }\n\n  if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }\n\n  ch = state.src.charCodeAt(pos + 1);\n\n  if (ch === 0x21/* ! */ || ch === 0x3F/* ? */) {\n    // Directive start / comment start / processing instruction start\n    if (silent) { return true; }\n\n  } else if (ch === 0x2F/* / */ || isLetter(ch)) {\n\n    // Probably start or end of tag\n    if (ch === 0x2F/* \\ */) {\n      // closing tag\n      match = state.src.slice(pos, max).match(HTML_TAG_CLOSE_RE);\n      if (!match) { return false; }\n    } else {\n      // opening tag\n      match = state.src.slice(pos, max).match(HTML_TAG_OPEN_RE);\n      if (!match) { return false; }\n    }\n    // Make sure tag name is valid\n    if (block_names[match[1].toLowerCase()] !== true) { return false; }\n    if (silent) { return true; }\n\n  } else {\n    return false;\n  }\n\n  // If we are here - we detected HTML block.\n  // Let's roll down till empty line (block end).\n  nextLine = startLine + 1;\n  while (nextLine < state.lineMax && !state.isEmpty(nextLine)) {\n    nextLine++;\n  }\n\n  state.line = nextLine;\n  state.tokens.push({\n    type: 'html_block',\n    level: state.level,\n    lines: [ startLine, state.line ],\n    content: state.getLines(startLine, nextLine, 0, true)\n  });\n\n  return true;\n};\n\n},{\"../common/html_blocks\":2}],26:[function(require,module,exports){\n// lheading (---, ===)\n\n'use strict';\n\n\nmodule.exports = function lheading(state, startLine, endLine/*, silent*/) {\n  var marker, pos, max,\n      next = startLine + 1;\n\n  if (next >= endLine) { return false; }\n  if (state.tShift[next] < state.blkIndent) { return false; }\n\n  // Scan next line\n\n  if (state.tShift[next] - state.blkIndent > 3) { return false; }\n\n  pos = state.bMarks[next] + state.tShift[next];\n  max = state.eMarks[next];\n\n  if (pos >= max) { return false; }\n\n  marker = state.src.charCodeAt(pos);\n\n  if (marker !== 0x2D/* - */ && marker !== 0x3D/* = */) { return false; }\n\n  pos = state.skipChars(pos, marker);\n\n  pos = state.skipSpaces(pos);\n\n  if (pos < max) { return false; }\n\n  pos = state.bMarks[startLine] + state.tShift[startLine];\n\n  state.line = next + 1;\n  state.tokens.push({\n    type: 'heading_open',\n    hLevel: marker === 0x3D/* = */ ? 1 : 2,\n    lines: [ startLine, state.line ],\n    level: state.level\n  });\n  state.tokens.push({\n    type: 'inline',\n    content: state.src.slice(pos, state.eMarks[startLine]).trim(),\n    level: state.level + 1,\n    lines: [ startLine, state.line - 1 ],\n    children: []\n  });\n  state.tokens.push({\n    type: 'heading_close',\n    hLevel: marker === 0x3D/* = */ ? 1 : 2,\n    level: state.level\n  });\n\n  return true;\n};\n\n},{}],27:[function(require,module,exports){\n// Lists\n\n'use strict';\n\n\n// Search `[-+*][\\n ]`, returns next pos arter marker on success\n// or -1 on fail.\nfunction skipBulletListMarker(state, startLine) {\n  var marker, pos, max;\n\n  pos = state.bMarks[startLine] + state.tShift[startLine];\n  max = state.eMarks[startLine];\n\n  if (pos >= max) { return -1; }\n\n  marker = state.src.charCodeAt(pos++);\n  // Check bullet\n  if (marker !== 0x2A/* * */ &&\n      marker !== 0x2D/* - */ &&\n      marker !== 0x2B/* + */) {\n    return -1;\n  }\n\n  if (pos < max && state.src.charCodeAt(pos) !== 0x20) {\n    // \" 1.test \" - is not a list item\n    return -1;\n  }\n\n  return pos;\n}\n\n// Search `\\d+[.)][\\n ]`, returns next pos arter marker on success\n// or -1 on fail.\nfunction skipOrderedListMarker(state, startLine) {\n  var ch,\n      pos = state.bMarks[startLine] + state.tShift[startLine],\n      max = state.eMarks[startLine];\n\n  if (pos + 1 >= max) { return -1; }\n\n  ch = state.src.charCodeAt(pos++);\n\n  if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; }\n\n  for (;;) {\n    // EOL -> fail\n    if (pos >= max) { return -1; }\n\n    ch = state.src.charCodeAt(pos++);\n\n    if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) {\n      continue;\n    }\n\n    // found valid marker\n    if (ch === 0x29/* ) */ || ch === 0x2e/* . */) {\n      break;\n    }\n\n    return -1;\n  }\n\n\n  if (pos < max && state.src.charCodeAt(pos) !== 0x20/* space */) {\n    // \" 1.test \" - is not a list item\n    return -1;\n  }\n  return pos;\n}\n\nfunction markTightParagraphs(state, idx) {\n  var i, l,\n      level = state.level + 2;\n\n  for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {\n    if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {\n      state.tokens[i + 2].tight = true;\n      state.tokens[i].tight = true;\n      i += 2;\n    }\n  }\n}\n\n\nmodule.exports = function list(state, startLine, endLine, silent) {\n  var nextLine,\n      indent,\n      oldTShift,\n      oldIndent,\n      oldTight,\n      oldParentType,\n      start,\n      posAfterMarker,\n      max,\n      indentAfterMarker,\n      markerValue,\n      markerCharCode,\n      isOrdered,\n      contentStart,\n      listTokIdx,\n      prevEmptyEnd,\n      listLines,\n      itemLines,\n      tight = true,\n      terminatorRules,\n      i, l, terminate;\n\n  // Detect list type and position after marker\n  if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) {\n    isOrdered = true;\n  } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) {\n    isOrdered = false;\n  } else {\n    return false;\n  }\n\n  // We should terminate list on style change. Remember first one to compare.\n  markerCharCode = state.src.charCodeAt(posAfterMarker - 1);\n\n  // For validation mode we can terminate immediately\n  if (silent) { return true; }\n\n  // Start list\n  listTokIdx = state.tokens.length;\n\n  if (isOrdered) {\n    start = state.bMarks[startLine] + state.tShift[startLine];\n    markerValue = Number(state.src.substr(start, posAfterMarker - start - 1));\n\n    state.tokens.push({\n      type: 'ordered_list_open',\n      order: markerValue,\n      lines: listLines = [ startLine, 0 ],\n      level: state.level++\n    });\n\n  } else {\n    state.tokens.push({\n      type: 'bullet_list_open',\n      lines: listLines = [ startLine, 0 ],\n      level: state.level++\n    });\n  }\n\n  //\n  // Iterate list items\n  //\n\n  nextLine = startLine;\n  prevEmptyEnd = false;\n  terminatorRules = state.md.block.ruler.getRules('list');\n\n  while (nextLine < endLine) {\n    contentStart = state.skipSpaces(posAfterMarker);\n    max = state.eMarks[nextLine];\n\n    if (contentStart >= max) {\n      // trimming space in \"-    \\n  3\" case, indent is 1 here\n      indentAfterMarker = 1;\n    } else {\n      indentAfterMarker = contentStart - posAfterMarker;\n    }\n\n    // If we have more than 4 spaces, the indent is 1\n    // (the rest is just indented code block)\n    if (indentAfterMarker > 4) { indentAfterMarker = 1; }\n\n    // If indent is less than 1, assume that it's one, example:\n    //  \"-\\n  test\"\n    if (indentAfterMarker < 1) { indentAfterMarker = 1; }\n\n    // \"  -  test\"\n    //  ^^^^^ - calculating total length of this thing\n    indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker;\n\n    // Run subparser & write tokens\n    state.tokens.push({\n      type: 'list_item_open',\n      lines: itemLines = [ startLine, 0 ],\n      level: state.level++\n    });\n\n    oldIndent = state.blkIndent;\n    oldTight = state.tight;\n    oldTShift = state.tShift[startLine];\n    oldParentType = state.parentType;\n    state.tShift[startLine] = contentStart - state.bMarks[startLine];\n    state.blkIndent = indent;\n    state.tight = true;\n    state.parentType = 'list';\n\n    state.md.block.tokenize(state, startLine, endLine, true);\n\n    // If any of list item is tight, mark list as tight\n    if (!state.tight || prevEmptyEnd) {\n      tight = false;\n    }\n    // Item become loose if finish with empty line,\n    // but we should filter last element, because it means list finish\n    prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1);\n\n    state.blkIndent = oldIndent;\n    state.tShift[startLine] = oldTShift;\n    state.tight = oldTight;\n    state.parentType = oldParentType;\n\n    state.tokens.push({\n      type: 'list_item_close',\n      level: --state.level\n    });\n\n    nextLine = startLine = state.line;\n    itemLines[1] = nextLine;\n    contentStart = state.bMarks[startLine];\n\n    if (nextLine >= endLine) { break; }\n\n    if (state.isEmpty(nextLine)) {\n      break;\n    }\n\n    //\n    // Try to check if list is terminated or continued.\n    //\n    if (state.tShift[nextLine] < state.blkIndent) { break; }\n\n    // fail if terminating block found\n    terminate = false;\n    for (i = 0, l = terminatorRules.length; i < l; i++) {\n      if (terminatorRules[i](state, nextLine, endLine, true)) {\n        terminate = true;\n        break;\n      }\n    }\n    if (terminate) { break; }\n\n    // fail if list has another type\n    if (isOrdered) {\n      posAfterMarker = skipOrderedListMarker(state, nextLine);\n      if (posAfterMarker < 0) { break; }\n    } else {\n      posAfterMarker = skipBulletListMarker(state, nextLine);\n      if (posAfterMarker < 0) { break; }\n    }\n\n    if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; }\n  }\n\n  // Finilize list\n  state.tokens.push({\n    type: isOrdered ? 'ordered_list_close' : 'bullet_list_close',\n    level: --state.level\n  });\n  listLines[1] = nextLine;\n\n  state.line = nextLine;\n\n  // mark paragraphs tight if needed\n  if (tight) {\n    markTightParagraphs(state, listTokIdx);\n  }\n\n  return true;\n};\n\n},{}],28:[function(require,module,exports){\n// Paragraph\n\n'use strict';\n\n\nmodule.exports = function paragraph(state, startLine/*, endLine*/) {\n  var endLine, content, terminate, i, l,\n      nextLine = startLine + 1,\n      terminatorRules;\n\n  endLine = state.lineMax;\n\n  // jump line-by-line until empty one or EOF\n  if (nextLine < endLine && !state.isEmpty(nextLine)) {\n    terminatorRules = state.md.block.ruler.getRules('paragraph');\n\n    for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {\n      // this would be a code block normally, but after paragraph\n      // it's considered a lazy continuation regardless of what's there\n      if (state.tShift[nextLine] - state.blkIndent > 3) { continue; }\n\n      // Some tags can terminate paragraph without empty line.\n      terminate = false;\n      for (i = 0, l = terminatorRules.length; i < l; i++) {\n        if (terminatorRules[i](state, nextLine, endLine, true)) {\n          terminate = true;\n          break;\n        }\n      }\n      if (terminate) { break; }\n    }\n  }\n\n  content = state.getLines(startLine, nextLine, state.blkIndent, false).trim();\n\n  state.line = nextLine;\n  if (content.length) {\n    state.tokens.push({\n      type: 'paragraph_open',\n      tight: false,\n      lines: [ startLine, state.line ],\n      level: state.level\n    });\n    state.tokens.push({\n      type: 'inline',\n      content: content,\n      level: state.level + 1,\n      lines: [ startLine, state.line ],\n      children: []\n    });\n    state.tokens.push({\n      type: 'paragraph_close',\n      tight: false,\n      level: state.level\n    });\n  }\n\n  return true;\n};\n\n},{}],29:[function(require,module,exports){\n'use strict';\n\n\nvar parseLinkDestination = require('../helpers/parse_link_destination');\nvar parseLinkTitle       = require('../helpers/parse_link_title');\nvar normalizeReference   = require('../helpers/normalize_reference');\n\n\nmodule.exports = function reference(state, startLine, _endLine, silent) {\n  var ch,\n      destEndPos,\n      destEndLineNo,\n      endLine,\n      href,\n      i,\n      l,\n      label,\n      labelEnd,\n      res,\n      start,\n      str,\n      terminate,\n      terminatorRules,\n      title,\n      lines = 0,\n      pos = state.bMarks[startLine] + state.tShift[startLine],\n      max = state.eMarks[startLine],\n      nextLine = startLine + 1;\n\n  if (pos >= max) { return false; }\n  if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; }\n\n  endLine = state.lineMax;\n\n  // jump line-by-line until empty one or EOF\n  if (nextLine < endLine && !state.isEmpty(nextLine)) {\n    terminatorRules = state.md.block.ruler.getRules('references');\n\n    for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {\n      // this would be a code block normally, but after paragraph\n      // it's considered a lazy continuation regardless of what's there\n      if (state.tShift[nextLine] - state.blkIndent > 3) { continue; }\n\n      // Some tags can terminate paragraph without empty line.\n      terminate = false;\n      for (i = 0, l = terminatorRules.length; i < l; i++) {\n        if (terminatorRules[i](state, nextLine, endLine, true)) {\n          terminate = true;\n          break;\n        }\n      }\n      if (terminate) { break; }\n    }\n  }\n\n  str = state.getLines(startLine, nextLine, state.blkIndent, false).trim();\n  max = str.length;\n\n  for (pos = 1; pos < max; pos++) {\n    ch = str.charCodeAt(pos);\n    if (ch === 0x5B /* [ */) {\n      return false;\n    } else if (ch === 0x5D /* ] */) {\n      labelEnd = pos;\n      break;\n    } else if (ch === 0x0A /* \\n */) {\n      lines++;\n    } else if (ch === 0x5C /* \\ */) {\n      pos++;\n      if (pos < max && str.charCodeAt(pos) === 0x0A) {\n        lines++;\n      }\n    }\n  }\n\n  if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; }\n\n  // [label]:   destination   'title'\n  //         ^^^ skip optional whitespace here\n  for (pos = labelEnd + 2; pos < max; pos++) {\n    ch = str.charCodeAt(pos);\n    if (ch === 0x0A) {\n      lines++;\n    } else if (ch === 0x20) {\n      /*eslint no-empty:0*/\n    } else {\n      break;\n    }\n  }\n\n  // [label]:   destination   'title'\n  //            ^^^^^^^^^^^ parse this\n  res = parseLinkDestination(str, pos, max);\n  if (!res.ok) { return false; }\n  if (!state.md.inline.validateLink(res.str)) { return false; }\n  href = res.str;\n  pos = res.pos;\n  lines += res.lines;\n\n  // save cursor state, we could require to rollback later\n  destEndPos = pos;\n  destEndLineNo = lines;\n\n  // [label]:   destination   'title'\n  //                       ^^^ skipping those spaces\n  start = pos;\n  for (; pos < max; pos++) {\n    ch = str.charCodeAt(pos);\n    if (ch === 0x0A) {\n      lines++;\n    } else if (ch === 0x20) {\n      /*eslint no-empty:0*/\n    } else {\n      break;\n    }\n  }\n\n  // [label]:   destination   'title'\n  //                          ^^^^^^^ parse this\n  res = parseLinkTitle(str, pos, max);\n  if (pos < max && start !== pos && res.ok) {\n    title = res.str;\n    pos = res.pos;\n    lines += res.lines;\n  } else {\n    title = '';\n    pos = destEndPos;\n    lines = destEndLineNo;\n  }\n\n  // skip trailing spaces until the rest of the line\n  while (pos < max && str.charCodeAt(pos) === 0x20/* space */) { pos++; }\n\n  if (pos < max && str.charCodeAt(pos) !== 0x0A) {\n    // garbage at the end of the line\n    return false;\n  }\n\n  if (silent) { return true; }\n\n  label = normalizeReference(str.slice(1, labelEnd));\n  if (typeof state.env.references === 'undefined') {\n    state.env.references = {};\n  }\n  if (typeof state.env.references[label] === 'undefined') {\n    state.env.references[label] = { title: title, href: href };\n  }\n\n  state.line = startLine + lines + 1;\n  return true;\n};\n\n},{\"../helpers/normalize_reference\":7,\"../helpers/parse_link_destination\":8,\"../helpers/parse_link_title\":10}],30:[function(require,module,exports){\n// Parser state class\n\n'use strict';\n\n\nfunction StateBlock(src, md, env, tokens) {\n  var ch, s, start, pos, len, indent, indent_found;\n\n  this.src = src;\n\n  // link to parser instance\n  this.md     = md;\n\n  this.env = env;\n\n  //\n  // Internal state vartiables\n  //\n\n  this.tokens = tokens;\n\n  this.bMarks = [];  // line begin offsets for fast jumps\n  this.eMarks = [];  // line end offsets for fast jumps\n  this.tShift = [];  // indent for each line\n\n  // block parser variables\n  this.blkIndent  = 0; // required block content indent\n                       // (for example, if we are in list)\n  this.line       = 0; // line index in src\n  this.lineMax    = 0; // lines count\n  this.tight      = false;  // loose/tight mode for lists\n  this.parentType = 'root'; // if `list`, block parser stops on two newlines\n  this.ddIndent   = -1; // indent of the current dd block (-1 if there isn't any)\n\n  this.level = 0;\n\n  // renderer\n  this.result = '';\n\n  // Create caches\n  // Generate markers.\n  s = this.src;\n  indent = 0;\n  indent_found = false;\n\n  for (start = pos = indent = 0, len = s.length; pos < len; pos++) {\n    ch = s.charCodeAt(pos);\n\n    if (!indent_found) {\n      if (ch === 0x20/* space */) {\n        indent++;\n        continue;\n      } else {\n        indent_found = true;\n      }\n    }\n\n    if (ch === 0x0A || pos === len - 1) {\n      if (ch !== 0x0A) { pos++; }\n      this.bMarks.push(start);\n      this.eMarks.push(pos);\n      this.tShift.push(indent);\n\n      indent_found = false;\n      indent = 0;\n      start = pos + 1;\n    }\n  }\n\n  // Push fake entry to simplify cache bounds checks\n  this.bMarks.push(s.length);\n  this.eMarks.push(s.length);\n  this.tShift.push(0);\n\n  this.lineMax = this.bMarks.length - 1; // don't count last fake line\n}\n\nStateBlock.prototype.isEmpty = function isEmpty(line) {\n  return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];\n};\n\nStateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) {\n  for (var max = this.lineMax; from < max; from++) {\n    if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {\n      break;\n    }\n  }\n  return from;\n};\n\n// Skip spaces from given position.\nStateBlock.prototype.skipSpaces = function skipSpaces(pos) {\n  for (var max = this.src.length; pos < max; pos++) {\n    if (this.src.charCodeAt(pos) !== 0x20/* space */) { break; }\n  }\n  return pos;\n};\n\n// Skip char codes from given position\nStateBlock.prototype.skipChars = function skipChars(pos, code) {\n  for (var max = this.src.length; pos < max; pos++) {\n    if (this.src.charCodeAt(pos) !== code) { break; }\n  }\n  return pos;\n};\n\n// Skip char codes reverse from given position - 1\nStateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) {\n  if (pos <= min) { return pos; }\n\n  while (pos > min) {\n    if (code !== this.src.charCodeAt(--pos)) { return pos + 1; }\n  }\n  return pos;\n};\n\n// cut lines range from source.\nStateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) {\n  var i, first, last, queue, shift,\n      line = begin;\n\n  if (begin >= end) {\n    return '';\n  }\n\n  // Opt: don't use push queue for single line;\n  if (line + 1 === end) {\n    first = this.bMarks[line] + Math.min(this.tShift[line], indent);\n    last = keepLastLF ? this.bMarks[end] : this.eMarks[end - 1];\n    return this.src.slice(first, last);\n  }\n\n  queue = new Array(end - begin);\n\n  for (i = 0; line < end; line++, i++) {\n    shift = this.tShift[line];\n    if (shift > indent) { shift = indent; }\n    if (shift < 0) { shift = 0; }\n\n    first = this.bMarks[line] + shift;\n\n    if (line + 1 < end || keepLastLF) {\n      // No need for bounds check because we have fake entry on tail.\n      last = this.eMarks[line] + 1;\n    } else {\n      last = this.eMarks[line];\n    }\n\n    queue[i] = this.src.slice(first, last);\n  }\n\n  return queue.join('');\n};\n\n\nmodule.exports = StateBlock;\n\n},{}],31:[function(require,module,exports){\n// GFM table, non-standard\n\n'use strict';\n\n\nfunction getLine(state, line) {\n  var pos = state.bMarks[line] + state.blkIndent,\n      max = state.eMarks[line];\n\n  return state.src.substr(pos, max - pos);\n}\n\n\nmodule.exports = function table(state, startLine, endLine, silent) {\n  var ch, lineText, pos, i, nextLine, rows,\n      aligns, t, tableLines, tbodyLines;\n\n  // should have at least three lines\n  if (startLine + 2 > endLine) { return false; }\n\n  nextLine = startLine + 1;\n\n  if (state.tShift[nextLine] < state.blkIndent) { return false; }\n\n  // first character of the second line should be '|' or '-'\n\n  pos = state.bMarks[nextLine] + state.tShift[nextLine];\n  if (pos >= state.eMarks[nextLine]) { return false; }\n\n  ch = state.src.charCodeAt(pos);\n  if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; }\n\n  lineText = getLine(state, startLine + 1);\n  if (!/^[-:| ]+$/.test(lineText)) { return false; }\n\n  rows = lineText.split('|');\n  if (rows <= 2) { return false; }\n  aligns = [];\n  for (i = 0; i < rows.length; i++) {\n    t = rows[i].trim();\n    if (!t) {\n      // allow empty columns before and after table, but not in between columns;\n      // e.g. allow ` |---| `, disallow ` ---||--- `\n      if (i === 0 || i === rows.length - 1) {\n        continue;\n      } else {\n        return false;\n      }\n    }\n\n    if (!/^:?-+:?$/.test(t)) { return false; }\n    if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {\n      aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');\n    } else if (t.charCodeAt(0) === 0x3A/* : */) {\n      aligns.push('left');\n    } else {\n      aligns.push('');\n    }\n  }\n\n  lineText = getLine(state, startLine).trim();\n  if (lineText.indexOf('|') === -1) { return false; }\n  rows = lineText.replace(/^\\||\\|$/g, '').split('|');\n  if (aligns.length !== rows.length) { return false; }\n  if (silent) { return true; }\n\n  state.tokens.push({\n    type: 'table_open',\n    lines: tableLines = [ startLine, 0 ],\n    level: state.level++\n  });\n  state.tokens.push({\n    type: 'thead_open',\n    lines: [ startLine, startLine + 1 ],\n    level: state.level++\n  });\n\n  state.tokens.push({\n    type: 'tr_open',\n    lines: [ startLine, startLine + 1 ],\n    level: state.level++\n  });\n  for (i = 0; i < rows.length; i++) {\n    state.tokens.push({\n      type: 'th_open',\n      align: aligns[i],\n      lines: [ startLine, startLine + 1 ],\n      level: state.level++\n    });\n    state.tokens.push({\n      type: 'inline',\n      content: rows[i].trim(),\n      lines: [ startLine, startLine + 1 ],\n      level: state.level,\n      children: []\n    });\n    state.tokens.push({ type: 'th_close', level: --state.level });\n  }\n  state.tokens.push({ type: 'tr_close', level: --state.level });\n  state.tokens.push({ type: 'thead_close', level: --state.level });\n\n  state.tokens.push({\n    type: 'tbody_open',\n    lines: tbodyLines = [ startLine + 2, 0 ],\n    level: state.level++\n  });\n\n  for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {\n    if (state.tShift[nextLine] < state.blkIndent) { break; }\n\n    lineText = getLine(state, nextLine).trim();\n    if (lineText.indexOf('|') === -1) { break; }\n    rows = lineText.replace(/^\\||\\|$/g, '').split('|');\n\n    state.tokens.push({ type: 'tr_open', level: state.level++ });\n    for (i = 0; i < rows.length; i++) {\n      state.tokens.push({ type: 'td_open', align: aligns[i], level: state.level++ });\n      state.tokens.push({\n        type: 'inline',\n        content: rows[i].replace(/^\\|? *| *\\|?$/g, ''),\n        level: state.level,\n        children: []\n      });\n      state.tokens.push({ type: 'td_close', level: --state.level });\n    }\n    state.tokens.push({ type: 'tr_close', level: --state.level });\n  }\n  state.tokens.push({ type: 'tbody_close', level: --state.level });\n  state.tokens.push({ type: 'table_close', level: --state.level });\n\n  tableLines[1] = tbodyLines[1] = nextLine;\n  state.line = nextLine;\n  return true;\n};\n\n},{}],32:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function block(state) {\n\n  if (state.inlineMode) {\n    state.tokens.push({\n      type: 'inline',\n      content: state.src,\n      level: 0,\n      lines: [ 0, 1 ],\n      children: []\n    });\n\n  } else {\n    state.md.block.parse(state.src, state.md, state.env, state.tokens);\n  }\n};\n\n},{}],33:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function inline(state) {\n  var tokens = state.tokens, tok, i, l;\n\n  // Parse inlines\n  for (i = 0, l = tokens.length; i < l; i++) {\n    tok = tokens[i];\n    if (tok.type === 'inline') {\n      state.md.inline.parse(tok.content, state.md, state.env, tok.children);\n    }\n  }\n};\n\n},{}],34:[function(require,module,exports){\n// Replace link-like texts with link nodes.\n//\n// Currently restricted by `inline.validateLink()` to http/https/ftp\n//\n'use strict';\n\n\nvar Autolinker     = require('autolinker');\nvar arrayReplaceAt = require('../common/utils').arrayReplaceAt;\n\n\nvar LINK_SCAN_RE = /www|@|\\:\\/\\//;\n\n\nfunction isLinkOpen(str) {\n  return /^<a[>\\s]/i.test(str);\n}\nfunction isLinkClose(str) {\n  return /^<\\/a\\s*>/i.test(str);\n}\n\n// Stupid fabric to avoid singletons, for thread safety.\n// Required for engines like Nashorn.\n//\nfunction createLinkifier() {\n  var links = [];\n  var autolinker = new Autolinker({\n    stripPrefix: false,\n    url: true,\n    email: true,\n    twitter: false,\n    replaceFn: function (autolinker, match) {\n      // Only collect matched strings but don't change anything.\n      switch (match.getType()) {\n        /*eslint default-case:0*/\n        case 'url':\n          links.push({\n            text: match.matchedText,\n            url: match.getUrl()\n          });\n          break;\n        case 'email':\n          links.push({\n            text: match.matchedText,\n            // normalize email protocol\n            url: 'mailto:' + match.getEmail().replace(/^mailto:/i, '')\n          });\n          break;\n      }\n      return false;\n    }\n  });\n\n  return {\n    links: links,\n    autolinker: autolinker\n  };\n}\n\n\nmodule.exports = function linkify(state) {\n  var i, j, l, tokens, token, text, nodes, ln, pos, level, htmlLinkLevel,\n      blockTokens = state.tokens,\n      linkifier = null, links, autolinker;\n\n  if (!state.md.options.linkify) { return; }\n\n  for (j = 0, l = blockTokens.length; j < l; j++) {\n    if (blockTokens[j].type !== 'inline') { continue; }\n    tokens = blockTokens[j].children;\n\n    htmlLinkLevel = 0;\n\n    // We scan from the end, to keep position when new tags added.\n    // Use reversed logic in links start/end match\n    for (i = tokens.length - 1; i >= 0; i--) {\n      token = tokens[i];\n\n      // Skip content of markdown links\n      if (token.type === 'link_close') {\n        i--;\n        while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') {\n          i--;\n        }\n        continue;\n      }\n\n      // Skip content of html tag links\n      if (token.type === 'html_inline') {\n        if (isLinkOpen(token.content) && htmlLinkLevel > 0) {\n          htmlLinkLevel--;\n        }\n        if (isLinkClose(token.content)) {\n          htmlLinkLevel++;\n        }\n      }\n      if (htmlLinkLevel > 0) { continue; }\n\n      if (token.type === 'text' && LINK_SCAN_RE.test(token.content)) {\n\n        // Init linkifier in lazy manner, only if required.\n        if (!linkifier) {\n          linkifier = createLinkifier();\n          links = linkifier.links;\n          autolinker = linkifier.autolinker;\n        }\n\n        text = token.content;\n        links.length = 0;\n        autolinker.link(text);\n\n        if (!links.length) { continue; }\n\n        // Now split string to nodes\n        nodes = [];\n        level = token.level;\n\n        for (ln = 0; ln < links.length; ln++) {\n\n          if (!state.md.inline.validateLink(links[ln].url)) { continue; }\n\n          pos = text.indexOf(links[ln].text);\n\n          if (pos) {\n            level = level;\n            nodes.push({\n              type: 'text',\n              content: text.slice(0, pos),\n              level: level\n            });\n          }\n          nodes.push({\n            type: 'link_open',\n            href: links[ln].url,\n            target: '',\n            title: '',\n            level: level++\n          });\n          nodes.push({\n            type: 'text',\n            content: links[ln].text,\n            level: level\n          });\n          nodes.push({\n            type: 'link_close',\n            level: --level\n          });\n          text = text.slice(pos + links[ln].text.length);\n        }\n        if (text.length) {\n          nodes.push({\n            type: 'text',\n            content: text,\n            level: level\n          });\n        }\n\n        // replace current node\n        blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes);\n      }\n    }\n  }\n};\n\n},{\"../common/utils\":5,\"autolinker\":51}],35:[function(require,module,exports){\n// Normalize input string\n\n'use strict';\n\n\nvar TABS_SCAN_RE = /[\\n\\t]/g;\nvar NEWLINES_RE  = /\\r[\\n\\u0085]|[\\u2424\\u2028\\u0085]/g;\nvar NULL_RE      = /\\u0000/g;\n\n\nmodule.exports = function inline(state) {\n  var str, lineStart, lastTabPos;\n\n  if (!state.normalizeInput) { return; }\n\n  // Normalize newlines\n  str = state.src.replace(NEWLINES_RE, '\\n');\n\n  // Replace NULL characters\n  str = str.replace(NULL_RE, '\\uFFFD');\n\n  // Replace tabs with proper number of spaces (1..4)\n  if (str.indexOf('\\t') >= 0) {\n    lineStart = 0;\n    lastTabPos = 0;\n\n    str = str.replace(TABS_SCAN_RE, function (match, offset) {\n      var result;\n      if (str.charCodeAt(offset) === 0x0A) {\n        lineStart = offset + 1;\n        lastTabPos = 0;\n        return match;\n      }\n      result = '    '.slice((offset - lineStart - lastTabPos) % 4);\n      lastTabPos = offset - lineStart + 1;\n      return result;\n    });\n  }\n\n  state.src = str;\n};\n\n},{}],36:[function(require,module,exports){\n// Simple typographyc replacements\n//\n// '' → ‘’\n// \"\" → “”. Set '«»' for Russian, '„“' for German, empty to disable\n// (c) (C) → ©\n// (tm) (TM) → ™\n// (r) (R) → ®\n// +- → ±\n// (p) (P) -> §\n// ... → … (also ?.... → ?.., !.... → !..)\n// ???????? → ???, !!!!! → !!!, `,,` → `,`\n// -- → &ndash;, --- → &mdash;\n//\n'use strict';\n\n// TODO:\n// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾\n// - miltiplication 2 x 4 -> 2 × 4\n\nvar RARE_RE = /\\+-|\\.\\.|\\?\\?\\?\\?|!!!!|,,|--/;\n\nvar SCOPED_ABBR_RE = /\\((c|tm|r|p)\\)/ig;\nvar SCOPED_ABBR = {\n  'c': '©',\n  'r': '®',\n  'p': '§',\n  'tm': '™'\n};\n\nfunction replaceScopedAbbr(str) {\n  if (str.indexOf('(') < 0) { return str; }\n\n  return str.replace(SCOPED_ABBR_RE, function(match, name) {\n    return SCOPED_ABBR[name.toLowerCase()];\n  });\n}\n\n\nmodule.exports = function replace(state) {\n  var i, token, text, inlineTokens, blkIdx;\n\n  if (!state.md.options.typographer) { return; }\n\n  for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {\n\n    if (state.tokens[blkIdx].type !== 'inline') { continue; }\n\n    inlineTokens = state.tokens[blkIdx].children;\n\n    for (i = inlineTokens.length - 1; i >= 0; i--) {\n      token = inlineTokens[i];\n      if (token.type === 'text') {\n        text = token.content;\n\n        text = replaceScopedAbbr(text);\n\n        if (RARE_RE.test(text)) {\n          text = text.replace(/\\+-/g, '±')\n                      // .., ..., ....... -> …\n                      // but ?..... & !..... -> ?.. & !..\n                      .replace(/\\.{2,}/g, '…').replace(/([?!])…/g, '$1..')\n                      .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',')\n                      // em-dash\n                      .replace(/(^|[^-])---([^-]|$)/mg, '$1\\u2014$2')\n                      // en-dash\n                      .replace(/(^|\\s)--(\\s|$)/mg, '$1\\u2013$2')\n                      .replace(/(^|[^-\\s])--([^-\\s]|$)/mg, '$1\\u2013$2');\n        }\n\n        token.content = text;\n      }\n    }\n  }\n};\n\n},{}],37:[function(require,module,exports){\n// Convert straight quotation marks to typographic ones\n//\n'use strict';\n\n\nvar QUOTE_TEST_RE = /['\"]/;\nvar QUOTE_RE = /['\"]/g;\nvar PUNCT_RE = /[-\\s()\\[\\]]/;\nvar APOSTROPHE = '\\u2019'; /* ’ */\n\n// This function returns true if the character at `pos`\n// could be inside a word.\nfunction isLetter(str, pos) {\n  if (pos < 0 || pos >= str.length) { return false; }\n  return !PUNCT_RE.test(str[pos]);\n}\n\n\nfunction replaceAt(str, index, ch) {\n  return str.substr(0, index) + ch + str.substr(index + 1);\n}\n\n\nmodule.exports = function smartquotes(state) {\n  /*eslint max-depth:0*/\n  var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item,\n      canOpen, canClose, j, isSingle, blkIdx, tokens,\n      stack;\n\n  if (!state.md.options.typographer) { return; }\n\n  stack = [];\n\n  for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {\n\n    if (state.tokens[blkIdx].type !== 'inline') { continue; }\n\n    tokens = state.tokens[blkIdx].children;\n    stack.length = 0;\n\n    for (i = 0; i < tokens.length; i++) {\n      token = tokens[i];\n\n      if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; }\n\n      thisLevel = tokens[i].level;\n\n      for (j = stack.length - 1; j >= 0; j--) {\n        if (stack[j].level <= thisLevel) { break; }\n      }\n      stack.length = j + 1;\n\n      text = token.content;\n      pos = 0;\n      max = text.length;\n\n      /*eslint no-labels:0,block-scoped-var:0*/\n      OUTER:\n      while (pos < max) {\n        QUOTE_RE.lastIndex = pos;\n        t = QUOTE_RE.exec(text);\n        if (!t) { break; }\n\n        lastSpace = !isLetter(text, t.index - 1);\n        pos = t.index + 1;\n        isSingle = (t[0] === \"'\");\n        nextSpace = !isLetter(text, pos);\n\n        if (!nextSpace && !lastSpace) {\n          // middle of word\n          if (isSingle) {\n            token.content = replaceAt(token.content, t.index, APOSTROPHE);\n          }\n          continue;\n        }\n\n        canOpen = !nextSpace;\n        canClose = !lastSpace;\n\n        if (canClose) {\n          // this could be a closing quote, rewind the stack to get a match\n          for (j = stack.length - 1; j >= 0; j--) {\n            item = stack[j];\n            if (stack[j].level < thisLevel) { break; }\n            if (item.single === isSingle && stack[j].level === thisLevel) {\n              item = stack[j];\n              if (isSingle) {\n                tokens[item.token].content = replaceAt(\n                  tokens[item.token].content, item.pos, state.md.options.quotes[2]);\n                token.content = replaceAt(\n                  token.content, t.index, state.md.options.quotes[3]);\n              } else {\n                tokens[item.token].content = replaceAt(\n                  tokens[item.token].content, item.pos, state.md.options.quotes[0]);\n                token.content = replaceAt(token.content, t.index, state.md.options.quotes[1]);\n              }\n              stack.length = j;\n              continue OUTER;\n            }\n          }\n        }\n\n        if (canOpen) {\n          stack.push({\n            token: i,\n            pos: t.index,\n            single: isSingle,\n            level: thisLevel\n          });\n        } else if (canClose && isSingle) {\n          token.content = replaceAt(token.content, t.index, APOSTROPHE);\n        }\n      }\n    }\n  }\n};\n\n},{}],38:[function(require,module,exports){\n// Core state object\n//\n'use strict';\n\nmodule.exports = function StateCore(src, md, env) {\n  this.src = src;\n  this.env = env;\n  this.tokens = [];\n  this.inlineMode = false;\n  this.normalizeInput = true;\n  this.md = md; // link to parser instance\n};\n\n},{}],39:[function(require,module,exports){\n// Process autolinks '<protocol:...>'\n\n'use strict';\n\nvar url_schemas   = require('../common/url_schemas');\nvar normalizeLink = require('../common/utils').normalizeLink;\n\n\n/*eslint max-len:0*/\nvar EMAIL_RE    = /^<([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/;\nvar AUTOLINK_RE = /^<([a-zA-Z.\\-]{1,25}):([^<>\\x00-\\x20]*)>/;\n\n\nmodule.exports = function autolink(state, silent) {\n  var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos;\n\n  if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }\n\n  tail = state.src.slice(pos);\n\n  if (tail.indexOf('>') < 0) { return false; }\n\n  linkMatch = tail.match(AUTOLINK_RE);\n\n  if (linkMatch) {\n    if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; }\n\n    url = linkMatch[0].slice(1, -1);\n    fullUrl = normalizeLink(url);\n    if (!state.md.inline.validateLink(url)) { return false; }\n\n    if (!silent) {\n      state.push({\n        type: 'link_open',\n        href: fullUrl,\n        target: '',\n        level: state.level\n      });\n      state.push({\n        type: 'text',\n        content: url,\n        level: state.level + 1\n      });\n      state.push({ type: 'link_close', level: state.level });\n    }\n\n    state.pos += linkMatch[0].length;\n    return true;\n  }\n\n  emailMatch = tail.match(EMAIL_RE);\n\n  if (emailMatch) {\n\n    url = emailMatch[0].slice(1, -1);\n\n    fullUrl = normalizeLink('mailto:' + url);\n    if (!state.md.inline.validateLink(fullUrl)) { return false; }\n\n    if (!silent) {\n      state.push({\n        type: 'link_open',\n        href: fullUrl,\n        target: '',\n        level: state.level\n      });\n      state.push({\n        type: 'text',\n        content: url,\n        level: state.level + 1\n      });\n      state.push({ type: 'link_close', level: state.level });\n    }\n\n    state.pos += emailMatch[0].length;\n    return true;\n  }\n\n  return false;\n};\n\n},{\"../common/url_schemas\":4,\"../common/utils\":5}],40:[function(require,module,exports){\n// Parse backticks\n\n'use strict';\n\nmodule.exports = function backtick(state, silent) {\n  var start, max, marker, matchStart, matchEnd,\n      pos = state.pos,\n      ch = state.src.charCodeAt(pos);\n\n  if (ch !== 0x60/* ` */) { return false; }\n\n  start = pos;\n  pos++;\n  max = state.posMax;\n\n  while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; }\n\n  marker = state.src.slice(start, pos);\n\n  matchStart = matchEnd = pos;\n\n  while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) {\n    matchEnd = matchStart + 1;\n\n    while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; }\n\n    if (matchEnd - matchStart === marker.length) {\n      if (!silent) {\n        state.push({\n          type: 'code_inline',\n          content: state.src.slice(pos, matchStart)\n                              .replace(/[ \\n]+/g, ' ')\n                              .trim(),\n          level: state.level\n        });\n      }\n      state.pos = matchEnd;\n      return true;\n    }\n  }\n\n  if (!silent) { state.pending += marker; }\n  state.pos += marker.length;\n  return true;\n};\n\n},{}],41:[function(require,module,exports){\n// Process *this* and _that_\n\n'use strict';\n\nvar isWhiteSpace   = require('../common/utils').isWhiteSpace;\nvar isPunctChar    = require('../common/utils').isPunctChar;\nvar isMdAsciiPunct = require('../common/utils').isMdAsciiPunct;\n\n\nfunction isAlphaNum(code) {\n  return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) ||\n         (code >= 0x41 /* A */ && code <= 0x5A /* Z */) ||\n         (code >= 0x61 /* a */ && code <= 0x7A /* z */);\n}\n\n// parse sequence of emphasis markers,\n// \"start\" should point at a valid marker\nfunction scanDelims(state, start) {\n  var pos = start, lastChar, nextChar, count,\n      isLastWhiteSpace, isLastPunctChar,\n      isNextWhiteSpace, isNextPunctChar,\n      can_open = true,\n      can_close = true,\n      max = state.posMax,\n      marker = state.src.charCodeAt(start);\n\n  lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;\n\n  while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }\n  if (pos >= max) { can_open = false; }\n  count = pos - start;\n\n  nextChar = pos < max ? state.src.charCodeAt(pos) : -1;\n\n  isLastPunctChar = lastChar >= 0 &&\n    (isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)));\n  isNextPunctChar = nextChar >= 0 &&\n    (isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)));\n  isLastWhiteSpace = lastChar >= 0 && isWhiteSpace(lastChar);\n  isNextWhiteSpace = nextChar >= 0 && isWhiteSpace(nextChar);\n\n  if (isNextWhiteSpace) {\n    can_open = false;\n  } else if (isNextPunctChar) {\n    if (!(isLastWhiteSpace || isLastPunctChar || lastChar === -1)) {\n      can_open = false;\n    }\n  }\n\n  if (isLastWhiteSpace) {\n    can_close = false;\n  } else if (isLastPunctChar) {\n    if (!(isNextWhiteSpace || isNextPunctChar || nextChar === -1)) {\n      can_close = false;\n    }\n  }\n\n  if (marker === 0x5F /* _ */) {\n    // check if we aren't inside the word\n    if (isAlphaNum(lastChar)) { can_open = false; }\n    if (isAlphaNum(nextChar)) { can_close = false; }\n  }\n\n  return {\n    can_open: can_open,\n    can_close: can_close,\n    delims: count\n  };\n}\n\nmodule.exports = function emphasis(state, silent) {\n  var startCount,\n      count,\n      found,\n      oldCount,\n      newCount,\n      stack,\n      res,\n      max = state.posMax,\n      start = state.pos,\n      marker = state.src.charCodeAt(start);\n\n  if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; }\n  if (silent) { return false; } // don't run any pairs in validation mode\n\n  res = scanDelims(state, start);\n  startCount = res.delims;\n  if (!res.can_open) {\n    state.pos += startCount;\n    // Earlier we checked !silent, but this implementation does not need it\n    state.pending += state.src.slice(start, state.pos);\n    return true;\n  }\n\n  state.pos = start + startCount;\n  stack = [ startCount ];\n\n  while (state.pos < max) {\n    if (state.src.charCodeAt(state.pos) === marker) {\n      res = scanDelims(state, state.pos);\n      count = res.delims;\n      if (res.can_close) {\n        oldCount = stack.pop();\n        newCount = count;\n\n        while (oldCount !== newCount) {\n          if (newCount < oldCount) {\n            stack.push(oldCount - newCount);\n            break;\n          }\n\n          // assert(newCount > oldCount)\n          newCount -= oldCount;\n\n          if (stack.length === 0) { break; }\n          state.pos += oldCount;\n          oldCount = stack.pop();\n        }\n\n        if (stack.length === 0) {\n          startCount = oldCount;\n          found = true;\n          break;\n        }\n        state.pos += count;\n        continue;\n      }\n\n      if (res.can_open) { stack.push(count); }\n      state.pos += count;\n      continue;\n    }\n\n    state.md.inline.skipToken(state);\n  }\n\n  if (!found) {\n    // parser failed to find ending tag, so it's not valid emphasis\n    state.pos = start;\n    return false;\n  }\n\n  // found!\n  state.posMax = state.pos;\n  state.pos = start + startCount;\n\n  // Earlier we checked !silent, but this implementation does not need it\n\n  // we have `startCount` starting and ending markers,\n  // now trying to serialize them into tokens\n  for (count = startCount; count > 1; count -= 2) {\n    state.push({ type: 'strong_open', level: state.level++ });\n  }\n  if (count % 2) { state.push({ type: 'em_open', level: state.level++ }); }\n\n  state.md.inline.tokenize(state);\n\n  if (count % 2) { state.push({ type: 'em_close', level: --state.level }); }\n  for (count = startCount; count > 1; count -= 2) {\n    state.push({ type: 'strong_close', level: --state.level });\n  }\n\n  state.pos = state.posMax + startCount;\n  state.posMax = max;\n  return true;\n};\n\n},{\"../common/utils\":5}],42:[function(require,module,exports){\n// Process html entity - &#123;, &#xAF;, &quot;, ...\n\n'use strict';\n\nvar entities          = require('../common/entities');\nvar has               = require('../common/utils').has;\nvar isValidEntityCode = require('../common/utils').isValidEntityCode;\nvar fromCodePoint     = require('../common/utils').fromCodePoint;\n\n\nvar DIGITAL_RE = /^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i;\nvar NAMED_RE   = /^&([a-z][a-z0-9]{1,31});/i;\n\n\nmodule.exports = function entity(state, silent) {\n  var ch, code, match, pos = state.pos, max = state.posMax;\n\n  if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; }\n\n  if (pos + 1 < max) {\n    ch = state.src.charCodeAt(pos + 1);\n\n    if (ch === 0x23 /* # */) {\n      match = state.src.slice(pos).match(DIGITAL_RE);\n      if (match) {\n        if (!silent) {\n          code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10);\n          state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD);\n        }\n        state.pos += match[0].length;\n        return true;\n      }\n    } else {\n      match = state.src.slice(pos).match(NAMED_RE);\n      if (match) {\n        if (has(entities, match[1])) {\n          if (!silent) { state.pending += entities[match[1]]; }\n          state.pos += match[0].length;\n          return true;\n        }\n      }\n    }\n  }\n\n  if (!silent) { state.pending += '&'; }\n  state.pos++;\n  return true;\n};\n\n},{\"../common/entities\":1,\"../common/utils\":5}],43:[function(require,module,exports){\n// Proceess escaped chars and hardbreaks\n\n'use strict';\n\nvar ESCAPED = [];\n\nfor (var i = 0; i < 256; i++) { ESCAPED.push(0); }\n\n'\\\\!\"#$%&\\'()*+,./:;<=>?@[]^_`{|}~-'\n  .split('').forEach(function(ch) { ESCAPED[ch.charCodeAt(0)] = 1; });\n\n\nmodule.exports = function escape(state, silent) {\n  var ch, pos = state.pos, max = state.posMax;\n\n  if (state.src.charCodeAt(pos) !== 0x5C/* \\ */) { return false; }\n\n  pos++;\n\n  if (pos < max) {\n    ch = state.src.charCodeAt(pos);\n\n    if (ch < 256 && ESCAPED[ch] !== 0) {\n      if (!silent) { state.pending += state.src[pos]; }\n      state.pos += 2;\n      return true;\n    }\n\n    if (ch === 0x0A) {\n      if (!silent) {\n        state.push({\n          type: 'hardbreak',\n          level: state.level\n        });\n      }\n\n      pos++;\n      // skip leading whitespaces from next line\n      while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; }\n\n      state.pos = pos;\n      return true;\n    }\n  }\n\n  if (!silent) { state.pending += '\\\\'; }\n  state.pos++;\n  return true;\n};\n\n},{}],44:[function(require,module,exports){\n// Process html tags\n\n'use strict';\n\n\nvar HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE;\n\nvar COMMENT_RE = /^<!--[\\s\\S]*?-->$/;\n\nfunction isLetter(ch) {\n  /*eslint no-bitwise:0*/\n  var lc = ch | 0x20; // to lower case\n  return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */);\n}\n\n\nmodule.exports = function html_inline(state, silent) {\n  var ch, match, max, content, pos = state.pos;\n\n  if (!state.md.options.html) { return false; }\n\n  // Check start\n  max = state.posMax;\n  if (state.src.charCodeAt(pos) !== 0x3C/* < */ ||\n      pos + 2 >= max) {\n    return false;\n  }\n\n  // Quick fail on second char\n  ch = state.src.charCodeAt(pos + 1);\n  if (ch !== 0x21/* ! */ &&\n      ch !== 0x3F/* ? */ &&\n      ch !== 0x2F/* / */ &&\n      !isLetter(ch)) {\n    return false;\n  }\n\n  match = state.src.slice(pos).match(HTML_TAG_RE);\n  if (!match) { return false; }\n\n  content = state.src.slice(pos, pos + match[0].length);\n\n  // Additional check for comments\n  if (COMMENT_RE.test(content)) {\n    if (/(^>|^->|--|-$)/.test(content.slice(4, -3))) { return false; }\n  }\n\n  if (!silent) {\n    state.push({\n      type: 'html_inline',\n      content: content,\n      level: state.level\n    });\n  }\n  state.pos += match[0].length;\n  return true;\n};\n\n},{\"../common/html_re\":3}],45:[function(require,module,exports){\n// Process ![image](<src> \"title\")\n\n'use strict';\n\nvar parseLinkLabel       = require('../helpers/parse_link_label');\nvar parseLinkDestination = require('../helpers/parse_link_destination');\nvar parseLinkTitle       = require('../helpers/parse_link_title');\nvar normalizeReference   = require('../helpers/normalize_reference');\n\n\nmodule.exports = function image(state, silent) {\n  var code,\n      href,\n      label,\n      labelEnd,\n      labelStart,\n      pos,\n      ref,\n      res,\n      title,\n      tokens,\n      start,\n      oldPos = state.pos,\n      max = state.posMax;\n\n  if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; }\n  if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; }\n\n  labelStart = state.pos + 2;\n  labelEnd = parseLinkLabel(state, state.pos + 1, false);\n\n  // parser failed to find ']', so it's not a valid link\n  if (labelEnd < 0) { return false; }\n\n  pos = labelEnd + 1;\n  if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {\n    //\n    // Inline link\n    //\n\n    // [link](  <href>  \"title\"  )\n    //        ^^ skipping these spaces\n    pos++;\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n    if (pos >= max) { return false; }\n\n    // [link](  <href>  \"title\"  )\n    //          ^^^^^^ parsing link destination\n    start = pos;\n    res = parseLinkDestination(state.src, pos, state.posMax);\n    if (res.ok && state.md.inline.validateLink(res.str)) {\n      href = res.str;\n      pos = res.pos;\n    } else {\n      href = '';\n    }\n\n    // [link](  <href>  \"title\"  )\n    //                ^^ skipping these spaces\n    start = pos;\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n\n    // [link](  <href>  \"title\"  )\n    //                  ^^^^^^^ parsing link title\n    res = parseLinkTitle(state.src, pos, state.posMax);\n    if (pos < max && start !== pos && res.ok) {\n      title = res.str;\n      pos = res.pos;\n\n      // [link](  <href>  \"title\"  )\n      //                         ^^ skipping these spaces\n      for (; pos < max; pos++) {\n        code = state.src.charCodeAt(pos);\n        if (code !== 0x20 && code !== 0x0A) { break; }\n      }\n    } else {\n      title = '';\n    }\n\n    if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {\n      state.pos = oldPos;\n      return false;\n    }\n    pos++;\n  } else {\n    //\n    // Link reference\n    //\n    if (typeof state.env.references === 'undefined') { return false; }\n\n    // [foo]  [bar]\n    //      ^^ optional whitespace (can include newlines)\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n\n    if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {\n      start = pos + 1;\n      pos = parseLinkLabel(state, pos);\n      if (pos >= 0) {\n        label = state.src.slice(start, pos++);\n      } else {\n        pos = labelEnd + 1;\n      }\n    } else {\n      pos = labelEnd + 1;\n    }\n\n    // covers label === '' and label === undefined\n    // (collapsed reference link and shortcut reference link respectively)\n    if (!label) { label = state.src.slice(labelStart, labelEnd); }\n\n    ref = state.env.references[normalizeReference(label)];\n    if (!ref) {\n      state.pos = oldPos;\n      return false;\n    }\n    href = ref.href;\n    title = ref.title;\n  }\n\n  //\n  // We found the end of the link, and know for a fact it's a valid link;\n  // so all that's left to do is to call tokenizer.\n  //\n  if (!silent) {\n    state.pos = labelStart;\n    state.posMax = labelEnd;\n\n    var newState = new state.md.inline.State(\n      state.src.slice(labelStart, labelEnd),\n      state.md,\n      state.env,\n      tokens = []\n    );\n    newState.md.inline.tokenize(newState);\n\n    state.push({\n      type: 'image',\n      src: href,\n      title: title,\n      tokens: tokens,\n      level: state.level\n    });\n  }\n\n  state.pos = pos;\n  state.posMax = max;\n  return true;\n};\n\n},{\"../helpers/normalize_reference\":7,\"../helpers/parse_link_destination\":8,\"../helpers/parse_link_label\":9,\"../helpers/parse_link_title\":10}],46:[function(require,module,exports){\n// Process [link](<to> \"stuff\")\n\n'use strict';\n\nvar parseLinkLabel       = require('../helpers/parse_link_label');\nvar parseLinkDestination = require('../helpers/parse_link_destination');\nvar parseLinkTitle       = require('../helpers/parse_link_title');\nvar normalizeReference   = require('../helpers/normalize_reference');\n\n\nmodule.exports = function link(state, silent) {\n  var code,\n      href,\n      label,\n      labelEnd,\n      labelStart,\n      pos,\n      res,\n      ref,\n      title,\n      oldPos = state.pos,\n      max = state.posMax,\n      start = state.pos;\n\n  if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; }\n\n  labelStart = state.pos + 1;\n  labelEnd = parseLinkLabel(state, state.pos, true);\n\n  // parser failed to find ']', so it's not a valid link\n  if (labelEnd < 0) { return false; }\n\n  pos = labelEnd + 1;\n  if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {\n    //\n    // Inline link\n    //\n\n    // [link](  <href>  \"title\"  )\n    //        ^^ skipping these spaces\n    pos++;\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n    if (pos >= max) { return false; }\n\n    // [link](  <href>  \"title\"  )\n    //          ^^^^^^ parsing link destination\n    start = pos;\n    res = parseLinkDestination(state.src, pos, state.posMax);\n    if (res.ok && state.md.inline.validateLink(res.str)) {\n      href = res.str;\n      pos = res.pos;\n    } else {\n      href = '';\n    }\n\n    // [link](  <href>  \"title\"  )\n    //                ^^ skipping these spaces\n    start = pos;\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n\n    // [link](  <href>  \"title\"  )\n    //                  ^^^^^^^ parsing link title\n    res = parseLinkTitle(state.src, pos, state.posMax);\n    if (pos < max && start !== pos && res.ok) {\n      title = res.str;\n      pos = res.pos;\n\n      // [link](  <href>  \"title\"  )\n      //                         ^^ skipping these spaces\n      for (; pos < max; pos++) {\n        code = state.src.charCodeAt(pos);\n        if (code !== 0x20 && code !== 0x0A) { break; }\n      }\n    } else {\n      title = '';\n    }\n\n    if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {\n      state.pos = oldPos;\n      return false;\n    }\n    pos++;\n  } else {\n    //\n    // Link reference\n    //\n    if (typeof state.env.references === 'undefined') { return false; }\n\n    // [foo]  [bar]\n    //      ^^ optional whitespace (can include newlines)\n    for (; pos < max; pos++) {\n      code = state.src.charCodeAt(pos);\n      if (code !== 0x20 && code !== 0x0A) { break; }\n    }\n\n    if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {\n      start = pos + 1;\n      pos = parseLinkLabel(state, pos);\n      if (pos >= 0) {\n        label = state.src.slice(start, pos++);\n      } else {\n        pos = labelEnd + 1;\n      }\n    } else {\n      pos = labelEnd + 1;\n    }\n\n    // covers label === '' and label === undefined\n    // (collapsed reference link and shortcut reference link respectively)\n    if (!label) { label = state.src.slice(labelStart, labelEnd); }\n\n    ref = state.env.references[normalizeReference(label)];\n    if (!ref) {\n      state.pos = oldPos;\n      return false;\n    }\n    href = ref.href;\n    title = ref.title;\n  }\n\n  //\n  // We found the end of the link, and know for a fact it's a valid link;\n  // so all that's left to do is to call tokenizer.\n  //\n  if (!silent) {\n    state.pos = labelStart;\n    state.posMax = labelEnd;\n\n    state.push({\n      type: 'link_open',\n      href: href,\n      target: '',\n      title: title,\n      level: state.level++\n    });\n    state.md.inline.tokenize(state);\n    state.push({ type: 'link_close', level: --state.level });\n  }\n\n  state.pos = pos;\n  state.posMax = max;\n  return true;\n};\n\n},{\"../helpers/normalize_reference\":7,\"../helpers/parse_link_destination\":8,\"../helpers/parse_link_label\":9,\"../helpers/parse_link_title\":10}],47:[function(require,module,exports){\n// Proceess '\\n'\n\n'use strict';\n\nmodule.exports = function newline(state, silent) {\n  var pmax, max, pos = state.pos;\n\n  if (state.src.charCodeAt(pos) !== 0x0A/* \\n */) { return false; }\n\n  pmax = state.pending.length - 1;\n  max = state.posMax;\n\n  // '  \\n' -> hardbreak\n  // Lookup in pending chars is bad practice! Don't copy to other rules!\n  // Pending string is stored in concat mode, indexed lookups will cause\n  // convertion to flat mode.\n  if (!silent) {\n    if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) {\n      if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) {\n        state.pending = state.pending.replace(/ +$/, '');\n        state.push({\n          type: 'hardbreak',\n          level: state.level\n        });\n      } else {\n        state.pending = state.pending.slice(0, -1);\n        state.push({\n          type: 'softbreak',\n          level: state.level\n        });\n      }\n\n    } else {\n      state.push({\n        type: 'softbreak',\n        level: state.level\n      });\n    }\n  }\n\n  pos++;\n\n  // skip heading spaces for next line\n  while (pos < max && state.src.charCodeAt(pos) === 0x20) { pos++; }\n\n  state.pos = pos;\n  return true;\n};\n\n},{}],48:[function(require,module,exports){\n// Inline parser state\n\n'use strict';\n\n\nfunction StateInline(src, md, env, outTokens) {\n  this.src = src;\n  this.env = env;\n  this.md = md;\n  this.tokens = outTokens;\n\n  this.pos = 0;\n  this.posMax = this.src.length;\n  this.level = 0;\n  this.pending = '';\n  this.pendingLevel = 0;\n\n  this.cache = [];        // Stores { start: end } pairs. Useful for backtrack\n                          // optimization of pairs parse (emphasis, strikes).\n\n  // Link parser state vars\n\n  this.labelUnmatchedScopes = 0; // Track unpaired `[` for link labels\n                                 // (backtrack optimization)\n}\n\n\n// Flush pending text\n//\nStateInline.prototype.pushPending = function () {\n  this.tokens.push({\n    type: 'text',\n    content: this.pending,\n    level: this.pendingLevel\n  });\n  this.pending = '';\n};\n\n\n// Push new token to \"stream\".\n// If pending text exists - flush it as text token\n//\nStateInline.prototype.push = function (token) {\n  if (this.pending) {\n    this.pushPending();\n  }\n\n  this.tokens.push(token);\n  this.pendingLevel = this.level;\n};\n\n\n// Store value to cache.\n// !!! Implementation has parser-specific optimizations\n// !!! keys MUST be integer, >= 0; values MUST be integer, > 0\n//\nStateInline.prototype.cacheSet = function (key, val) {\n  for (var i = this.cache.length; i <= key; i++) {\n    this.cache.push(0);\n  }\n\n  this.cache[key] = val;\n};\n\n\n// Get cache value\n//\nStateInline.prototype.cacheGet = function (key) {\n  return key < this.cache.length ? this.cache[key] : 0;\n};\n\n\nmodule.exports = StateInline;\n\n},{}],49:[function(require,module,exports){\n'use strict';\n\n\n// parse sequence of markers,\n// \"start\" should point at a valid marker\nfunction scanDelims(state, start) {\n  var pos = start, lastChar, nextChar, count,\n      can_open = true,\n      can_close = true,\n      max = state.posMax,\n      marker = state.src.charCodeAt(start);\n\n  lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1;\n\n  while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }\n  if (pos >= max) { can_open = false; }\n  count = pos - start;\n\n  nextChar = pos < max ? state.src.charCodeAt(pos) : -1;\n\n  // check whitespace conditions\n  if (nextChar === 0x20 || nextChar === 0x0A) { can_open = false; }\n  if (lastChar === 0x20 || lastChar === 0x0A) { can_close = false; }\n\n  return {\n    can_open: can_open,\n    can_close: can_close,\n    delims: count\n  };\n}\n\n\nmodule.exports = function strikethrough(state, silent) {\n  var startCount,\n      count,\n      tagCount,\n      found,\n      stack,\n      res,\n      max = state.posMax,\n      start = state.pos,\n      marker = state.src.charCodeAt(start);\n\n  if (marker !== 0x7E/* ~ */) { return false; }\n  if (silent) { return false; } // don't run any pairs in validation mode\n\n  res = scanDelims(state, start);\n  startCount = res.delims;\n  if (!res.can_open) {\n    state.pos += startCount;\n    // Earlier we checked !silent, but this implementation does not need it\n    state.pending += state.src.slice(start, state.pos);\n    return true;\n  }\n\n  stack = Math.floor(startCount / 2);\n  if (stack <= 0) { return false; }\n  state.pos = start + startCount;\n\n  while (state.pos < max) {\n    if (state.src.charCodeAt(state.pos) === marker) {\n      res = scanDelims(state, state.pos);\n      count = res.delims;\n      tagCount = Math.floor(count / 2);\n      if (res.can_close) {\n        if (tagCount >= stack) {\n          state.pos += count - 2;\n          found = true;\n          break;\n        }\n        stack -= tagCount;\n        state.pos += count;\n        continue;\n      }\n\n      if (res.can_open) { stack += tagCount; }\n      state.pos += count;\n      continue;\n    }\n\n    state.md.inline.skipToken(state);\n  }\n\n  if (!found) {\n    // parser failed to find ending tag, so it's not valid emphasis\n    state.pos = start;\n    return false;\n  }\n\n  // found!\n  state.posMax = state.pos;\n  state.pos = start + 2;\n\n  // Earlier we checked !silent, but this implementation does not need it\n  state.push({ type: 's_open', level: state.level++ });\n  state.md.inline.tokenize(state);\n  state.push({ type: 's_close', level: --state.level });\n\n  state.pos = state.posMax + 2;\n  state.posMax = max;\n  return true;\n};\n\n},{}],50:[function(require,module,exports){\n// Skip text characters for text token, place those to pending buffer\n// and increment current pos\n\n'use strict';\n\n\n// Rule to skip pure text\n// '{}$%@~+=:' reserved for extentions\n\n// !, \", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, _, `, {, |, }, or ~\n\n// !!!! Don't confuse with \"Markdown ASCII Punctuation\" chars\n// http://spec.commonmark.org/0.15/#ascii-punctuation-character\nfunction isTerminatorChar(ch) {\n  switch (ch) {\n    case 0x0A/* \\n */:\n    case 0x21/* ! */:\n    case 0x23/* # */:\n    case 0x24/* $ */:\n    case 0x25/* % */:\n    case 0x26/* & */:\n    case 0x2A/* * */:\n    case 0x2B/* + */:\n    case 0x2D/* - */:\n    case 0x3A/* : */:\n    case 0x3C/* < */:\n    case 0x3D/* = */:\n    case 0x3E/* > */:\n    case 0x40/* @ */:\n    case 0x5B/* [ */:\n    case 0x5C/* \\ */:\n    case 0x5D/* ] */:\n    case 0x5E/* ^ */:\n    case 0x5F/* _ */:\n    case 0x60/* ` */:\n    case 0x7B/* { */:\n    case 0x7D/* } */:\n    case 0x7E/* ~ */:\n      return true;\n    default:\n      return false;\n  }\n}\n\nmodule.exports = function text(state, silent) {\n  var pos = state.pos;\n\n  while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) {\n    pos++;\n  }\n\n  if (pos === state.pos) { return false; }\n\n  if (!silent) { state.pending += state.src.slice(state.pos, pos); }\n\n  state.pos = pos;\n\n  return true;\n};\n\n},{}],51:[function(require,module,exports){\n(function (root, factory) {\n  if (typeof define === 'function' && define.amd) {\n    // AMD. Register as an anonymous module.\n    define([], function () {\n      return (root.returnExportsGlobal = factory());\n    });\n  } else if (typeof exports === 'object') {\n    // Node. Does not work with strict CommonJS, but\n    // only CommonJS-like enviroments that support module.exports,\n    // like Node.\n    module.exports = factory();\n  } else {\n    root['Autolinker'] = factory();\n  }\n}(this, function () {\n\n  /*!\n   * Autolinker.js\n   * 0.15.0\n   *\n   * Copyright(c) 2014 Gregory Jacobs <greg@greg-jacobs.com>\n   * MIT Licensed. http://www.opensource.org/licenses/mit-license.php\n   *\n   * https://github.com/gregjacobs/Autolinker.js\n   */\n  /**\n   * @class Autolinker\n   * @extends Object\n   *\n   * Utility class used to process a given string of text, and wrap the URLs, email addresses, and Twitter handles in\n   * the appropriate anchor (&lt;a&gt;) tags to turn them into links.\n   *\n   * Any of the configuration options may be provided in an Object (map) provided to the Autolinker constructor, which\n   * will configure how the {@link #link link()} method will process the links.\n   *\n   * For example:\n   *\n   *     var autolinker = new Autolinker( {\n   *         newWindow : false,\n   *         truncate  : 30\n   *     } );\n   *\n   *     var html = autolinker.link( \"Joe went to www.yahoo.com\" );\n   *     // produces: 'Joe went to <a href=\"http://www.yahoo.com\">yahoo.com</a>'\n   *\n   *\n   * The {@link #static-link static link()} method may also be used to inline options into a single call, which may\n   * be more convenient for one-off uses. For example:\n   *\n   *     var html = Autolinker.link( \"Joe went to www.yahoo.com\", {\n   *         newWindow : false,\n   *         truncate  : 30\n   *     } );\n   *     // produces: 'Joe went to <a href=\"http://www.yahoo.com\">yahoo.com</a>'\n   *\n   *\n   * ## Custom Replacements of Links\n   *\n   * If the configuration options do not provide enough flexibility, a {@link #replaceFn} may be provided to fully customize\n   * the output of Autolinker. This function is called once for each URL/Email/Twitter handle match that is encountered.\n   *\n   * For example:\n   *\n   *     var input = \"...\";  // string with URLs, Email Addresses, and Twitter Handles\n   *\n   *     var linkedText = Autolinker.link( input, {\n   *         replaceFn : function( autolinker, match ) {\n   *             console.log( \"href = \", match.getAnchorHref() );\n   *             console.log( \"text = \", match.getAnchorText() );\n   *\n   *             switch( match.getType() ) {\n   *                 case 'url' :\n   *                     console.log( \"url: \", match.getUrl() );\n   *\n   *                     if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {\n   *                         var tag = autolinker.getTagBuilder().build( match );  // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes\n   *                         tag.setAttr( 'rel', 'nofollow' );\n   *                         tag.addClass( 'external-link' );\n   *\n   *                         return tag;\n   *\n   *                     } else {\n   *                         return true;  // let Autolinker perform its normal anchor tag replacement\n   *                     }\n   *\n   *                 case 'email' :\n   *                     var email = match.getEmail();\n   *                     console.log( \"email: \", email );\n   *\n   *                     if( email === \"my@own.address\" ) {\n   *                         return false;  // don't auto-link this particular email address; leave as-is\n   *                     } else {\n   *                         return;  // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)\n   *                     }\n   *\n   *                 case 'twitter' :\n   *                     var twitterHandle = match.getTwitterHandle();\n   *                     console.log( twitterHandle );\n   *\n   *                     return '<a href=\"http://newplace.to.link.twitter.handles.to/\">' + twitterHandle + '</a>';\n   *             }\n   *         }\n   *     } );\n   *\n   *\n   * The function may return the following values:\n   *\n   * - `true` (Boolean): Allow Autolinker to replace the match as it normally would.\n   * - `false` (Boolean): Do not replace the current match at all - leave as-is.\n   * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for\n   *   the match.\n   * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text.\n   *\n   * @constructor\n   * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map).\n   */\n  var Autolinker = function( cfg ) {\n    Autolinker.Util.assign( this, cfg );  // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs.\n\n    this.matchValidator = new Autolinker.MatchValidator();\n  };\n\n\n  Autolinker.prototype = {\n    constructor : Autolinker,  // fix constructor property\n\n    /**\n     * @cfg {Boolean} urls\n     *\n     * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be.\n     */\n    urls : true,\n\n    /**\n     * @cfg {Boolean} email\n     *\n     * `true` if email addresses should be automatically linked, `false` if they should not be.\n     */\n    email : true,\n\n    /**\n     * @cfg {Boolean} twitter\n     *\n     * `true` if Twitter handles (\"@example\") should be automatically linked, `false` if they should not be.\n     */\n    twitter : true,\n\n    /**\n     * @cfg {Boolean} newWindow\n     *\n     * `true` if the links should open in a new window, `false` otherwise.\n     */\n    newWindow : true,\n\n    /**\n     * @cfg {Boolean} stripPrefix\n     *\n     * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of URL links' text,\n     * `false` otherwise.\n     */\n    stripPrefix : true,\n\n    /**\n     * @cfg {Number} truncate\n     *\n     * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of\n     * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by\n     * adding a two period ellipsis ('..') to the end of the string.\n     *\n     * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look\n     * something like this: 'yahoo.com/some/long/pat..'\n     */\n\n    /**\n     * @cfg {String} className\n     *\n     * A CSS class name to add to the generated links. This class will be added to all links, as well as this class\n     * plus url/email/twitter suffixes for styling url/email/twitter links differently.\n     *\n     * For example, if this config is provided as \"myLink\", then:\n     *\n     * - URL links will have the CSS classes: \"myLink myLink-url\"\n     * - Email links will have the CSS classes: \"myLink myLink-email\", and\n     * - Twitter links will have the CSS classes: \"myLink myLink-twitter\"\n     */\n    className : \"\",\n\n    /**\n     * @cfg {Function} replaceFn\n     *\n     * A function to individually process each URL/Email/Twitter match found in the input string.\n     *\n     * See the class's description for usage.\n     *\n     * This function is called with the following parameters:\n     *\n     * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such\n     *   as the instance's {@link #getTagBuilder tag builder}).\n     * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the\n     *   {@link Autolinker.match.Url URL}/{@link Autolinker.match.Email email}/{@link Autolinker.match.Twitter Twitter}\n     *   match that the `replaceFn` is currently processing.\n     */\n\n\n    /**\n     * @private\n     * @property {RegExp} htmlCharacterEntitiesRegex\n     *\n     * The regular expression that matches common HTML character entities.\n     *\n     * Ignoring &amp; as it could be part of a query string -- handling it separately.\n     */\n    htmlCharacterEntitiesRegex: /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;)/gi,\n\n    /**\n     * @private\n     * @property {RegExp} matcherRegex\n     *\n     * The regular expression that matches URLs, email addresses, and Twitter handles.\n     *\n     * This regular expression has the following capturing groups:\n     *\n     * 1. Group that is used to determine if there is a Twitter handle match (i.e. \\@someTwitterUser). Simply check for its\n     *    existence to determine if there is a Twitter handle match. The next couple of capturing groups give information\n     *    about the Twitter handle match.\n     * 2. The whitespace character before the \\@sign in a Twitter handle. This is needed because there are no lookbehinds in\n     *    JS regular expressions, and can be used to reconstruct the original string in a replace().\n     * 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'.\n     * 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full\n     *    address. Ex: 'me@my.com'\n     * 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'.\n     *    This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor\n     * 6. Group that matches a protocol URL (i.e. 'http://google.com'). This is used to match protocol URLs with just a single\n     *    word, like 'http://localhost', where we won't double check that the domain name has at least one '.' in it.\n     * 7. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a\n     *    protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match\n     *    or the // was in a string we don't want to auto-link.\n     * 8. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a\n     *    protocol-relative match. See #6 for more info.\n     */\n    matcherRegex : (function() {\n      var twitterRegex = /(^|[^\\w])@(\\w{1,15})/,              // For matching a twitter handle. Ex: @gregory_jacobs\n\n          emailRegex = /(?:[\\-;:&=\\+\\$,\\w\\.]+@)/,             // something@ for email addresses (a.k.a. local-part)\n\n          protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\\/\\/)(?!\\d+\\/?)(?:\\/\\/)?)/,  // match protocol, allow in format \"http://\" or \"mailto:\". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match \"link:\"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex)\n          wwwRegex = /(?:www\\.)/,                             // starting with 'www.'\n          domainNameRegex = /[A-Za-z0-9\\.\\-]*[A-Za-z0-9\\-]/,  // anything looking at all like a domain, non-unicode domains, not ending in a period\n          tldRegex = /\\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\\b/,   // match our known top level domains (TLDs)\n\n          // Allow optional path, query string, and hash anchor, not ending in the following characters: \"?!:,.;\"\n          // http://blog.codinghorror.com/the-problem-with-urls/\n          urlSuffixRegex = /[\\-A-Za-z0-9+&@#\\/%=~_()|'$*\\[\\]?!:,.;]*[\\-A-Za-z0-9+&@#\\/%=~_()|'$*\\[\\]]/;\n\n      return new RegExp( [\n        '(',  // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace()\n          // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and\n          // *** Capturing group $3, which matches the actual twitter handle\n          twitterRegex.source,\n        ')',\n\n        '|',\n\n        '(',  // *** Capturing group $4, which is used to determine an email match\n          emailRegex.source,\n          domainNameRegex.source,\n          tldRegex.source,\n        ')',\n\n        '|',\n\n        '(',  // *** Capturing group $5, which is used to match a URL\n          '(?:', // parens to cover match for protocol (optional), and domain\n            '(',  // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com)\n              protocolRegex.source,\n              domainNameRegex.source,\n            ')',\n\n            '|',\n\n            '(?:',  // non-capturing paren for a 'www.' prefixed url (ex: www.google.com)\n              '(.?//)?',  // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character\n              wwwRegex.source,\n              domainNameRegex.source,\n            ')',\n\n            '|',\n\n            '(?:',  // non-capturing paren for known a TLD url (ex: google.com)\n              '(.?//)?',  // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character\n              domainNameRegex.source,\n              tldRegex.source,\n            ')',\n          ')',\n\n          '(?:' + urlSuffixRegex.source + ')?',  // match for path, query string, and/or hash anchor - optional\n        ')'\n      ].join( \"\" ), 'gi' );\n    } )(),\n\n    /**\n     * @private\n     * @property {RegExp} charBeforeProtocolRelMatchRegex\n     *\n     * The regular expression used to retrieve the character before a protocol-relative URL match.\n     *\n     * This is used in conjunction with the {@link #matcherRegex}, which needs to grab the character before a protocol-relative\n     * '//' due to the lack of a negative look-behind in JavaScript regular expressions. The character before the match is stripped\n     * from the URL.\n     */\n    charBeforeProtocolRelMatchRegex : /^(.)?\\/\\//,\n\n    /**\n     * @private\n     * @property {Autolinker.MatchValidator} matchValidator\n     *\n     * The MatchValidator object, used to filter out any false positives from the {@link #matcherRegex}. See\n     * {@link Autolinker.MatchValidator} for details.\n     */\n\n    /**\n     * @private\n     * @property {Autolinker.HtmlParser} htmlParser\n     *\n     * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated\n     * in the {@link #getHtmlParser} method.\n     */\n\n    /**\n     * @private\n     * @property {Autolinker.AnchorTagBuilder} tagBuilder\n     *\n     * The AnchorTagBuilder instance used to build the URL/email/Twitter replacement anchor tags. This is lazily instantiated\n     * in the {@link #getTagBuilder} method.\n     */\n\n\n    /**\n     * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML.\n     * Does not link URLs found within HTML tags.\n     *\n     * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result\n     * will be `You should go to &lt;a href=\"http://www.yahoo.com\"&gt;http://www.yahoo.com&lt;/a&gt;`\n     *\n     * This method finds the text around any HTML elements in the input `textOrHtml`, which will be the text that is processed.\n     * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor (&lt;a&gt;) tags.\n     *\n     * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within (depending on if\n     *   the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled).\n     * @return {String} The HTML, with URLs/emails/Twitter handles automatically linked.\n     */\n    link : function( textOrHtml ) {\n      var me = this,  // for closure\n          htmlParser = this.getHtmlParser(),\n          htmlCharacterEntitiesRegex = this.htmlCharacterEntitiesRegex,\n          anchorTagStackCount = 0,  // used to only process text around anchor tags, and any inner text/html they may have\n          resultHtml = [];\n\n      htmlParser.parse( textOrHtml, {\n        // Process HTML nodes in the input `textOrHtml`\n        processHtmlNode : function( tagText, tagName, isClosingTag ) {\n          if( tagName === 'a' ) {\n            if( !isClosingTag ) {  // it's the start <a> tag\n              anchorTagStackCount++;\n            } else {   // it's the end </a> tag\n              anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 );  // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0\n            }\n          }\n          resultHtml.push( tagText );  // now add the text of the tag itself verbatim\n        },\n\n        // Process text nodes in the input `textOrHtml`\n        processTextNode : function( text ) {\n          if( anchorTagStackCount === 0 ) {\n            // If we're not within an <a> tag, process the text node\n            var unescapedText = Autolinker.Util.splitAndCapture( text, htmlCharacterEntitiesRegex );  // split at HTML entities, but include the HTML entities in the results array\n\n            for ( var i = 0, len = unescapedText.length; i < len; i++ ) {\n              var textToProcess = unescapedText[ i ],\n                  processedTextNode = me.processTextNode( textToProcess );\n\n              resultHtml.push( processedTextNode );\n            }\n\n          } else {\n            // `text` is within an <a> tag, simply append the text - we do not want to autolink anything\n            // already within an <a>...</a> tag\n            resultHtml.push( text );\n          }\n        }\n      } );\n\n      return resultHtml.join( \"\" );\n    },\n\n\n    /**\n     * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance.\n     *\n     * @protected\n     * @return {Autolinker.HtmlParser}\n     */\n    getHtmlParser : function() {\n      var htmlParser = this.htmlParser;\n\n      if( !htmlParser ) {\n        htmlParser = this.htmlParser = new Autolinker.HtmlParser();\n      }\n\n      return htmlParser;\n    },\n\n\n    /**\n     * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it\n     * if it does not yet exist.\n     *\n     * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that\n     * Autolinker would normally generate, and then allow for modifications before returning it. For example:\n     *\n     *     var html = Autolinker.link( \"Test google.com\", {\n     *         replaceFn : function( autolinker, match ) {\n     *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance\n     *             tag.setAttr( 'rel', 'nofollow' );\n     *\n     *             return tag;\n     *         }\n     *     } );\n     *\n     *     // generated html:\n     *     //   Test <a href=\"http://google.com\" target=\"_blank\" rel=\"nofollow\">google.com</a>\n     *\n     * @return {Autolinker.AnchorTagBuilder}\n     */\n    getTagBuilder : function() {\n      var tagBuilder = this.tagBuilder;\n\n      if( !tagBuilder ) {\n        tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( {\n          newWindow   : this.newWindow,\n          truncate    : this.truncate,\n          className   : this.className\n        } );\n      }\n\n      return tagBuilder;\n    },\n\n\n    /**\n     * Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with\n     * anchor tags.\n     *\n     * @private\n     * @param {String} text The text to auto-link.\n     * @return {String} The text with anchor tags auto-filled.\n     */\n    processTextNode : function( text ) {\n      var me = this;  // for closure\n\n      return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 ) {\n        var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 );  // match description object\n\n        // Return out with no changes for match types that are disabled (url, email, twitter), or for matches that are\n        // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS).\n        if( !matchDescObj ) {\n          return matchStr;\n\n        } else {\n          // Generate the replacement text for the match\n          var matchReturnVal = me.createMatchReturnVal( matchDescObj.match, matchDescObj.matchStr );\n          return matchDescObj.prefixStr + matchReturnVal + matchDescObj.suffixStr;\n        }\n      } );\n    },\n\n\n    /**\n     * Processes a candidate match from the {@link #matcherRegex}.\n     *\n     * Not all matches found by the regex are actual URL/email/Twitter matches, as determined by the {@link #matchValidator}. In\n     * this case, the method returns `null`. Otherwise, a valid Object with `prefixStr`, `match`, and `suffixStr` is returned.\n     *\n     * @private\n     * @param {String} matchStr The full match that was found by the {@link #matcherRegex}.\n     * @param {String} twitterMatch The matched text of a Twitter handle, if the match is a Twitter match.\n     * @param {String} twitterHandlePrefixWhitespaceChar The whitespace char before the @ sign in a Twitter handle match. This\n     *   is needed because of no lookbehinds in JS regexes, and is need to re-include the character for the anchor tag replacement.\n     * @param {String} twitterHandle The actual Twitter user (i.e the word after the @ sign in a Twitter match).\n     * @param {String} emailAddressMatch The matched email address for an email address match.\n     * @param {String} urlMatch The matched URL string for a URL match.\n     * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match\n     *   something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it.\n     * @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative match from a 'www' url, with the character that\n     *   comes before the '//'.\n     * @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative match from a TLD (top level domain) match, with\n     *   the character that comes before the '//'.\n     *\n     * @return {Object} A \"match description object\". This will be `null` if the match was invalid, or if a match type is disabled.\n     *   Otherwise, this will be an Object (map) with the following properties:\n     * @return {String} return.prefixStr The char(s) that should be prepended to the replacement string. These are char(s) that\n     *   were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into\n     *   the replacement stream.\n     * @return {String} return.suffixStr The char(s) that should be appended to the replacement string. These are char(s) that\n     *   were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into\n     *   the replacement stream.\n     * @return {String} return.matchStr The `matchStr`, fixed up to remove characters that are no longer needed (which have been\n     *   added to `prefixStr` and `suffixStr`).\n     * @return {Autolinker.match.Match} return.match The Match object that represents the match that was found.\n     */\n    processCandidateMatch : function(\n      matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle,\n      emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch, tldProtocolRelativeMatch\n    ) {\n      var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch,\n          match,  // Will be an Autolinker.match.Match object\n\n          prefixStr = \"\",       // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match\n          suffixStr = \"\";       // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked.\n\n\n      // Return out with `null` for match types that are disabled (url, email, twitter), or for matches that are\n      // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS).\n      if(\n        ( twitterMatch && !this.twitter ) || ( emailAddressMatch && !this.email ) || ( urlMatch && !this.urls ) ||\n        !this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch )\n      ) {\n        return null;\n      }\n\n      // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis\n      // in the match itself.\n      if( this.matchHasUnbalancedClosingParen( matchStr ) ) {\n        matchStr = matchStr.substr( 0, matchStr.length - 1 );  // remove the trailing \")\"\n        suffixStr = \")\";  // this will be added after the generated <a> tag\n      }\n\n\n      if( emailAddressMatch ) {\n        match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } );\n\n      } else if( twitterMatch ) {\n        // fix up the `matchStr` if there was a preceding whitespace char, which was needed to determine the match\n        // itself (since there are no look-behinds in JS regexes)\n        if( twitterHandlePrefixWhitespaceChar ) {\n          prefixStr = twitterHandlePrefixWhitespaceChar;\n          matchStr = matchStr.slice( 1 );  // remove the prefixed whitespace char from the match\n        }\n        match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } );\n\n      } else {  // url match\n        // If it's a protocol-relative '//' match, remove the character before the '//' (which the matcherRegex needed\n        // to match due to the lack of a negative look-behind in JavaScript regular expressions)\n        if( protocolRelativeMatch ) {\n          var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || \"\";\n\n          if( charBeforeMatch ) {  // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes)\n            prefixStr = charBeforeMatch;\n            matchStr = matchStr.slice( 1 );  // remove the prefixed char from the match\n          }\n        }\n\n        match = new Autolinker.match.Url( {\n          matchedText : matchStr,\n          url : matchStr,\n          protocolUrlMatch : !!protocolUrlMatch,\n          protocolRelativeMatch : !!protocolRelativeMatch,\n          stripPrefix : this.stripPrefix\n        } );\n      }\n\n      return {\n        prefixStr : prefixStr,\n        suffixStr : suffixStr,\n        matchStr  : matchStr,\n        match     : match\n      };\n    },\n\n\n    /**\n     * Determines if a match found has an unmatched closing parenthesis. If so, this parenthesis will be removed\n     * from the match itself, and appended after the generated anchor tag in {@link #processTextNode}.\n     *\n     * A match may have an extra closing parenthesis at the end of the match because the regular expression must include parenthesis\n     * for URLs such as \"wikipedia.com/something_(disambiguation)\", which should be auto-linked.\n     *\n     * However, an extra parenthesis *will* be included when the URL itself is wrapped in parenthesis, such as in the case of\n     * \"(wikipedia.com/something_(disambiguation))\". In this case, the last closing parenthesis should *not* be part of the URL\n     * itself, and this method will return `true`.\n     *\n     * @private\n     * @param {String} matchStr The full match string from the {@link #matcherRegex}.\n     * @return {Boolean} `true` if there is an unbalanced closing parenthesis at the end of the `matchStr`, `false` otherwise.\n     */\n    matchHasUnbalancedClosingParen : function( matchStr ) {\n      var lastChar = matchStr.charAt( matchStr.length - 1 );\n\n      if( lastChar === ')' ) {\n        var openParensMatch = matchStr.match( /\\(/g ),\n            closeParensMatch = matchStr.match( /\\)/g ),\n            numOpenParens = ( openParensMatch && openParensMatch.length ) || 0,\n            numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0;\n\n        if( numOpenParens < numCloseParens ) {\n          return true;\n        }\n      }\n\n      return false;\n    },\n\n\n    /**\n     * Creates the return string value for a given match in the input string, for the {@link #processTextNode} method.\n     *\n     * This method handles the {@link #replaceFn}, if one was provided.\n     *\n     * @private\n     * @param {Autolinker.match.Match} match The Match object that represents the match.\n     * @param {String} matchStr The original match string, after having been preprocessed to fix match edge cases (see\n     *   the `prefixStr` and `suffixStr` vars in {@link #processTextNode}.\n     * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but\n     *   may be the `matchStr` itself if the match is not to be replaced.\n     */\n    createMatchReturnVal : function( match, matchStr ) {\n      // Handle a custom `replaceFn` being provided\n      var replaceFnResult;\n      if( this.replaceFn ) {\n        replaceFnResult = this.replaceFn.call( this, this, match );  // Autolinker instance is the context, and the first arg\n      }\n\n      if( typeof replaceFnResult === 'string' ) {\n        return replaceFnResult;  // `replaceFn` returned a string, use that\n\n      } else if( replaceFnResult === false ) {\n        return matchStr;  // no replacement for the match\n\n      } else if( replaceFnResult instanceof Autolinker.HtmlTag ) {\n        return replaceFnResult.toString();\n\n      } else {  // replaceFnResult === true, or no/unknown return value from function\n        // Perform Autolinker's default anchor tag generation\n        var tagBuilder = this.getTagBuilder(),\n            anchorTag = tagBuilder.build( match );  // returns an Autolinker.HtmlTag instance\n\n        return anchorTag.toString();\n      }\n    }\n\n  };\n\n\n  /**\n   * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML.\n   * Does not link URLs found within HTML tags.\n   *\n   * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result\n   * will be `You should go to &lt;a href=\"http://www.yahoo.com\"&gt;http://www.yahoo.com&lt;/a&gt;`\n   *\n   * Example:\n   *\n   *     var linkedText = Autolinker.link( \"Go to google.com\", { newWindow: false } );\n   *     // Produces: \"Go to <a href=\"http://google.com\">google.com</a>\"\n   *\n   * @static\n   * @param {String} textOrHtml The HTML or text to find URLs, email addresses, and Twitter handles within (depending on if\n   *   the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled).\n   * @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map).\n   *   See the class description for an example call.\n   * @return {String} The HTML text, with URLs automatically linked\n   */\n  Autolinker.link = function( textOrHtml, options ) {\n    var autolinker = new Autolinker( options );\n    return autolinker.link( textOrHtml );\n  };\n\n\n  // Namespace for `match` classes\n  Autolinker.match = {};\n  /*global Autolinker */\n  /*jshint eqnull:true, boss:true */\n  /**\n   * @class Autolinker.Util\n   * @singleton\n   *\n   * A few utility methods for Autolinker.\n   */\n  Autolinker.Util = {\n\n    /**\n     * @property {Function} abstractMethod\n     *\n     * A function object which represents an abstract method.\n     */\n    abstractMethod : function() { throw \"abstract\"; },\n\n\n    /**\n     * Assigns (shallow copies) the properties of `src` onto `dest`.\n     *\n     * @param {Object} dest The destination object.\n     * @param {Object} src The source object.\n     * @return {Object} The destination object (`dest`)\n     */\n    assign : function( dest, src ) {\n      for( var prop in src ) {\n        if( src.hasOwnProperty( prop ) ) {\n          dest[ prop ] = src[ prop ];\n        }\n      }\n\n      return dest;\n    },\n\n\n    /**\n     * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype.\n     *\n     * @param {Function} superclass The constructor function for the superclass.\n     * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the\n     *   special property `constructor`, which will be used as the new subclass's constructor function.\n     * @return {Function} The new subclass function.\n     */\n    extend : function( superclass, protoProps ) {\n      var superclassProto = superclass.prototype;\n\n      var F = function() {};\n      F.prototype = superclassProto;\n\n      var subclass;\n      if( protoProps.hasOwnProperty( 'constructor' ) ) {\n        subclass = protoProps.constructor;\n      } else {\n        subclass = function() { superclassProto.constructor.apply( this, arguments ); };\n      }\n\n      var subclassProto = subclass.prototype = new F();  // set up prototype chain\n      subclassProto.constructor = subclass;  // fix constructor property\n      subclassProto.superclass = superclassProto;\n\n      delete protoProps.constructor;  // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there\n      Autolinker.Util.assign( subclassProto, protoProps );\n\n      return subclass;\n    },\n\n\n    /**\n     * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the\n     * end of the string (by default, two periods: '..'). If the `str` length does not exceed\n     * `len`, the string will be returned unchanged.\n     *\n     * @param {String} str The string to truncate and add an ellipsis to.\n     * @param {Number} truncateLen The length to truncate the string at.\n     * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str`\n     *   when truncated. Defaults to '..'\n     */\n    ellipsis : function( str, truncateLen, ellipsisChars ) {\n      if( str.length > truncateLen ) {\n        ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars;\n        str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars;\n      }\n      return str;\n    },\n\n\n    /**\n     * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below).\n     *\n     * @param {Array} arr The array to find an element of.\n     * @param {*} element The element to find in the array, and return the index of.\n     * @return {Number} The index of the `element`, or -1 if it was not found.\n     */\n    indexOf : function( arr, element ) {\n      if( Array.prototype.indexOf ) {\n        return arr.indexOf( element );\n\n      } else {\n        for( var i = 0, len = arr.length; i < len; i++ ) {\n          if( arr[ i ] === element ) return i;\n        }\n        return -1;\n      }\n    },\n\n\n\n    /**\n     * Performs the functionality of what modern browsers do when `String.prototype.split()` is called\n     * with a regular expression that contains capturing parenthesis.\n     *\n     * For example:\n     *\n     *     // Modern browsers:\n     *     \"a,b,c\".split( /(,)/ );  // --> [ 'a', ',', 'b', ',', 'c' ]\n     *\n     *     // Old IE (including IE8):\n     *     \"a,b,c\".split( /(,)/ );  // --> [ 'a', 'b', 'c' ]\n     *\n     * This method emulates the functionality of modern browsers for the old IE case.\n     *\n     * @param {String} str The string to split.\n     * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting\n     *   character(s) will be spliced into the array, as in the \"modern browsers\" example in the\n     *   description of this method.\n     *   Note #1: the supplied regular expression **must** have the 'g' flag specified.\n     *   Note #2: for simplicity's sake, the regular expression does not need\n     *   to contain capturing parenthesis - it will be assumed that any match has them.\n     * @return {String[]} The split array of strings, with the splitting character(s) included.\n     */\n    splitAndCapture : function( str, splitRegex ) {\n      if( !splitRegex.global ) throw new Error( \"`splitRegex` must have the 'g' flag set\" );\n\n      var result = [],\n          lastIdx = 0,\n          match;\n\n      while( match = splitRegex.exec( str ) ) {\n        result.push( str.substring( lastIdx, match.index ) );\n        result.push( match[ 0 ] );  // push the splitting char(s)\n\n        lastIdx = match.index + match[ 0 ].length;\n      }\n      result.push( str.substring( lastIdx ) );\n\n      return result;\n    }\n\n  };\n  /*global Autolinker */\n  /**\n   * @private\n   * @class Autolinker.HtmlParser\n   * @extends Object\n   *\n   * An HTML parser implementation which simply walks an HTML string and calls the provided visitor functions to process\n   * HTML and text nodes.\n   *\n   * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, basically ignoring HTML tags.\n   */\n  Autolinker.HtmlParser = Autolinker.Util.extend( Object, {\n\n    /**\n     * @private\n     * @property {RegExp} htmlRegex\n     *\n     * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and\n     * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html.\n     *\n     * Capturing groups:\n     *\n     * 1. The \"!DOCTYPE\" tag name, if a tag is a &lt;!DOCTYPE&gt; tag.\n     * 2. If it is an end tag, this group will have the '/'.\n     * 3. The tag name for all tags (other than the &lt;!DOCTYPE&gt; tag)\n     */\n    htmlRegex : (function() {\n      var tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/,\n          attrNameRegex = /[^\\s\\0\"'>\\/=\\x01-\\x1F\\x7F]+/,   // the unicode range accounts for excluding control chars, and the delete char\n          attrValueRegex = /(?:\".*?\"|'.*?'|[^'\"=<>`\\s]+)/, // double quoted, single quoted, or unquoted attribute values\n          nameEqualsValueRegex = attrNameRegex.source + '(?:\\\\s*=\\\\s*' + attrValueRegex.source + ')?';  // optional '=[value]'\n\n      return new RegExp( [\n        // for <!DOCTYPE> tag. Ex: <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">)\n        '(?:',\n          '<(!DOCTYPE)',  // *** Capturing Group 1 - If it's a doctype tag\n\n            // Zero or more attributes following the tag name\n            '(?:',\n              '\\\\s+',  // one or more whitespace chars before an attribute\n\n              // Either:\n              // A. attr=\"value\", or\n              // B. \"value\" alone (To cover example doctype tag: <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">)\n              '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')',\n            ')*',\n          '>',\n        ')',\n\n        '|',\n\n        // All other HTML tags (i.e. tags that are not <!DOCTYPE>)\n        '(?:',\n          '<(/)?',  // Beginning of a tag. Either '<' for a start tag, or '</' for an end tag.\n                    // *** Capturing Group 2: The slash or an empty string. Slash ('/') for end tag, empty string for start or self-closing tag.\n\n            // *** Capturing Group 3 - The tag name\n            '(' + tagNameRegex.source + ')',\n\n            // Zero or more attributes following the tag name\n            '(?:',\n              '\\\\s+',                // one or more whitespace chars before an attribute\n              nameEqualsValueRegex,  // attr=\"value\" (with optional =\"value\" part)\n            ')*',\n\n            '\\\\s*/?',  // any trailing spaces and optional '/' before the closing '>'\n          '>',\n        ')'\n      ].join( \"\" ), 'gi' );\n    } )(),\n\n\n    /**\n     * Walks an HTML string, calling the `options.processHtmlNode` function for each HTML tag that is encountered, and calling\n     * the `options.processTextNode` function when each text around HTML tags is encountered.\n     *\n     * @param {String} html The HTML to parse.\n     * @param {Object} [options] An Object (map) which may contain the following properties:\n     *\n     * @param {Function} [options.processHtmlNode] A visitor function which allows processing of an encountered HTML node.\n     *   This function is called with the following arguments:\n     * @param {String} [options.processHtmlNode.tagText] The HTML tag text that was found.\n     * @param {String} [options.processHtmlNode.tagName] The tag name for the HTML tag that was found. Ex: 'a' for an anchor tag.\n     * @param {String} [options.processHtmlNode.isClosingTag] `true` if the tag is a closing tag (ex: &lt;/a&gt;), `false` otherwise.\n     *\n     * @param {Function} [options.processTextNode] A visitor function which allows processing of an encountered text node.\n     *   This function is called with the following arguments:\n     * @param {String} [options.processTextNode.text] The text node that was matched.\n     */\n    parse : function( html, options ) {\n      options = options || {};\n\n      var processHtmlNodeVisitor = options.processHtmlNode || function() {},\n          processTextNodeVisitor = options.processTextNode || function() {},\n          htmlRegex = this.htmlRegex,\n          currentResult,\n          lastIndex = 0;\n\n      // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them,\n      // wrapping the URLs in anchor tags\n      while( ( currentResult = htmlRegex.exec( html ) ) !== null ) {\n        var tagText = currentResult[ 0 ],\n            tagName = currentResult[ 1 ] || currentResult[ 3 ],  // The <!DOCTYPE> tag (ex: \"!DOCTYPE\"), or another tag (ex: \"a\")\n            isClosingTag = !!currentResult[ 2 ],\n            inBetweenTagsText = html.substring( lastIndex, currentResult.index );\n\n        if( inBetweenTagsText ) {\n          processTextNodeVisitor( inBetweenTagsText );\n        }\n\n        processHtmlNodeVisitor( tagText, tagName.toLowerCase(), isClosingTag );\n\n        lastIndex = currentResult.index + tagText.length;\n      }\n\n      // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements.\n      if( lastIndex < html.length ) {\n        var text = html.substring( lastIndex );\n\n        if( text ) {\n          processTextNodeVisitor( text );\n        }\n      }\n    }\n\n  } );\n  /*global Autolinker */\n  /*jshint boss:true */\n  /**\n   * @class Autolinker.HtmlTag\n   * @extends Object\n   *\n   * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.\n   *\n   * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use\n   * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.\n   *\n   * ## Examples\n   *\n   * Example instantiation:\n   *\n   *     var tag = new Autolinker.HtmlTag( {\n   *         tagName : 'a',\n   *         attrs   : { 'href': 'http://google.com', 'class': 'external-link' },\n   *         innerHtml : 'Google'\n   *     } );\n   *\n   *     tag.toString();  // <a href=\"http://google.com\" class=\"external-link\">Google</a>\n   *\n   *     // Individual accessor methods\n   *     tag.getTagName();                 // 'a'\n   *     tag.getAttr( 'href' );            // 'http://google.com'\n   *     tag.hasClass( 'external-link' );  // true\n   *\n   *\n   * Using mutator methods (which may be used in combination with instantiation config properties):\n   *\n   *     var tag = new Autolinker.HtmlTag();\n   *     tag.setTagName( 'a' );\n   *     tag.setAttr( 'href', 'http://google.com' );\n   *     tag.addClass( 'external-link' );\n   *     tag.setInnerHtml( 'Google' );\n   *\n   *     tag.getTagName();                 // 'a'\n   *     tag.getAttr( 'href' );            // 'http://google.com'\n   *     tag.hasClass( 'external-link' );  // true\n   *\n   *     tag.toString();  // <a href=\"http://google.com\" class=\"external-link\">Google</a>\n   *\n   *\n   * ## Example use within a {@link Autolinker#replaceFn replaceFn}\n   *\n   *     var html = Autolinker.link( \"Test google.com\", {\n   *         replaceFn : function( autolinker, match ) {\n   *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text\n   *             tag.setAttr( 'rel', 'nofollow' );\n   *\n   *             return tag;\n   *         }\n   *     } );\n   *\n   *     // generated html:\n   *     //   Test <a href=\"http://google.com\" target=\"_blank\" rel=\"nofollow\">google.com</a>\n   *\n   *\n   * ## Example use with a new tag for the replacement\n   *\n   *     var html = Autolinker.link( \"Test google.com\", {\n   *         replaceFn : function( autolinker, match ) {\n   *             var tag = new Autolinker.HtmlTag( {\n   *                 tagName : 'button',\n   *                 attrs   : { 'title': 'Load URL: ' + match.getAnchorHref() },\n   *                 innerHtml : 'Load URL: ' + match.getAnchorText()\n   *             } );\n   *\n   *             return tag;\n   *         }\n   *     } );\n   *\n   *     // generated html:\n   *     //   Test <button title=\"Load URL: http://google.com\">Load URL: google.com</button>\n   */\n  Autolinker.HtmlTag = Autolinker.Util.extend( Object, {\n\n    /**\n     * @cfg {String} tagName\n     *\n     * The tag name. Ex: 'a', 'button', etc.\n     *\n     * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toString}\n     * is executed.\n     */\n\n    /**\n     * @cfg {Object.<String, String>} attrs\n     *\n     * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the\n     * values are the attribute values.\n     */\n\n    /**\n     * @cfg {String} innerHtml\n     *\n     * The inner HTML for the tag.\n     *\n     * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym\n     * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML}\n     * if you prefer, but this one is recommended.\n     */\n\n    /**\n     * @cfg {String} innerHTML\n     *\n     * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version\n     * for acronym names.\n     */\n\n\n    /**\n     * @protected\n     * @property {RegExp} whitespaceRegex\n     *\n     * Regular expression used to match whitespace in a string of CSS classes.\n     */\n    whitespaceRegex : /\\s+/,\n\n\n    /**\n     * @constructor\n     * @param {Object} [cfg] The configuration properties for this class, in an Object (map)\n     */\n    constructor : function( cfg ) {\n      Autolinker.Util.assign( this, cfg );\n\n      this.innerHtml = this.innerHtml || this.innerHTML;  // accept either the camelCased form or the fully capitalized acronym\n    },\n\n\n    /**\n     * Sets the tag name that will be used to generate the tag with.\n     *\n     * @param {String} tagName\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    setTagName : function( tagName ) {\n      this.tagName = tagName;\n      return this;\n    },\n\n\n    /**\n     * Retrieves the tag name.\n     *\n     * @return {String}\n     */\n    getTagName : function() {\n      return this.tagName || \"\";\n    },\n\n\n    /**\n     * Sets an attribute on the HtmlTag.\n     *\n     * @param {String} attrName The attribute name to set.\n     * @param {String} attrValue The attribute value to set.\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    setAttr : function( attrName, attrValue ) {\n      var tagAttrs = this.getAttrs();\n      tagAttrs[ attrName ] = attrValue;\n\n      return this;\n    },\n\n\n    /**\n     * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.\n     *\n     * @param {String} name The attribute name to retrieve.\n     * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.\n     */\n    getAttr : function( attrName ) {\n      return this.getAttrs()[ attrName ];\n    },\n\n\n    /**\n     * Sets one or more attributes on the HtmlTag.\n     *\n     * @param {Object.<String, String>} attrs A key/value Object (map) of the attributes to set.\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    setAttrs : function( attrs ) {\n      var tagAttrs = this.getAttrs();\n      Autolinker.Util.assign( tagAttrs, attrs );\n\n      return this;\n    },\n\n\n    /**\n     * Retrieves the attributes Object (map) for the HtmlTag.\n     *\n     * @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.\n     */\n    getAttrs : function() {\n      return this.attrs || ( this.attrs = {} );\n    },\n\n\n    /**\n     * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.\n     *\n     * @param {String} cssClass One or more space-separated CSS classes to set (overwrite).\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    setClass : function( cssClass ) {\n      return this.setAttr( 'class', cssClass );\n    },\n\n\n    /**\n     * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.\n     *\n     * @param {String} cssClass One or more space-separated CSS classes to add.\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    addClass : function( cssClass ) {\n      var classAttr = this.getClass(),\n          whitespaceRegex = this.whitespaceRegex,\n          indexOf = Autolinker.Util.indexOf,  // to support IE8 and below\n          classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),\n          newClasses = cssClass.split( whitespaceRegex ),\n          newClass;\n\n      while( newClass = newClasses.shift() ) {\n        if( indexOf( classes, newClass ) === -1 ) {\n          classes.push( newClass );\n        }\n      }\n\n      this.getAttrs()[ 'class' ] = classes.join( \" \" );\n      return this;\n    },\n\n\n    /**\n     * Convenience method to remove one or more CSS classes from the HtmlTag.\n     *\n     * @param {String} cssClass One or more space-separated CSS classes to remove.\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    removeClass : function( cssClass ) {\n      var classAttr = this.getClass(),\n          whitespaceRegex = this.whitespaceRegex,\n          indexOf = Autolinker.Util.indexOf,  // to support IE8 and below\n          classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),\n          removeClasses = cssClass.split( whitespaceRegex ),\n          removeClass;\n\n      while( classes.length && ( removeClass = removeClasses.shift() ) ) {\n        var idx = indexOf( classes, removeClass );\n        if( idx !== -1 ) {\n          classes.splice( idx, 1 );\n        }\n      }\n\n      this.getAttrs()[ 'class' ] = classes.join( \" \" );\n      return this;\n    },\n\n\n    /**\n     * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when\n     * there are multiple.\n     *\n     * @return {String}\n     */\n    getClass : function() {\n      return this.getAttrs()[ 'class' ] || \"\";\n    },\n\n\n    /**\n     * Convenience method to check if the tag has a CSS class or not.\n     *\n     * @param {String} cssClass The CSS class to check for.\n     * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.\n     */\n    hasClass : function( cssClass ) {\n      return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1;\n    },\n\n\n    /**\n     * Sets the inner HTML for the tag.\n     *\n     * @param {String} html The inner HTML to set.\n     * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n     */\n    setInnerHtml : function( html ) {\n      this.innerHtml = html;\n\n      return this;\n    },\n\n\n    /**\n     * Retrieves the inner HTML for the tag.\n     *\n     * @return {String}\n     */\n    getInnerHtml : function() {\n      return this.innerHtml || \"\";\n    },\n\n\n    /**\n     * Override of superclass method used to generate the HTML string for the tag.\n     *\n     * @return {String}\n     */\n    toString : function() {\n      var tagName = this.getTagName(),\n          attrsStr = this.buildAttrsStr();\n\n      attrsStr = ( attrsStr ) ? ' ' + attrsStr : '';  // prepend a space if there are actually attributes\n\n      return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '</', tagName, '>' ].join( \"\" );\n    },\n\n\n    /**\n     * Support method for {@link #toString}, returns the string space-separated key=\"value\" pairs, used to populate\n     * the stringified HtmlTag.\n     *\n     * @protected\n     * @return {String} Example return: `attr1=\"value1\" attr2=\"value2\"`\n     */\n    buildAttrsStr : function() {\n      if( !this.attrs ) return \"\";  // no `attrs` Object (map) has been set, return empty string\n\n      var attrs = this.getAttrs(),\n          attrsArr = [];\n\n      for( var prop in attrs ) {\n        if( attrs.hasOwnProperty( prop ) ) {\n          attrsArr.push( prop + '=\"' + attrs[ prop ] + '\"' );\n        }\n      }\n      return attrsArr.join( \" \" );\n    }\n\n  } );\n  /*global Autolinker */\n  /*jshint scripturl:true */\n  /**\n   * @private\n   * @class Autolinker.MatchValidator\n   * @extends Object\n   *\n   * Used by Autolinker to filter out false positives from the {@link Autolinker#matcherRegex}.\n   *\n   * Due to the limitations of regular expressions (including the missing feature of look-behinds in JS regular expressions),\n   * we cannot always determine the validity of a given match. This class applies a bit of additional logic to filter out any\n   * false positives that have been matched by the {@link Autolinker#matcherRegex}.\n   */\n  Autolinker.MatchValidator = Autolinker.Util.extend( Object, {\n\n    /**\n     * @private\n     * @property {RegExp} invalidProtocolRelMatchRegex\n     *\n     * The regular expression used to check a potential protocol-relative URL match, coming from the\n     * {@link Autolinker#matcherRegex}. A protocol-relative URL is, for example, \"//yahoo.com\"\n     *\n     * This regular expression checks to see if there is a word character before the '//' match in order to determine if\n     * we should actually autolink a protocol-relative URL. This is needed because there is no negative look-behind in\n     * JavaScript regular expressions.\n     *\n     * For instance, we want to autolink something like \"Go to: //google.com\", but we don't want to autolink something\n     * like \"abc//google.com\"\n     */\n    invalidProtocolRelMatchRegex : /^[\\w]\\/\\//,\n\n    /**\n     * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://'\n     *\n     * @private\n     * @property {RegExp} hasFullProtocolRegex\n     */\n    hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\\/\\//,\n\n    /**\n     * Regex to find the URI scheme, such as 'mailto:'.\n     *\n     * This is used to filter out 'javascript:' and 'vbscript:' schemes.\n     *\n     * @private\n     * @property {RegExp} uriSchemeRegex\n     */\n    uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/,\n\n    /**\n     * Regex to determine if at least one word char exists after the protocol (i.e. after the ':')\n     *\n     * @private\n     * @property {RegExp} hasWordCharAfterProtocolRegex\n     */\n    hasWordCharAfterProtocolRegex : /:[^\\s]*?[A-Za-z]/,\n\n\n    /**\n     * Determines if a given match found by {@link Autolinker#processTextNode} is valid. Will return `false` for:\n     *\n     * 1) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over\n     *    matches like \"abc:def\"). However, URL matches with a protocol will be allowed (ex: 'http://localhost')\n     * 2) URL matches which do not have at least one word character in the domain name (effectively skipping over\n     *    matches like \"git:1.0\").\n     * 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character\n     *    (effectively skipping over strings like \"abc//google.com\")\n     *\n     * Otherwise, returns `true`.\n     *\n     * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match.\n     * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match\n     *   something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it.\n     * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding\n     *   character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character\n     *   preceding the '//'.\n     * @return {Boolean} `true` if the match given is valid and should be processed, or `false` if the match is invalid and/or\n     *   should just not be processed.\n     */\n    isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) {\n      if(\n        ( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) ||\n        this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) ||       // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost')\n        this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) ||  // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like \"git:1.0\"\n        this.isInvalidProtocolRelativeMatch( protocolRelativeMatch )                 // A protocol-relative match which has a word character in front of it (so we can skip something like \"abc//google.com\")\n      ) {\n        return false;\n      }\n\n      return true;\n    },\n\n\n    /**\n     * Determines if the URI scheme is a valid scheme to be autolinked. Returns `false` if the scheme is\n     * 'javascript:' or 'vbscript:'\n     *\n     * @private\n     * @param {String} uriSchemeMatch The match URL string for a full URI scheme match. Ex: 'http://yahoo.com'\n     *   or 'mailto:a@a.com'.\n     * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise.\n     */\n    isValidUriScheme : function( uriSchemeMatch ) {\n      var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ];\n\n      return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' );\n    },\n\n\n    /**\n     * Determines if a URL match does not have either:\n     *\n     * a) a full protocol (i.e. 'http://'), or\n     * b) at least one dot ('.') in the domain name (for a non-full-protocol match).\n     *\n     * Either situation is considered an invalid URL (ex: 'git:d' does not have either the '://' part, or at least one dot\n     * in the domain name. If the match was 'git:abc.com', we would consider this valid.)\n     *\n     * @private\n     * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match.\n     * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match\n     *   something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it.\n     * @return {Boolean} `true` if the URL match does not have a full protocol, or at least one dot ('.') in a non-full-protocol\n     *   match.\n     */\n    urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) {\n      return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 );\n    },\n\n\n    /**\n     * Determines if a URL match does not have at least one word character after the protocol (i.e. in the domain name).\n     *\n     * At least one letter character must exist in the domain name after a protocol match. Ex: skip over something\n     * like \"git:1.0\"\n     *\n     * @private\n     * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match.\n     * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to\n     *   know whether or not we have a protocol in the URL string, in order to check for a word character after the protocol\n     *   separator (':').\n     * @return {Boolean} `true` if the URL match does not have at least one word character in it after the protocol, `false`\n     *   otherwise.\n     */\n    urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) {\n      if( urlMatch && protocolUrlMatch ) {\n        return !this.hasWordCharAfterProtocolRegex.test( urlMatch );\n      } else {\n        return false;\n      }\n    },\n\n\n    /**\n     * Determines if a protocol-relative match is an invalid one. This method returns `true` if there is a `protocolRelativeMatch`,\n     * and that match contains a word character before the '//' (i.e. it must contain whitespace or nothing before the '//' in\n     * order to be considered valid).\n     *\n     * @private\n     * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding\n     *   character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character\n     *   preceding the '//'.\n     * @return {Boolean} `true` if it is an invalid protocol-relative match, `false` otherwise.\n     */\n    isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) {\n      return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) );\n    }\n\n  } );\n  /*global Autolinker */\n  /*jshint sub:true */\n  /**\n   * @protected\n   * @class Autolinker.AnchorTagBuilder\n   * @extends Object\n   *\n   * Builds anchor (&lt;a&gt;) tags for the Autolinker utility when a match is found.\n   *\n   * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may\n   * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances\n   * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example:\n   *\n   *     var html = Autolinker.link( \"Test google.com\", {\n   *         replaceFn : function( autolinker, match ) {\n   *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance\n   *             tag.setAttr( 'rel', 'nofollow' );\n   *\n   *             return tag;\n   *         }\n   *     } );\n   *\n   *     // generated html:\n   *     //   Test <a href=\"http://google.com\" target=\"_blank\" rel=\"nofollow\">google.com</a>\n   */\n  Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, {\n\n    /**\n     * @cfg {Boolean} newWindow\n     * @inheritdoc Autolinker#newWindow\n     */\n\n    /**\n     * @cfg {Number} truncate\n     * @inheritdoc Autolinker#truncate\n     */\n\n    /**\n     * @cfg {String} className\n     * @inheritdoc Autolinker#className\n     */\n\n\n    /**\n     * @constructor\n     * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).\n     */\n    constructor : function( cfg ) {\n      Autolinker.Util.assign( this, cfg );\n    },\n\n\n    /**\n     * Generates the actual anchor (&lt;a&gt;) tag to use in place of the matched URL/email/Twitter text,\n     * via its `match` object.\n     *\n     * @param {Autolinker.match.Match} match The Match instance to generate an anchor tag from.\n     * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.\n     */\n    build : function( match ) {\n      var tag = new Autolinker.HtmlTag( {\n        tagName   : 'a',\n        attrs     : this.createAttrs( match.getType(), match.getAnchorHref() ),\n        innerHtml : this.processAnchorText( match.getAnchorText() )\n      } );\n\n      return tag;\n    },\n\n\n    /**\n     * Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;) tag being generated.\n     *\n     * @protected\n     * @param {\"url\"/\"email\"/\"twitter\"} matchType The type of match that an anchor tag is being generated for.\n     * @param {String} href The href for the anchor tag.\n     * @return {Object} A key/value Object (map) of the anchor tag's attributes.\n     */\n    createAttrs : function( matchType, anchorHref ) {\n      var attrs = {\n        'href' : anchorHref  // we'll always have the `href` attribute\n      };\n\n      var cssClass = this.createCssClass( matchType );\n      if( cssClass ) {\n        attrs[ 'class' ] = cssClass;\n      }\n      if( this.newWindow ) {\n        attrs[ 'target' ] = \"_blank\";\n      }\n\n      return attrs;\n    },\n\n\n    /**\n     * Creates the CSS class that will be used for a given anchor tag, based on the `matchType` and the {@link #className}\n     * config.\n     *\n     * @private\n     * @param {\"url\"/\"email\"/\"twitter\"} matchType The type of match that an anchor tag is being generated for.\n     * @return {String} The CSS class string for the link. Example return: \"myLink myLink-url\". If no {@link #className}\n     *   was configured, returns an empty string.\n     */\n    createCssClass : function( matchType ) {\n      var className = this.className;\n\n      if( !className )\n        return \"\";\n      else\n        return className + \" \" + className + \"-\" + matchType;  // ex: \"myLink myLink-url\", \"myLink myLink-email\", or \"myLink myLink-twitter\"\n    },\n\n\n    /**\n     * Processes the `anchorText` by truncating the text according to the {@link #truncate} config.\n     *\n     * @private\n     * @param {String} anchorText The anchor tag's text (i.e. what will be displayed).\n     * @return {String} The processed `anchorText`.\n     */\n    processAnchorText : function( anchorText ) {\n      anchorText = this.doTruncate( anchorText );\n\n      return anchorText;\n    },\n\n\n    /**\n     * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option.\n     * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds \"..\" to the end.\n     *\n     * @private\n     * @param {String} text The anchor tag's text (i.e. what will be displayed).\n     * @return {String} The truncated anchor text.\n     */\n    doTruncate : function( anchorText ) {\n      return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY );\n    }\n\n  } );\n  /*global Autolinker */\n  /**\n   * @abstract\n   * @class Autolinker.match.Match\n   *\n   * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a\n   * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.\n   *\n   * For example:\n   *\n   *     var input = \"...\";  // string with URLs, Email Addresses, and Twitter Handles\n   *\n   *     var linkedText = Autolinker.link( input, {\n   *         replaceFn : function( autolinker, match ) {\n   *             console.log( \"href = \", match.getAnchorHref() );\n   *             console.log( \"text = \", match.getAnchorText() );\n   *\n   *             switch( match.getType() ) {\n   *                 case 'url' :\n   *                     console.log( \"url: \", match.getUrl() );\n   *\n   *                 case 'email' :\n   *                     console.log( \"email: \", match.getEmail() );\n   *\n   *                 case 'twitter' :\n   *                     console.log( \"twitter: \", match.getTwitterHandle() );\n   *             }\n   *         }\n   *     } );\n   *\n   * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.\n   */\n  Autolinker.match.Match = Autolinker.Util.extend( Object, {\n\n    /**\n     * @cfg {String} matchedText (required)\n     *\n     * The original text that was matched.\n     */\n\n\n    /**\n     * @constructor\n     * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).\n     */\n    constructor : function( cfg ) {\n      Autolinker.Util.assign( this, cfg );\n    },\n\n\n    /**\n     * Returns a string name for the type of match that this class represents.\n     *\n     * @abstract\n     * @return {String}\n     */\n    getType : Autolinker.Util.abstractMethod,\n\n\n    /**\n     * Returns the original text that was matched.\n     *\n     * @return {String}\n     */\n    getMatchedText : function() {\n      return this.matchedText;\n    },\n\n\n    /**\n     * Returns the anchor href that should be generated for the match.\n     *\n     * @abstract\n     * @return {String}\n     */\n    getAnchorHref : Autolinker.Util.abstractMethod,\n\n\n    /**\n     * Returns the anchor text that should be generated for the match.\n     *\n     * @abstract\n     * @return {String}\n     */\n    getAnchorText : Autolinker.Util.abstractMethod\n\n  } );\n  /*global Autolinker */\n  /**\n   * @class Autolinker.match.Email\n   * @extends Autolinker.match.Match\n   *\n   * Represents a Email match found in an input string which should be Autolinked.\n   *\n   * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n   */\n  Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, {\n\n    /**\n     * @cfg {String} email (required)\n     *\n     * The email address that was matched.\n     */\n\n\n    /**\n     * Returns a string name for the type of match that this class represents.\n     *\n     * @return {String}\n     */\n    getType : function() {\n      return 'email';\n    },\n\n\n    /**\n     * Returns the email address that was matched.\n     *\n     * @return {String}\n     */\n    getEmail : function() {\n      return this.email;\n    },\n\n\n    /**\n     * Returns the anchor href that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorHref : function() {\n      return 'mailto:' + this.email;\n    },\n\n\n    /**\n     * Returns the anchor text that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorText : function() {\n      return this.email;\n    }\n\n  } );\n  /*global Autolinker */\n  /**\n   * @class Autolinker.match.Twitter\n   * @extends Autolinker.match.Match\n   *\n   * Represents a Twitter match found in an input string which should be Autolinked.\n   *\n   * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n   */\n  Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, {\n\n    /**\n     * @cfg {String} twitterHandle (required)\n     *\n     * The Twitter handle that was matched.\n     */\n\n\n    /**\n     * Returns the type of match that this class represents.\n     *\n     * @return {String}\n     */\n    getType : function() {\n      return 'twitter';\n    },\n\n\n    /**\n     * Returns a string name for the type of match that this class represents.\n     *\n     * @return {String}\n     */\n    getTwitterHandle : function() {\n      return this.twitterHandle;\n    },\n\n\n    /**\n     * Returns the anchor href that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorHref : function() {\n      return 'https://twitter.com/' + this.twitterHandle;\n    },\n\n\n    /**\n     * Returns the anchor text that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorText : function() {\n      return '@' + this.twitterHandle;\n    }\n\n  } );\n  /*global Autolinker */\n  /**\n   * @class Autolinker.match.Url\n   * @extends Autolinker.match.Match\n   *\n   * Represents a Url match found in an input string which should be Autolinked.\n   *\n   * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n   */\n  Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {\n\n    /**\n     * @cfg {String} url (required)\n     *\n     * The url that was matched.\n     */\n\n    /**\n     * @cfg {Boolean} protocolUrlMatch (required)\n     *\n     * `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or\n     * known TLD match.\n     */\n\n    /**\n     * @cfg {Boolean} protocolRelativeMatch (required)\n     *\n     * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//',\n     * and will be either http:// or https:// based on the protocol that the site is loaded under.\n     */\n\n    /**\n     * @cfg {Boolean} stripPrefix (required)\n     * @inheritdoc Autolinker#stripPrefix\n     */\n\n\n    /**\n     * @private\n     * @property {RegExp} urlPrefixRegex\n     *\n     * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs.\n     */\n    urlPrefixRegex: /^(https?:\\/\\/)?(www\\.)?/i,\n\n    /**\n     * @private\n     * @property {RegExp} protocolRelativeRegex\n     *\n     * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes\n     * of {@link #getAnchorText}. A protocol-relative URL is, for example, \"//yahoo.com\"\n     */\n    protocolRelativeRegex : /^\\/\\//,\n\n    /**\n     * @private\n     * @property {Boolean} protocolPrepended\n     *\n     * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the\n     * {@link #url} did not have a protocol)\n     */\n    protocolPrepended : false,\n\n\n    /**\n     * Returns a string name for the type of match that this class represents.\n     *\n     * @return {String}\n     */\n    getType : function() {\n      return 'url';\n    },\n\n\n    /**\n     * Returns the url that was matched, assuming the protocol to be 'http://' if the original\n     * match was missing a protocol.\n     *\n     * @return {String}\n     */\n    getUrl : function() {\n      var url = this.url;\n\n      // if the url string doesn't begin with a protocol, assume 'http://'\n      if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) {\n        url = this.url = 'http://' + url;\n\n        this.protocolPrepended = true;\n      }\n\n      return url;\n    },\n\n\n    /**\n     * Returns the anchor href that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorHref : function() {\n      var url = this.getUrl();\n\n      return url.replace( /&amp;/g, '&' );  // any &amp;'s in the URL should be converted back to '&' if they were displayed as &amp; in the source html\n    },\n\n\n    /**\n     * Returns the anchor text that should be generated for the match.\n     *\n     * @return {String}\n     */\n    getAnchorText : function() {\n      var anchorText = this.getUrl();\n\n      if( this.protocolRelativeMatch ) {\n        // Strip off any protocol-relative '//' from the anchor text\n        anchorText = this.stripProtocolRelativePrefix( anchorText );\n      }\n      if( this.stripPrefix ) {\n        anchorText = this.stripUrlPrefix( anchorText );\n      }\n      anchorText = this.removeTrailingSlash( anchorText );  // remove trailing slash, if there is one\n\n      return anchorText;\n    },\n\n\n    // ---------------------------------------\n\n    // Utility Functionality\n\n    /**\n     * Strips the URL prefix (such as \"http://\" or \"https://\") from the given text.\n     *\n     * @private\n     * @param {String} text The text of the anchor that is being generated, for which to strip off the\n     *   url prefix (such as stripping off \"http://\")\n     * @return {String} The `anchorText`, with the prefix stripped.\n     */\n    stripUrlPrefix : function( text ) {\n      return text.replace( this.urlPrefixRegex, '' );\n    },\n\n\n    /**\n     * Strips any protocol-relative '//' from the anchor text.\n     *\n     * @private\n     * @param {String} text The text of the anchor that is being generated, for which to strip off the\n     *   protocol-relative prefix (such as stripping off \"//\")\n     * @return {String} The `anchorText`, with the protocol-relative prefix stripped.\n     */\n    stripProtocolRelativePrefix : function( text ) {\n      return text.replace( this.protocolRelativeRegex, '' );\n    },\n\n\n    /**\n     * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.\n     *\n     * @private\n     * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing\n     *   slash ('/') that may exist.\n     * @return {String} The `anchorText`, with the trailing slash removed.\n     */\n    removeTrailingSlash : function( anchorText ) {\n      if( anchorText.charAt( anchorText.length - 1 ) === '/' ) {\n        anchorText = anchorText.slice( 0, -1 );\n      }\n      return anchorText;\n    }\n\n  } );\n\n  return Autolinker;\n\n\n}));\n\n},{}],52:[function(require,module,exports){\n'use strict';\n\n\nmodule.exports = require('./lib/');\n\n},{\"./lib/\":11}]},{},[52])(52)\n});\n"
  },
  {
    "path": "public/libs/qrcode.js",
    "content": "/**\n * @fileoverview\n * - Using the 'QRCode for Javascript library'\n * - Fixed dataset of 'QRCode for Javascript library' for support full-spec.\n * - this library has no dependencies.\n *\n * @author davidshimjs\n * @see <a href=\"http://www.d-project.com/\" target=\"_blank\">http://www.d-project.com/</a>\n * @see <a href=\"http://jeromeetienne.github.com/jquery-qrcode/\" target=\"_blank\">http://jeromeetienne.github.com/jquery-qrcode/</a>\n */\nvar QRCode;\n\n(function () {\n  //---------------------------------------------------------------------\n  // QRCode for JavaScript\n  //\n  // Copyright (c) 2009 Kazuhiko Arase\n  //\n  // URL: http://www.d-project.com/\n  //\n  // Licensed under the MIT license:\n  //   http://www.opensource.org/licenses/mit-license.php\n  //\n  // The word \"QR Code\" is registered trademark of\n  // DENSO WAVE INCORPORATED\n  //   http://www.denso-wave.com/qrcode/faqpatent-e.html\n  //\n  //---------------------------------------------------------------------\n  function QR8bitByte(data) {\n    this.mode = QRMode.MODE_8BIT_BYTE;\n    this.data = data;\n    this.parsedData = [];\n\n    // Added to support UTF-8 Characters\n    for (var i = 0, l = this.data.length; i < l; i++) {\n      var byteArray = [];\n      var code = this.data.charCodeAt(i);\n\n      if (code > 0x10000) {\n        byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);\n        byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);\n        byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);\n        byteArray[3] = 0x80 | (code & 0x3F);\n      } else if (code > 0x800) {\n        byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);\n        byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);\n        byteArray[2] = 0x80 | (code & 0x3F);\n      } else if (code > 0x80) {\n        byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);\n        byteArray[1] = 0x80 | (code & 0x3F);\n      } else {\n        byteArray[0] = code;\n      }\n\n      this.parsedData.push(byteArray);\n    }\n\n    this.parsedData = Array.prototype.concat.apply([], this.parsedData);\n\n    if (this.parsedData.length != this.data.length) {\n      this.parsedData.unshift(191);\n      this.parsedData.unshift(187);\n      this.parsedData.unshift(239);\n    }\n  }\n\n  QR8bitByte.prototype = {\n    getLength: function (buffer) {\n      return this.parsedData.length;\n    },\n    write: function (buffer) {\n      for (var i = 0, l = this.parsedData.length; i < l; i++) {\n        buffer.put(this.parsedData[i], 8);\n      }\n    }\n  };\n\n  function QRCodeModel(typeNumber, errorCorrectLevel) {\n    this.typeNumber = typeNumber;\n    this.errorCorrectLevel = errorCorrectLevel;\n    this.modules = null;\n    this.moduleCount = 0;\n    this.dataCache = null;\n    this.dataList = [];\n  }\n\n  QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+\",\"+col);}\n  return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}\n  this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}\n  if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}\n  this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}\n  return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}\n  return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}\n  this.modules[r][6]=(r%2==0);}\n  for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}\n  this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}\n  for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}\n  for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}\n  for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}\n  this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}\n  var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}\n  this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}\n  row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}\n  var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}\n  if(buffer.getLengthInBits()>totalDataCount*8){throw new Error(\"code length overflow. (\"\n  +buffer.getLengthInBits()\n  +\">\"\n  +totalDataCount*8\n  +\")\");}\n  if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}\n  while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}\n  while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}\n  buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}\n  buffer.put(QRCodeModel.PAD1,8);}\n  return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}\n  offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}\n  var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}\n  var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}\n  for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}\n  return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}\n  return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}\n  return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}\n  return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error(\"bad maskPattern:\"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}\n  return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error(\"mode:\"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error(\"mode:\"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error(\"mode:\"+mode);}}else{throw new Error(\"type:\"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}\n  for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}\n  if(r==0&&c==0){continue;}\n  if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}\n  if(sameCount>5){lostPoint+=(3+sameCount-5);}}}\n  for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}\n  for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}\n  for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}\n  var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}\n  var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error(\"glog(\"+n+\")\");}\n  return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}\n  while(n>=256){n-=255;}\n  return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}\n  for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}\n  for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}\n  function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+\"/\"+shift);}\n  var offset=0;while(offset<num.length&&num[offset]==0){offset++;}\n  this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}\n  QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}\n  return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}\n  var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}\n  for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}\n  return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}\n  QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error(\"bad rs block @ typeNumber:\"+typeNumber+\"/errorCorrectLevel:\"+errorCorrectLevel);}\n  var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}\n  return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}\n  QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}\n  if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}\n  this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];\n\n  function _isSupportCanvas() {\n    return typeof CanvasRenderingContext2D != \"undefined\";\n  }\n\n  // android 2.x doesn't support Data-URI spec\n  function _getAndroid() {\n    var android = false;\n    var sAgent = navigator.userAgent;\n\n    if (/android/i.test(sAgent)) { // android\n      android = true;\n      var aMat = sAgent.toString().match(/android ([0-9]\\.[0-9])/i);\n\n      if (aMat && aMat[1]) {\n        android = parseFloat(aMat[1]);\n      }\n    }\n\n    return android;\n  }\n\n  var svgDrawer = (function() {\n\n    var Drawing = function (el, htOption) {\n      this._el = el;\n      this._htOption = htOption;\n    };\n\n    Drawing.prototype.draw = function (oQRCode) {\n      var _htOption = this._htOption;\n      var _el = this._el;\n      var nCount = oQRCode.getModuleCount();\n      var nWidth = Math.floor(_htOption.width / nCount);\n      var nHeight = Math.floor(_htOption.height / nCount);\n\n      this.clear();\n\n      function makeSVG(tag, attrs) {\n        var el = document.createElementNS('http://www.w3.org/2000/svg', tag);\n        for (var k in attrs)\n          if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);\n        return el;\n      }\n\n      var svg = makeSVG(\"svg\" , {'viewBox': '0 0 ' + String(nCount) + \" \" + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});\n      svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\", \"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n      _el.appendChild(svg);\n\n      svg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorLight, \"width\": \"100%\", \"height\": \"100%\"}));\n      svg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorDark, \"width\": \"1\", \"height\": \"1\", \"id\": \"template\"}));\n\n      for (var row = 0; row < nCount; row++) {\n        for (var col = 0; col < nCount; col++) {\n          if (oQRCode.isDark(row, col)) {\n            var child = makeSVG(\"use\", {\"x\": String(row), \"y\": String(col)});\n            child.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"#template\")\n            svg.appendChild(child);\n          }\n        }\n      }\n    };\n    Drawing.prototype.clear = function () {\n      while (this._el.hasChildNodes())\n        this._el.removeChild(this._el.lastChild);\n    };\n    return Drawing;\n  })();\n\n  var useSVG = document.documentElement.tagName.toLowerCase() === \"svg\";\n\n  // Drawing in DOM by using Table tag\n  var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {\n    var Drawing = function (el, htOption) {\n      this._el = el;\n      this._htOption = htOption;\n    };\n\n    /**\n     * Draw the QRCode\n     *\n     * @param {QRCode} oQRCode\n     */\n    Drawing.prototype.draw = function (oQRCode) {\n            var _htOption = this._htOption;\n            var _el = this._el;\n      var nCount = oQRCode.getModuleCount();\n      var nWidth = Math.floor(_htOption.width / nCount);\n      var nHeight = Math.floor(_htOption.height / nCount);\n      var aHTML = ['<table style=\"border:0;border-collapse:collapse;\">'];\n\n      for (var row = 0; row < nCount; row++) {\n        aHTML.push('<tr>');\n\n        for (var col = 0; col < nCount; col++) {\n          aHTML.push('<td style=\"border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';\"></td>');\n        }\n\n        aHTML.push('</tr>');\n      }\n\n      aHTML.push('</table>');\n      _el.innerHTML = aHTML.join('');\n\n      // Fix the margin values as real size.\n      var elTable = _el.childNodes[0];\n      var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;\n      var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;\n\n      if (nLeftMarginTable > 0 && nTopMarginTable > 0) {\n        elTable.style.margin = nTopMarginTable + \"px \" + nLeftMarginTable + \"px\";\n      }\n    };\n\n    /**\n     * Clear the QRCode\n     */\n    Drawing.prototype.clear = function () {\n      this._el.innerHTML = '';\n    };\n\n    return Drawing;\n  })() : (function () { // Drawing in Canvas\n    function _onMakeImage() {\n      this._elImage.src = this._elCanvas.toDataURL(\"image/png\");\n      this._elImage.style.display = \"block\";\n      this._elCanvas.style.display = \"none\";\n    }\n\n    // Android 2.1 bug workaround\n    // http://code.google.com/p/android/issues/detail?id=5141\n    if (this._android && this._android <= 2.1) {\n        var factor = 1 / window.devicePixelRatio;\n          var drawImage = CanvasRenderingContext2D.prototype.drawImage;\n        CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {\n          if ((\"nodeName\" in image) && /img/i.test(image.nodeName)) {\n              for (var i = arguments.length - 1; i >= 1; i--) {\n                  arguments[i] = arguments[i] * factor;\n              }\n          } else if (typeof dw == \"undefined\") {\n            arguments[1] *= factor;\n            arguments[2] *= factor;\n            arguments[3] *= factor;\n            arguments[4] *= factor;\n          }\n\n            drawImage.apply(this, arguments);\n        };\n    }\n\n    /**\n     * Check whether the user's browser supports Data URI or not\n     *\n     * @private\n     * @param {Function} fSuccess Occurs if it supports Data URI\n     * @param {Function} fFail Occurs if it doesn't support Data URI\n     */\n    function _safeSetDataURI(fSuccess, fFail) {\n            var self = this;\n            self._fFail = fFail;\n            self._fSuccess = fSuccess;\n\n            // Check it just once\n            if (self._bSupportDataURI === null) {\n                var el = document.createElement(\"img\");\n                var fOnError = function() {\n                    self._bSupportDataURI = false;\n\n                    if (self._fFail) {\n                        self._fFail.call(self);\n                    }\n                };\n                var fOnSuccess = function() {\n                    self._bSupportDataURI = true;\n\n                    if (self._fSuccess) {\n                        self._fSuccess.call(self);\n                    }\n                };\n\n                el.onabort = fOnError;\n                el.onerror = fOnError;\n                el.onload = fOnSuccess;\n                el.src = \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==\"; // the Image contains 1px data.\n                return;\n            } else if (self._bSupportDataURI === true && self._fSuccess) {\n                self._fSuccess.call(self);\n            } else if (self._bSupportDataURI === false && self._fFail) {\n                self._fFail.call(self);\n            }\n    };\n\n    /**\n     * Drawing QRCode by using canvas\n     *\n     * @constructor\n     * @param {HTMLElement} el\n     * @param {Object} htOption QRCode Options\n     */\n    var Drawing = function (el, htOption) {\n        this._bIsPainted = false;\n        this._android = _getAndroid();\n\n      this._htOption = htOption;\n      this._elCanvas = document.createElement(\"canvas\");\n      this._elCanvas.width = htOption.width;\n      this._elCanvas.height = htOption.height;\n      el.appendChild(this._elCanvas);\n      this._el = el;\n      this._oContext = this._elCanvas.getContext(\"2d\");\n      this._bIsPainted = false;\n      this._elImage = document.createElement(\"img\");\n      this._elImage.alt = \"Scan me!\";\n      this._elImage.style.display = \"none\";\n      this._el.appendChild(this._elImage);\n      this._bSupportDataURI = null;\n    };\n\n    /**\n     * Draw the QRCode\n     *\n     * @param {QRCode} oQRCode\n     */\n    Drawing.prototype.draw = function (oQRCode) {\n            var _elImage = this._elImage;\n            var _oContext = this._oContext;\n            var _htOption = this._htOption;\n\n      var nCount = oQRCode.getModuleCount();\n      var nWidth = _htOption.width / nCount;\n      var nHeight = _htOption.height / nCount;\n      var nRoundedWidth = Math.round(nWidth);\n      var nRoundedHeight = Math.round(nHeight);\n\n      _elImage.style.display = \"none\";\n      this.clear();\n\n      for (var row = 0; row < nCount; row++) {\n        for (var col = 0; col < nCount; col++) {\n          var bIsDark = oQRCode.isDark(row, col);\n          var nLeft = col * nWidth;\n          var nTop = row * nHeight;\n          _oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n          _oContext.lineWidth = 1;\n          _oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n          _oContext.fillRect(nLeft, nTop, nWidth, nHeight);\n\n          // 안티 앨리어싱 방지 처리\n          _oContext.strokeRect(\n            Math.floor(nLeft) + 0.5,\n            Math.floor(nTop) + 0.5,\n            nRoundedWidth,\n            nRoundedHeight\n          );\n\n          _oContext.strokeRect(\n            Math.ceil(nLeft) - 0.5,\n            Math.ceil(nTop) - 0.5,\n            nRoundedWidth,\n            nRoundedHeight\n          );\n        }\n      }\n\n      this._bIsPainted = true;\n    };\n\n    /**\n     * Make the image from Canvas if the browser supports Data URI.\n     */\n    Drawing.prototype.makeImage = function () {\n      if (this._bIsPainted) {\n        _safeSetDataURI.call(this, _onMakeImage);\n      }\n    };\n\n    /**\n     * Return whether the QRCode is painted or not\n     *\n     * @return {Boolean}\n     */\n    Drawing.prototype.isPainted = function () {\n      return this._bIsPainted;\n    };\n\n    /**\n     * Clear the QRCode\n     */\n    Drawing.prototype.clear = function () {\n      this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);\n      this._bIsPainted = false;\n    };\n\n    /**\n     * @private\n     * @param {Number} nNumber\n     */\n    Drawing.prototype.round = function (nNumber) {\n      if (!nNumber) {\n        return nNumber;\n      }\n\n      return Math.floor(nNumber * 1000) / 1000;\n    };\n\n    return Drawing;\n  })();\n\n  /**\n   * Get the type by string length\n   *\n   * @private\n   * @param {String} sText\n   * @param {Number} nCorrectLevel\n   * @return {Number} type\n   */\n  function _getTypeNumber(sText, nCorrectLevel) {\n    var nType = 1;\n    var length = _getUTF8Length(sText);\n\n    for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {\n      var nLimit = 0;\n\n      switch (nCorrectLevel) {\n        case QRErrorCorrectLevel.L :\n          nLimit = QRCodeLimitLength[i][0];\n          break;\n        case QRErrorCorrectLevel.M :\n          nLimit = QRCodeLimitLength[i][1];\n          break;\n        case QRErrorCorrectLevel.Q :\n          nLimit = QRCodeLimitLength[i][2];\n          break;\n        case QRErrorCorrectLevel.H :\n          nLimit = QRCodeLimitLength[i][3];\n          break;\n      }\n\n      if (length <= nLimit) {\n        break;\n      } else {\n        nType++;\n      }\n    }\n\n    if (nType > QRCodeLimitLength.length) {\n      throw new Error(\"Too long data\");\n    }\n\n    return nType;\n  }\n\n  function _getUTF8Length(sText) {\n    var replacedText = encodeURI(sText).toString().replace(/\\%[0-9a-fA-F]{2}/g, 'a');\n    return replacedText.length + (replacedText.length != sText ? 3 : 0);\n  }\n\n  /**\n   * @class QRCode\n   * @constructor\n   * @example\n   * new QRCode(document.getElementById(\"test\"), \"http://jindo.dev.naver.com/collie\");\n   *\n   * @example\n   * var oQRCode = new QRCode(\"test\", {\n   *    text : \"http://naver.com\",\n   *    width : 128,\n   *    height : 128\n   * });\n   *\n   * oQRCode.clear(); // Clear the QRCode.\n   * oQRCode.makeCode(\"http://map.naver.com\"); // Re-create the QRCode.\n   *\n   * @param {HTMLElement|String} el target element or 'id' attribute of element.\n   * @param {Object|String} vOption\n   * @param {String} vOption.text QRCode link data\n   * @param {Number} [vOption.width=256]\n   * @param {Number} [vOption.height=256]\n   * @param {String} [vOption.colorDark=\"#000000\"]\n   * @param {String} [vOption.colorLight=\"#ffffff\"]\n   * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]\n   */\n  QRCode = function (el, vOption) {\n    this._htOption = {\n      width : 256,\n      height : 256,\n      typeNumber : 4,\n      colorDark : \"#000000\",\n      colorLight : \"#ffffff\",\n      correctLevel : QRErrorCorrectLevel.H\n    };\n\n    if (typeof vOption === 'string') {\n      vOption = {\n        text : vOption\n      };\n    }\n\n    // Overwrites options\n    if (vOption) {\n      for (var i in vOption) {\n        this._htOption[i] = vOption[i];\n      }\n    }\n\n    if (typeof el == \"string\") {\n      el = document.getElementById(el);\n    }\n\n    if (this._htOption.useSVG) {\n      Drawing = svgDrawer;\n    }\n\n    this._android = _getAndroid();\n    this._el = el;\n    this._oQRCode = null;\n    this._oDrawing = new Drawing(this._el, this._htOption);\n\n    if (this._htOption.text) {\n      this.makeCode(this._htOption.text);\n    }\n  };\n\n  /**\n   * Make the QRCode\n   *\n   * @param {String} sText link data\n   */\n  QRCode.prototype.makeCode = function (sText) {\n    this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);\n    this._oQRCode.addData(sText);\n    this._oQRCode.make();\n    this._el.title = sText;\n    this._oDrawing.draw(this._oQRCode);\n    this.makeImage();\n  };\n\n  /**\n   * Make the Image from Canvas element\n   * - It occurs automatically\n   * - Android below 3 doesn't support Data-URI spec.\n   *\n   * @private\n   */\n  QRCode.prototype.makeImage = function () {\n    if (typeof this._oDrawing.makeImage == \"function\" && (!this._android || this._android >= 3)) {\n      this._oDrawing.makeImage();\n    }\n  };\n\n  /**\n   * Clear the QRCode\n   */\n  QRCode.prototype.clear = function () {\n    this._oDrawing.clear();\n  };\n\n  /**\n   * @name QRCode.CorrectLevel\n   */\n  QRCode.CorrectLevel = QRErrorCorrectLevel;\n})();\n"
  },
  {
    "path": "public/libs/webuploader/webuploader.css",
    "content": ".webuploader-container {\n\tposition: relative;\n}\n.webuploader-element-invisible {\n\tposition: absolute !important;\n\tclip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n    clip: rect(1px,1px,1px,1px);\n}\n.webuploader-pick {\n\tposition: relative;\n\tdisplay: inline-block;\n\tcursor: pointer;\n\tbackground: #00b7ee;\n\tpadding: 10px 15px;\n\tcolor: #fff;\n\ttext-align: center;\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.webuploader-pick-hover {\n\tbackground: #00a2d4;\n}\n\n.webuploader-pick-disable {\n\topacity: 0.6;\n\tpointer-events:none;\n}\n\n"
  },
  {
    "path": "public/libs/webuploader/webuploader.withoutimage.js",
    "content": "/*! WebUploader 0.1.5 */\n\n\n/**\n* @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n*\n* AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n*/\n(function (root, factory) {\n    var modules = {},\n\n    // 内部require, 简单不完全实现。\n    // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function (deps, callback) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if (typeof deps === 'string') {\n                return getModule(deps);\n            } else {\n                args = [];\n                for (len = deps.length, i = 0; i < len; i++) {\n                    args.push(getModule(deps[i]));\n                }\n\n                return callback.apply(null, args);\n            }\n        },\n\n    // 内部define，暂时不支持不指定id.\n        _define = function (id, deps, factory) {\n            if (arguments.length === 2) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require(deps || [], function () {\n                setModule(id, factory, arguments);\n            });\n        },\n\n    // 设置module, 兼容CommonJs写法。\n        setModule = function (id, factory, args) {\n            var module = {\n                exports: factory\n            },\n                returned;\n\n            if (typeof factory === 'function') {\n                args.length || (args = [_require, module.exports, module]);\n                returned = factory.apply(null, args);\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[id] = module.exports;\n        },\n\n    // 根据id获取module\n        getModule = function (id) {\n            var module = modules[id] || root[id];\n\n            if (!module) {\n                throw new Error('`' + id + '` is undefined');\n            }\n\n            return module;\n        },\n\n    // 将所有modules，将路径ids装换成对象。\n        exportsTo = function (obj) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function (str) {\n                return str && (str.charAt(0).toUpperCase() + str.substr(1));\n            };\n\n            for (key in modules) {\n                host = obj;\n\n                if (!modules.hasOwnProperty(key)) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst(parts.pop());\n\n                while ((part = ucFirst(parts.shift()))) {\n                    host[part] = host[part] || {};\n                    host = host[part];\n                }\n\n                host[last] = modules[key];\n            }\n\n            return obj;\n        },\n\n        makeExport = function (dollar) {\n            root.__dollar = dollar;\n\n            // exports every module.\n            return exportsTo(factory(root, _define, _require));\n        },\n\n        origin;\n\n    if (typeof module === 'object' && typeof module.exports === 'object') {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = makeExport();\n    } else if (typeof define === 'function' && define.amd) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define('webuploader', ['jquery'], makeExport);\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = makeExport();\n        root.WebUploader.noConflict = function () {\n            root.WebUploader = origin;\n        };\n    }\n})(window, function (window, define, require) {\n\n\n    /**\n    * @fileOverview jQuery or Zepto\n    */\n    define('dollar-third', [], function () {\n        var $ = window.__dollar || window.jQuery || window.Zepto;\n\n        if (!$) {\n            throw new Error('jQuery or Zepto not found!');\n        }\n\n        return $;\n    });\n    /**\n    * @fileOverview Dom 操作相关\n    */\n    define('dollar', [\n        'dollar-third'\n    ], function (_) {\n        return _;\n    });\n    /**\n    * @fileOverview 使用jQuery的Promise\n    */\n    define('promise-third', [\n        'dollar'\n    ], function ($) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n\n            isPromise: function (anything) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n    * @fileOverview Promise/A+\n    */\n    define('promise', [\n        'promise-third'\n    ], function (_) {\n        return _;\n    });\n    /**\n    * @fileOverview 基础类方法。\n    */\n\n    /**\n    * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n    *\n    * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n    * 默认module id为该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n    *\n    * * module `base`：WebUploader.Base\n    * * module `file`: WebUploader.File\n    * * module `lib/dnd`: WebUploader.Lib.Dnd\n    * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n    *\n    *\n    * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。\n    * @module WebUploader\n    * @title WebUploader API文档\n    */\n    define('base', [\n        'dollar',\n        'promise'\n    ], function ($, promise) {\n\n        var noop = function () { },\n            call = Function.call;\n\n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis(fn) {\n            return function () {\n                return call.apply(fn, arguments);\n            };\n        }\n\n        function bindFn(fn, context) {\n            return function () {\n                return fn.apply(context, arguments);\n            };\n        }\n\n        function createObject(proto) {\n            var f;\n\n            if (Object.create) {\n                return Object.create(proto);\n            } else {\n                f = function () { };\n                f.prototype = proto;\n                return new f();\n            }\n        }\n\n\n        /**\n        * 基础类，提供一些简单常用的方法。\n        * @class Base\n        */\n        return {\n\n            /**\n            * @property {String} version 当前版本号。\n            */\n            version: '0.1.5',\n\n            /**\n            * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n            */\n            $: $,\n\n            Deferred: promise.Deferred,\n\n            isPromise: promise.isPromise,\n\n            when: promise.when,\n\n            /**\n            * @description  简单的浏览器检查结果。\n            *\n            * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n            * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n            * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n            * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n            * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n            * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n            *\n            * @property {Object} [browser]\n            */\n            browser: (function (ua) {\n                var ret = {},\n                    webkit = ua.match(/WebKit\\/([\\d.]+)/),\n                    chrome = ua.match(/Chrome\\/([\\d.]+)/) ||\n                        ua.match(/CriOS\\/([\\d.]+)/),\n\n                    ie = ua.match(/MSIE\\s([\\d\\.]+)/) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match(/Firefox\\/([\\d.]+)/),\n                    safari = ua.match(/Safari\\/([\\d.]+)/),\n                    opera = ua.match(/OPR\\/([\\d.]+)/);\n\n                webkit && (ret.webkit = parseFloat(webkit[1]));\n                chrome && (ret.chrome = parseFloat(chrome[1]));\n                ie && (ret.ie = parseFloat(ie[1]));\n                firefox && (ret.firefox = parseFloat(firefox[1]));\n                safari && (ret.safari = parseFloat(safari[1]));\n                opera && (ret.opera = parseFloat(opera[1]));\n\n                return ret;\n            })(navigator.userAgent),\n\n            /**\n            * @description  操作系统检查结果。\n            *\n            * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n            * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n            * @property {Object} [os]\n            */\n            os: (function (ua) {\n                var ret = {},\n\n                // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match(/(?:Android);?[\\s\\/]+([\\d.]+)?/),\n                    ios = ua.match(/(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/);\n\n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat(android[1]));\n                ios && (ret.ios = parseFloat(ios[1].replace(/_/g, '.')));\n\n                return ret;\n            })(navigator.userAgent),\n\n            /**\n            * 实现类与类之间的继承。\n            * @method inherits\n            * @grammar Base.inherits( super ) => child\n            * @grammar Base.inherits( super, protos ) => child\n            * @grammar Base.inherits( super, protos, statics ) => child\n            * @param  {Class} super 父类\n            * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n            * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n            * @param  {Object} [statics] 静态属性或方法。\n            * @return {Class} 返回子类。\n            * @example\n            * function Person() {\n            *     console.log( 'Super' );\n            * }\n            * Person.prototype.hello = function() {\n            *     console.log( 'hello' );\n            * };\n            *\n            * var Manager = Base.inherits( Person, {\n            *     world: function() {\n            *         console.log( 'World' );\n            *     }\n            * });\n            *\n            * // 因为没有指定构造器，父类的构造器将会执行。\n            * var instance = new Manager();    // => Super\n            *\n            * // 继承子父类的方法\n            * instance.hello();    // => hello\n            * instance.world();    // => World\n            *\n            * // 子类的__super__属性指向父类\n            * console.log( Manager.__super__ === Person );    // => true\n            */\n            inherits: function (Super, protos, staticProtos) {\n                var child;\n\n                if (typeof protos === 'function') {\n                    child = protos;\n                    protos = null;\n                } else if (protos && protos.hasOwnProperty('constructor')) {\n                    child = protos.constructor;\n                } else {\n                    child = function () {\n                        return Super.apply(this, arguments);\n                    };\n                }\n\n                // 复制静态方法\n                $.extend(true, child, Super, staticProtos || {});\n\n                /* jshint camelcase: false */\n\n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n\n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject(Super.prototype);\n                protos && $.extend(true, child.prototype, protos);\n\n                return child;\n            },\n\n            /**\n            * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n            * @method noop\n            */\n            noop: noop,\n\n            /**\n            * 返回一个新的方法，此方法将已指定的`context`来执行。\n            * @grammar Base.bindFn( fn, context ) => Function\n            * @method bindFn\n            * @example\n            * var doSomething = function() {\n            *         console.log( this.name );\n            *     },\n            *     obj = {\n            *         name: 'Object Name'\n            *     },\n            *     aliasFn = Base.bind( doSomething, obj );\n            *\n            *  aliasFn();    // => Object Name\n            *\n            */\n            bindFn: bindFn,\n\n            /**\n            * 引用Console.log如果存在的话，否则引用一个[空函数noop](#WebUploader:Base.noop)。\n            * @grammar Base.log( args... ) => undefined\n            * @method log\n            */\n            log: (function () {\n                if (window.console) {\n                    return bindFn(console.log, console);\n                }\n                return noop;\n            })(),\n\n            nextTick: (function () {\n\n                return function (cb) {\n                    setTimeout(cb, 1);\n                };\n\n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n\n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n\n            /**\n            * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n            * 将用来将非数组对象转化成数组对象。\n            * @grammar Base.slice( target, start[, end] ) => Array\n            * @method slice\n            * @example\n            * function doSomthing() {\n            *     var args = Base.slice( arguments, 1 );\n            *     console.log( args );\n            * }\n            *\n            * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n            */\n            slice: uncurryThis([].slice),\n\n            /**\n            * 生成唯一的ID\n            * @method guid\n            * @grammar Base.guid() => String\n            * @grammar Base.guid( prefx ) => String\n            */\n            guid: (function () {\n                var counter = 0;\n\n                return function (prefix) {\n                    var guid = (+new Date()).toString(32),\n                        i = 0;\n\n                    for (; i < 5; i++) {\n                        guid += Math.floor(Math.random() * 65535).toString(32);\n                    }\n\n                    return (prefix || 'wu_') + guid + (counter++).toString(32);\n                };\n            })(),\n\n            /**\n            * 格式化文件大小, 输出成带单位的字符串\n            * @method formatSize\n            * @grammar Base.formatSize( size ) => String\n            * @grammar Base.formatSize( size, pointLength ) => String\n            * @grammar Base.formatSize( size, pointLength, units ) => String\n            * @param {Number} size 文件大小\n            * @param {Number} [pointLength=2] 精确到的小数点数。\n            * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n            * @example\n            * console.log( Base.formatSize( 100 ) );    // => 100B\n            * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n            * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n            * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n            * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n            * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n            */\n            formatSize: function (size, pointLength, units) {\n                var unit;\n\n                units = units || ['B', 'K', 'M', 'G', 'TB'];\n\n                while ((unit = units.shift()) && size > 1024) {\n                    size = size / 1024;\n                }\n\n                return (unit === 'B' ? size : size.toFixed(pointLength || 2)) +\n                        unit;\n            }\n        };\n    });\n    /**\n    * 事件处理类，可以独立使用，也可以扩展给对象使用。\n    * @fileOverview Mediator\n    */\n    define('mediator', [\n        'base'\n    ], function (Base) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n\n        // 根据条件过滤出事件handlers.\n        function findHandlers(arr, name, callback, context) {\n            return $.grep(arr, function (handler) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n\n        function eachEvent(events, callback, iterator) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each((events || '').split(separator), function (_, key) {\n                iterator(key, callback);\n            });\n        }\n\n        function triggerHanders(events, args) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n\n            while (++i < len) {\n                handler = events[i];\n\n                if (handler.cb.apply(handler.ctx2, args) === false) {\n                    stoped = true;\n                    break;\n                }\n            }\n\n            return !stoped;\n        }\n\n        protos = {\n\n            /**\n            * 绑定事件。\n            *\n            * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n            * ```javascript\n            * var obj = {};\n            *\n            * // 使得obj有事件行为\n            * Mediator.installTo( obj );\n            *\n            * obj.on( 'testa', function( arg1, arg2 ) {\n            *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n            * });\n            *\n            * obj.trigger( 'testa', 'arg1', 'arg2' );\n            * ```\n            *\n            * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n            * 切会影响到`trigger`方法的返回值，为`false`。\n            *\n            * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n            * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n            * ```javascript\n            * obj.on( 'all', function( type, arg1, arg2 ) {\n            *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n            * });\n            * ```\n            *\n            * @method on\n            * @grammar on( name, callback[, context] ) => self\n            * @param  {String}   name     事件名，支持多个事件用空格隔开\n            * @param  {Function} callback 事件处理器\n            * @param  {Object}   [context]  事件处理器的上下文。\n            * @return {self} 返回自身，方便链式\n            * @chainable\n            * @class Mediator\n            */\n            on: function (name, callback, context) {\n                var me = this,\n                    set;\n\n                if (!callback) {\n                    return this;\n                }\n\n                set = this._events || (this._events = []);\n\n                eachEvent(name, callback, function (name, callback) {\n                    var handler = { e: name };\n\n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n\n                    set.push(handler);\n                });\n\n                return this;\n            },\n\n            /**\n            * 绑定事件，且当handler执行完后，自动解除绑定。\n            * @method once\n            * @grammar once( name, callback[, context] ) => self\n            * @param  {String}   name     事件名\n            * @param  {Function} callback 事件处理器\n            * @param  {Object}   [context]  事件处理器的上下文。\n            * @return {self} 返回自身，方便链式\n            * @chainable\n            */\n            once: function (name, callback, context) {\n                var me = this;\n\n                if (!callback) {\n                    return me;\n                }\n\n                eachEvent(name, callback, function (name, callback) {\n                    var once = function () {\n                        me.off(name, once);\n                        return callback.apply(context || me, arguments);\n                    };\n\n                    once._cb = callback;\n                    me.on(name, once, context);\n                });\n\n                return me;\n            },\n\n            /**\n            * 解除事件绑定\n            * @method off\n            * @grammar off( [name[, callback[, context] ] ] ) => self\n            * @param  {String}   [name]     事件名\n            * @param  {Function} [callback] 事件处理器\n            * @param  {Object}   [context]  事件处理器的上下文。\n            * @return {self} 返回自身，方便链式\n            * @chainable\n            */\n            off: function (name, cb, ctx) {\n                var events = this._events;\n\n                if (!events) {\n                    return this;\n                }\n\n                if (!name && !cb && !ctx) {\n                    this._events = [];\n                    return this;\n                }\n\n                eachEvent(name, cb, function (name, cb) {\n                    $.each(findHandlers(events, name, cb, ctx), function () {\n                        delete events[this.id];\n                    });\n                });\n\n                return this;\n            },\n\n            /**\n            * 触发事件\n            * @method trigger\n            * @grammar trigger( name[, args...] ) => self\n            * @param  {String}   type     事件名\n            * @param  {*} [...] 任意参数\n            * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n            */\n            trigger: function (type) {\n                var args, events, allEvents;\n\n                if (!this._events || !type) {\n                    return this;\n                }\n\n                args = slice.call(arguments, 1);\n                events = findHandlers(this._events, type);\n                allEvents = findHandlers(this._events, 'all');\n\n                return triggerHanders(events, args) &&\n                        triggerHanders(allEvents, arguments);\n            }\n        };\n\n        /**\n        * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n        * 主要目的是负责模块与模块之间的合作，降低耦合度。\n        *\n        * @class Mediator\n        */\n        return $.extend({\n\n            /**\n            * 可以通过这个接口，使任何对象具备事件功能。\n            * @method installTo\n            * @param  {Object} obj 需要具备事件行为的对象。\n            * @return {Object} 返回obj.\n            */\n            installTo: function (obj) {\n                return $.extend(obj, protos);\n            }\n\n        }, protos);\n    });\n    /**\n    * @fileOverview Uploader上传类\n    */\n    define('uploader', [\n        'base',\n        'mediator'\n    ], function (Base, Mediator) {\n\n        var $ = Base.$;\n\n        /**\n        * 上传入口类。\n        * @class Uploader\n        * @constructor\n        * @grammar new Uploader( opts ) => Uploader\n        * @example\n        * var uploader = WebUploader.Uploader({\n        *     swf: 'path_of_swf/Uploader.swf',\n        *\n        *     // 开起分片上传。\n        *     chunked: true\n        * });\n        */\n        function Uploader(opts) {\n            this.options = $.extend(true, {}, Uploader.options, opts);\n            this._init(this.options);\n        }\n\n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo(Uploader.prototype);\n\n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            md5File: 'md5-file',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function (fn, command) {\n            Uploader.prototype[fn] = function () {\n                return this.request(command, arguments);\n            };\n        });\n\n        $.extend(Uploader.prototype, {\n            state: 'pending',\n\n            _init: function (opts) {\n                var me = this;\n\n                me.request('init', opts, function () {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n\n            /**\n            * 获取或者设置Uploader配置项。\n            * @method option\n            * @grammar option( key ) => *\n            * @grammar option( key, val ) => self\n            * @example\n            *\n            * // 初始状态图片上传前不会压缩\n            * var uploader = new WebUploader.Uploader({\n            *     compress: null;\n            * });\n            *\n            * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n            * uploader.option( 'compress', {\n            *     width: 1600,\n            *     height: 1600\n            * });\n            */\n            option: function (key, val) {\n                var opts = this.options;\n\n                // setter\n                if (arguments.length > 1) {\n\n                    if ($.isPlainObject(val) &&\n                            $.isPlainObject(opts[key])) {\n                        $.extend(opts[key], val);\n                    } else {\n                        opts[key] = val;\n                    }\n\n                } else {    // getter\n                    return key ? opts[key] : opts;\n                }\n            },\n\n            /**\n            * 获取文件统计信息。返回一个包含一下信息的对象。\n            * * `successNum` 上传成功的文件数\n            * * `successNum` 上传中的文件数\n            * * `uploadFailNum` 上传失败的文件数\n            * * `cancelNum` 被删除的文件数\n            * * `invalidNum` 无效的文件数\n            * * `queueNum` 还在队列中的文件数\n            * @method getStats\n            * @grammar getStats() => Object\n            */\n            getStats: function () {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n\n                return {\n                    successNum: stats.numOfSuccess,\n                    progressNum: stats.numOfProgress,\n\n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function (type/*, args...*/) {\n                var args = [].slice.call(arguments, 1),\n                    opts = this.options,\n                    name = 'on' + type.substring(0, 1).toUpperCase() +\n                        type.substring(1);\n\n                if (\n                // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply(this, arguments) === false ||\n\n                // 调用opts.onEvent\n                        $.isFunction(opts[name]) &&\n                        opts[name].apply(this, args) === false ||\n\n                // 调用this.onEvent\n                        $.isFunction(this[name]) &&\n                        this[name].apply(this, args) === false ||\n\n                // 广播所有uploader的事件。\n                        Mediator.trigger.apply(Mediator,\n                        [this, type].concat(args)) === false) {\n\n                    return false;\n                }\n\n                return true;\n            },\n\n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n\n        /**\n        * 创建Uploader实例，等同于new Uploader( opts );\n        * @method create\n        * @class Base\n        * @static\n        * @grammar Base.create( opts ) => Uploader\n        */\n        Base.create = Uploader.create = function (opts) {\n            return new Uploader(opts);\n        };\n\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n\n        return Uploader;\n    });\n    /**\n    * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n    */\n    define('runtime/runtime', [\n        'base',\n        'mediator'\n    ], function (Base, Mediator) {\n\n        var $ = Base.$,\n            factories = {},\n\n        // 获取对象的第一个key\n            getFirstKey = function (obj) {\n                for (var key in obj) {\n                    if (obj.hasOwnProperty(key)) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n\n        // 接口类。\n        function Runtime(options) {\n            this.options = $.extend({\n                container: document.body\n            }, options);\n            this.uid = Base.guid('rt_');\n        }\n\n        $.extend(Runtime.prototype, {\n\n            getContainer: function () {\n                var opts = this.options,\n                    parent, container;\n\n                if (this._container) {\n                    return this._container;\n                }\n\n                parent = $(opts.container || document.body);\n                container = $(document.createElement('div'));\n\n                container.attr('id', 'rt_' + this.uid);\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: parent.width() || '1px',\n                    height: parent.height() || '1px',\n                    overflow: 'hidden'\n                });\n\n                parent.append(container);\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n\n            init: Base.noop,\n            exec: Base.noop,\n\n            destroy: function () {\n                if (this._container) {\n                    this._container.parentNode.removeChild(this.__container);\n                }\n\n                this.off();\n            }\n        });\n\n        Runtime.orders = 'html5,flash';\n\n\n        /**\n        * 添加Runtime实现。\n        * @param {String} type    类型\n        * @param {Runtime} factory 具体Runtime实现。\n        */\n        Runtime.addRuntime = function (type, factory) {\n            factories[type] = factory;\n        };\n\n        Runtime.hasRuntime = function (type) {\n            return !!(type ? factories[type] : getFirstKey(factories));\n        };\n\n        Runtime.create = function (opts, orders) {\n            var type, runtime;\n\n            orders = orders || Runtime.orders;\n            $.each(orders.split(/\\s*,\\s*/g), function () {\n                if (factories[this]) {\n                    type = this;\n                    return false;\n                }\n            });\n\n            type = type || getFirstKey(factories);\n\n            if (!type) {\n                alert('程序需要 Flash 11 以上版本才能正常运行，请前往：http://get.adobe.com/cn/flashplayer/ 安装');\n                throw new Error('Runtime Error');\n            }\n\n            runtime = new factories[type](opts);\n            return runtime;\n        };\n\n        Mediator.installTo(Runtime.prototype);\n        return Runtime;\n    });\n\n    /**\n    * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n    */\n    define('runtime/client', [\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function (Base, Mediator, Runtime) {\n\n        var cache;\n\n        cache = (function () {\n            var obj = {};\n\n            return {\n                add: function (runtime) {\n                    obj[runtime.uid] = runtime;\n                },\n\n                get: function (ruid, standalone) {\n                    var i;\n\n                    if (ruid) {\n                        return obj[ruid];\n                    }\n\n                    for (i in obj) {\n                        // 有些类型不能重用，比如filepicker.\n                        if (standalone && obj[i].__standalone) {\n                            continue;\n                        }\n\n                        return obj[i];\n                    }\n\n                    return null;\n                },\n\n                remove: function (runtime) {\n                    delete obj[runtime.uid];\n                }\n            };\n        })();\n\n        function RuntimeClient(component, standalone) {\n            var deferred = Base.Deferred(),\n                runtime;\n\n            this.uid = Base.guid('client_');\n\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function (cb) {\n                return deferred.done(cb);\n            };\n\n            this.connectRuntime = function (opts, cb) {\n\n                // already connected.\n                if (runtime) {\n                    throw new Error('already connected!');\n                }\n\n                deferred.done(cb);\n\n                if (typeof opts === 'string' && cache.get(opts)) {\n                    runtime = cache.get(opts);\n                }\n\n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get(null, standalone);\n\n                // 需要创建\n                if (!runtime) {\n                    runtime = Runtime.create(opts, opts.runtimeOrder);\n                    runtime.__promise = deferred.promise();\n                    runtime.once('ready', deferred.resolve);\n                    runtime.init();\n                    cache.add(runtime);\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend(runtime.options, opts);\n                    runtime.__promise.then(deferred.resolve);\n                    runtime.__client++;\n                }\n\n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n\n            this.getRuntime = function () {\n                return runtime;\n            };\n\n            this.disconnectRuntime = function () {\n                if (!runtime) {\n                    return;\n                }\n\n                runtime.__client--;\n\n                if (runtime.__client <= 0) {\n                    cache.remove(runtime);\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n\n                runtime = null;\n            };\n\n            this.exec = function () {\n                if (!runtime) {\n                    return;\n                }\n\n                var args = Base.slice(arguments);\n                component && args.unshift(component);\n\n                return runtime.exec.apply(this, args);\n            };\n\n            this.getRuid = function () {\n                return runtime && runtime.uid;\n            };\n\n            this.destroy = (function (destroy) {\n                return function () {\n                    destroy && destroy.apply(this, arguments);\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })(this.destroy);\n        }\n\n        Mediator.installTo(RuntimeClient.prototype);\n        return RuntimeClient;\n    });\n    /**\n    * @fileOverview 错误信息\n    */\n    define('lib/dnd', [\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function (Base, Mediator, RuntimeClent) {\n\n        var $ = Base.$;\n\n        function DragAndDrop(opts) {\n            opts = this.options = $.extend({}, DragAndDrop.options, opts);\n\n            opts.container = $(opts.container);\n\n            if (!opts.container.length) {\n                return;\n            }\n\n            RuntimeClent.call(this, 'DragAndDrop');\n        }\n\n        DragAndDrop.options = {\n            accept: null,\n            disableGlobalDnd: false\n        };\n\n        Base.inherits(RuntimeClent, {\n            constructor: DragAndDrop,\n\n            init: function () {\n                var me = this;\n\n                me.connectRuntime(me.options, function () {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n\n            destroy: function () {\n                this.disconnectRuntime();\n            }\n        });\n\n        Mediator.installTo(DragAndDrop.prototype);\n\n        return DragAndDrop;\n    });\n    /**\n    * @fileOverview 组件基类。\n    */\n    define('widgets/widget', [\n        'base',\n        'uploader'\n    ], function (Base, Uploader) {\n\n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n\n        function isArrayLike(obj) {\n            if (!obj) {\n                return false;\n            }\n\n            var length = obj.length,\n                type = $.type(obj);\n\n            if (obj.nodeType === 1 && length) {\n                return true;\n            }\n\n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n\n        function Widget(uploader) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n\n        $.extend(Widget.prototype, {\n\n            init: Base.noop,\n\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function (apiName, args) {\n\n                /*\n                {\n                'make-thumb': 'makeThumb'\n                }\n                */\n                var map = this.responseMap;\n\n                // 如果无API响应声明则忽略\n                if (!map || !(apiName in map) || !(map[apiName] in this) ||\n                        !$.isFunction(this[map[apiName]])) {\n\n                    return IGNORE;\n                }\n\n                return this[map[apiName]].apply(this, args);\n\n            },\n\n            /**\n            * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n            * @method request\n            * @grammar request( command, args ) => * | Promise\n            * @grammar request( command, args, callback ) => Promise\n            * @for  Uploader\n            */\n            request: function () {\n                return this.owner.request.apply(this.owner, arguments);\n            }\n        });\n\n        // 扩展Uploader.\n        $.extend(Uploader.prototype, {\n\n            // 覆写_init用来初始化widgets\n            _init: function () {\n                var me = this,\n                    widgets = me._widgets = [];\n\n                $.each(widgetClass, function (_, klass) {\n                    widgets.push(new klass(me));\n                });\n\n                return _init.apply(me, arguments);\n            },\n\n            request: function (apiName, args, callback) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n\n                args = isArrayLike(args) ? args : [args];\n\n                for (; i < len; i++) {\n                    widget = widgets[i];\n                    rlt = widget.invoke(apiName, args);\n\n                    if (rlt !== IGNORE) {\n\n                        // Deferred对象\n                        if (Base.isPromise(rlt)) {\n                            dfds.push(rlt);\n                        } else {\n                            rlts.push(rlt);\n                        }\n                    }\n                }\n\n                // 如果有callback，则用异步方式。\n                if (callback || dfds.length) {\n                    promise = Base.when.apply(Base, dfds);\n                    key = promise.pipe ? 'pipe' : 'then';\n\n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个 tick 中执行。\n                    return promise[key](function () {\n                        var deferred = Base.Deferred(),\n                                    args = arguments;\n\n                        if (args.length === 1) {\n                            args = args[0];\n                        }\n\n                        setTimeout(function () {\n                            deferred.resolve(args);\n                        }, 1);\n\n                        return deferred.promise();\n                    })[callback ? key : 'done'](callback || Base.noop);\n                } else {\n                    return rlts[0];\n                }\n            }\n        });\n\n        /**\n        * 添加组件\n        * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n        * @param  {object} responseMap API名称与函数实现的映射\n        * @example\n        *     Uploader.register( {\n        *         init: function( options ) {},\n        *         makeThumb: function() {}\n        *     }, {\n        *         'make-thumb': 'makeThumb'\n        *     } );\n        */\n        Uploader.register = Widget.register = function (responseMap, widgetProto) {\n            var map = { init: 'init' },\n                klass;\n\n            if (arguments.length === 1) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend(map, responseMap);\n            }\n\n            klass = Base.inherits(Widget, widgetProto);\n            widgetClass.push(klass);\n\n            return klass;\n        };\n\n        return Widget;\n    });\n    /**\n    * @fileOverview DragAndDrop Widget。\n    */\n    define('widgets/filednd', [\n        'base',\n        'uploader',\n        'lib/dnd',\n        'widgets/widget'\n    ], function (Base, Uploader, Dnd) {\n        var $ = Base.$;\n\n        Uploader.options.dnd = '';\n\n        /**\n        * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\n        * @namespace options\n        * @for Uploader\n        */\n\n        /**\n        * @property {Selector} [disableGlobalDnd=false]  是否禁掉整个页面的拖拽功能，如果不禁用，图片拖进来的时候会默认被浏览器打开。\n        * @namespace options\n        * @for Uploader\n        */\n\n        /**\n        * @event dndAccept\n        * @param {DataTransferItemList} items DataTransferItem\n        * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\n        * @for  Uploader\n        */\n        return Uploader.register({\n            init: function (opts) {\n\n                if (!opts.dnd ||\n                        this.request('predict-runtime-type') !== 'html5') {\n                    return;\n                }\n\n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        disableGlobalDnd: opts.disableGlobalDnd,\n                        container: opts.dnd,\n                        accept: opts.accept\n                    }),\n                    dnd;\n\n                dnd = new Dnd(options);\n\n                dnd.once('ready', deferred.resolve);\n                dnd.on('drop', function (files) {\n                    me.request('add-file', [files]);\n                });\n\n                // 检测文件是否全部允许添加。\n                dnd.on('accept', function (items) {\n                    return me.owner.trigger('dndAccept', items);\n                });\n\n                dnd.init();\n\n                return deferred.promise();\n            }\n        });\n    });\n\n    /**\n    * @fileOverview 错误信息\n    */\n    define('lib/filepaste', [\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function (Base, Mediator, RuntimeClent) {\n\n        var $ = Base.$;\n\n        function FilePaste(opts) {\n            opts = this.options = $.extend({}, opts);\n            opts.container = $(opts.container || document.body);\n            RuntimeClent.call(this, 'FilePaste');\n        }\n\n        Base.inherits(RuntimeClent, {\n            constructor: FilePaste,\n\n            init: function () {\n                var me = this;\n\n                me.connectRuntime(me.options, function () {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n\n            destroy: function () {\n                this.exec('destroy');\n                this.disconnectRuntime();\n                this.off();\n            }\n        });\n\n        Mediator.installTo(FilePaste.prototype);\n\n        return FilePaste;\n    });\n    /**\n    * @fileOverview 组件基类。\n    */\n    define('widgets/filepaste', [\n        'base',\n        'uploader',\n        'lib/filepaste',\n        'widgets/widget'\n    ], function (Base, Uploader, FilePaste) {\n        var $ = Base.$;\n\n        /**\n        * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\n        * @namespace options\n        * @for Uploader\n        */\n        return Uploader.register({\n            init: function (opts) {\n\n                if (!opts.paste ||\n                        this.request('predict-runtime-type') !== 'html5') {\n                    return;\n                }\n\n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        container: opts.paste,\n                        accept: opts.accept\n                    }),\n                    paste;\n\n                paste = new FilePaste(options);\n\n                paste.once('ready', deferred.resolve);\n                paste.on('paste', function (files) {\n                    me.owner.request('add-file', [files]);\n                });\n                paste.init();\n\n                return deferred.promise();\n            }\n        });\n    });\n    /**\n    * @fileOverview Blob\n    */\n    define('lib/blob', [\n        'base',\n        'runtime/client'\n    ], function (Base, RuntimeClient) {\n\n        function Blob(ruid, source) {\n            var me = this;\n\n            me.source = source;\n            me.ruid = ruid;\n            this.size = source.size || 0;\n\n            // 如果没有指定 mimetype, 但是知道文件后缀。\n            if (!source.type && ~'jpg,jpeg,png,gif,bmp'.indexOf(this.ext)) {\n                this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);\n            } else {\n                this.type = source.type || 'application/octet-stream';\n            }\n\n            RuntimeClient.call(me, 'Blob');\n            this.uid = source.uid || this.uid;\n\n            if (ruid) {\n                me.connectRuntime(ruid);\n            }\n        }\n\n        Base.inherits(RuntimeClient, {\n            constructor: Blob,\n\n            slice: function (start, end) {\n                return this.exec('slice', start, end);\n            },\n\n            getSource: function () {\n                return this.source;\n            }\n        });\n\n        return Blob;\n    });\n    /**\n    * 为了统一化Flash的File和HTML5的File而存在。\n    * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n    * @fileOverview File\n    */\n    define('lib/file', [\n        'base',\n        'lib/blob'\n    ], function (Base, Blob) {\n\n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n\n        function File(ruid, file) {\n            var ext;\n\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec(file.name) ? RegExp.$1.toLowerCase() : '';\n\n            // todo 支持其他类型文件的转换。\n            // 如果有 mimetype, 但是文件名里面没有找出后缀规律\n            if (!ext && file.type) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec(file.type) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n\n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n\n            Blob.apply(this, arguments);\n        }\n\n        return Base.inherits(Blob, File);\n    });\n\n    /**\n    * @fileOverview 错误信息\n    */\n    define('lib/filepicker', [\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function (Base, RuntimeClent, File) {\n\n        var $ = Base.$;\n\n        function FilePicker(opts) {\n            opts = this.options = $.extend({}, FilePicker.options, opts);\n\n            opts.container = $(opts.id);\n\n            if (!opts.container.length) {\n                throw new Error('按钮指定错误');\n            }\n\n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n\n            opts.button = $(opts.button || document.createElement('div'));\n            opts.button.html(opts.innerHTML);\n            opts.container.html(opts.button);\n\n            RuntimeClent.call(this, 'FilePicker', true);\n        }\n\n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: false,\n            accept: null,\n            name: 'file'\n        };\n\n        Base.inherits(RuntimeClent, {\n            constructor: FilePicker,\n\n            init: function () {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n\n                button.addClass('webuploader-pick');\n\n                me.on('all', function (type) {\n                    var files;\n\n                    switch (type) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n\n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n\n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger('select', $.map(files, function (file) {\n                                file = new File(me.getRuid(), file);\n\n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container);\n                            break;\n                    }\n                });\n\n                me.connectRuntime(opts, function () {\n                    me.refresh();\n                    me.exec('init', opts);\n                    me.trigger('ready');\n                });\n\n                $(window).on('resize', function () {\n                    me.refresh();\n                });\n            },\n\n            refresh: function () {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n\n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n\n                    pos = button.offset();\n\n                if(width < shimContainer.width()){\n                    width = shimContainer.width();\n                }\n                if(height < shimContainer.height()){\n                    height = shimContainer.height();\n                }\n\n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset(pos);\n            },\n\n            enable: function () {\n                var btn = this.options.button;\n\n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n\n            disable: function () {\n                var btn = this.options.button;\n\n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n\n                btn.addClass('webuploader-pick-disable');\n            },\n\n            destroy: function () {\n                if (this.runtime) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n\n        return FilePicker;\n    });\n\n    /**\n    * @fileOverview 文件选择相关\n    */\n    define('widgets/filepicker', [\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function (Base, Uploader, FilePicker) {\n        var $ = Base.$;\n\n        $.extend(Uploader.options, {\n\n            /**\n            * @property {Selector | Object} [pick=undefined]\n            * @namespace options\n            * @for Uploader\n            * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n            *\n            * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n            * * `label` {String} 请采用 `innerHTML` 代替\n            * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n            * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n            */\n            pick: null,\n\n            /**\n            * @property {Arroy} [accept=null]\n            * @namespace options\n            * @for Uploader\n            * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n            *\n            * * `title` {String} 文字描述\n            * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n            * * `mimeTypes` {String} 多个用逗号分割。\n            *\n            * 如：\n            *\n            * ```\n            * {\n            *     title: 'Images',\n            *     extensions: 'gif,jpg,jpeg,bmp,png',\n            *     mimeTypes: 'image/*'\n            * }\n            * ```\n            */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n\n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n\n            init: function (opts) {\n                this.pickers = [];\n                return opts.pick && this.addButton(opts.pick);\n            },\n\n            refresh: function () {\n                $.each(this.pickers, function () {\n                    this.refresh();\n                });\n            },\n\n            /**\n            * @method addButton\n            * @for Uploader\n            * @grammar addButton( pick ) => Promise\n            * @description\n            * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n            * @example\n            * uploader.addButton({\n            *     id: '#btnContainer',\n            *     innerHTML: '选择文件'\n            * });\n            */\n            addButton: function (pick) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    promises = [];\n\n                if (!pick) {\n                    return;\n                }\n\n                $.isPlainObject(pick) || (pick = {\n                    id: pick\n                });\n\n                $(pick.id).each(function () {\n                    var options, picker, deferred;\n\n                    deferred = Base.Deferred();\n\n                    options = $.extend({}, pick, {\n                        accept: $.isPlainObject(accept) ? [accept] : accept,\n                        swf: opts.swf,\n                        runtimeOrder: opts.runtimeOrder,\n                        id: this\n                    });\n\n                    picker = new FilePicker(options);\n\n                    picker.once('ready', deferred.resolve);\n                    picker.on('select', function (files) {\n                        me.owner.request('add-file', [files]);\n                    });\n                    picker.init();\n\n                    me.pickers.push(picker);\n\n                    promises.push(deferred.promise());\n                });\n\n                return Base.when.apply(Base, promises);\n            },\n\n            disable: function () {\n                $.each(this.pickers, function () {\n                    this.disable();\n                });\n            },\n\n            enable: function () {\n                $.each(this.pickers, function () {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n    * @fileOverview 文件属性封装\n    */\n    define('file', [\n        'base',\n        'mediator'\n    ], function (Base, Mediator) {\n\n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n\n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n\n        /**\n        * 文件类\n        * @class File\n        * @constructor 构造函数\n        * @grammar new File( source ) => File\n        * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n        */\n        function WUFile(source) {\n\n            /**\n            * 文件名，包括扩展名（后缀）\n            * @property name\n            * @type {string}\n            */\n            this.name = source.name || 'Untitled';\n\n            /**\n            * 文件体积（字节）\n            * @property size\n            * @type {uint}\n            * @default 0\n            */\n            this.size = source.size || 0;\n\n            /**\n            * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n            * @property type\n            * @type {string}\n            * @default 'application'\n            */\n            this.type = source.type || 'application';\n\n            /**\n            * 文件最后修改日期\n            * @property lastModifiedDate\n            * @type {int}\n            * @default 当前时间戳\n            */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n\n            /**\n            * 文件ID，每个对象具有唯一ID，与文件名无关\n            * @property id\n            * @type {string}\n            */\n            this.id = gid();\n\n            /**\n            * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n            * @property ext\n            * @type {string}\n            */\n            this.ext = rExt.exec(this.name) ? RegExp.$1 : '';\n\n\n            /**\n            * 状态文字说明。在不同的status语境下有不同的用途。\n            * @property statusText\n            * @type {string}\n            */\n            this.statusText = '';\n\n            // 存储文件状态，防止通过属性直接修改\n            statusMap[this.id] = WUFile.Status.INITED;\n\n            this.source = source;\n            this.loaded = 0;\n\n            this.on('error', function (msg) {\n                this.setStatus(WUFile.Status.ERROR, msg);\n            });\n        }\n\n        $.extend(WUFile.prototype, {\n\n            /**\n            * 设置状态，状态变化时会触发`change`事件。\n            * @method setStatus\n            * @grammar setStatus( status[, statusText] );\n            * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n            * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n            */\n            setStatus: function (status, text) {\n\n                var prevStatus = statusMap[this.id];\n\n                typeof text !== 'undefined' && (this.statusText = text);\n\n                if (status !== prevStatus) {\n                    statusMap[this.id] = status;\n                    /**\n                    * 文件状态变化\n                    * @event statuschange\n                    */\n                    this.trigger('statuschange', status, prevStatus);\n                }\n\n            },\n\n            /**\n            * 获取文件状态\n            * @return {File.Status}\n            * @example\n            文件状态具体包括以下几种类型：\n            {\n            // 初始化\n            INITED:     0,\n            // 已入队列\n            QUEUED:     1,\n            // 正在上传\n            PROGRESS:     2,\n            // 上传出错\n            ERROR:         3,\n            // 上传成功\n            COMPLETE:     4,\n            // 上传取消\n            CANCELLED:     5\n            }\n            */\n            getStatus: function () {\n                return statusMap[this.id];\n            },\n\n            /**\n            * 获取文件原始信息。\n            * @return {*}\n            */\n            getSource: function () {\n                return this.source;\n            },\n\n            destory: function () {\n                delete statusMap[this.id];\n            }\n        });\n\n        Mediator.installTo(WUFile.prototype);\n\n        /**\n        * 文件状态值，具体包括以下几种类型：\n        * * `inited` 初始状态\n        * * `queued` 已经进入队列, 等待上传\n        * * `progress` 上传中\n        * * `complete` 上传完成。\n        * * `error` 上传出错，可重试\n        * * `interrupt` 上传中断，可续传。\n        * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n        * * `cancelled` 文件被移除。\n        * @property {Object} Status\n        * @namespace File\n        * @class File\n        * @static\n        */\n        WUFile.Status = {\n            INITED: 'inited',    // 初始状态\n            QUEUED: 'queued',    // 已经进入队列, 等待上传\n            PROGRESS: 'progress',    // 上传中\n            ERROR: 'error',    // 上传出错，可重试\n            COMPLETE: 'complete',    // 上传完成。\n            CANCELLED: 'cancelled',    // 上传取消。\n            INTERRUPT: 'interrupt',    // 上传中断，可续传。\n            INVALID: 'invalid'    // 文件不合格，不能重试上传。\n        };\n\n        return WUFile;\n    });\n\n    /**\n    * @fileOverview 文件队列\n    */\n    define('queue', [\n        'base',\n        'mediator',\n        'file'\n    ], function (Base, Mediator, WUFile) {\n\n        var $ = Base.$,\n            STATUS = WUFile.Status;\n\n        /**\n        * 文件队列, 用来存储各个状态中的文件。\n        * @class Queue\n        * @extends Mediator\n        */\n        function Queue() {\n\n            /**\n            * 统计文件数。\n            * * `numOfQueue` 队列中的文件数。\n            * * `numOfSuccess` 上传成功的文件数\n            * * `numOfCancel` 被移除的文件数\n            * * `numOfProgress` 正在上传中的文件数\n            * * `numOfUploadFailed` 上传错误的文件数。\n            * * `numOfInvalid` 无效的文件数。\n            * @property {Object} stats\n            */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n\n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n\n            // 存储所有文件\n            this._map = {};\n        }\n\n        $.extend(Queue.prototype, {\n\n            /**\n            * 将新文件加入对队列尾部\n            *\n            * @method append\n            * @param  {File} file   文件对象\n            */\n            append: function (file) {\n                this._queue.push(file);\n                this._fileAdded(file);\n                return this;\n            },\n\n            /**\n            * 将新文件加入对队列头部\n            *\n            * @method prepend\n            * @param  {File} file   文件对象\n            */\n            prepend: function (file) {\n                this._queue.unshift(file);\n                this._fileAdded(file);\n                return this;\n            },\n\n            /**\n            * 获取文件对象\n            *\n            * @method getFile\n            * @param  {String} fileId   文件ID\n            * @return {File}\n            */\n            getFile: function (fileId) {\n                if (typeof fileId !== 'string') {\n                    return fileId;\n                }\n                return this._map[fileId];\n            },\n\n            /**\n            * 从队列中取出一个指定状态的文件。\n            * @grammar fetch( status ) => File\n            * @method fetch\n            * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n            * @return {File} [File](#WebUploader:File)\n            */\n            fetch: function (status) {\n                var len = this._queue.length,\n                    i, file;\n\n                status = status || STATUS.QUEUED;\n\n                for (i = 0; i < len; i++) {\n                    file = this._queue[i];\n\n                    if (status === file.getStatus()) {\n                        return file;\n                    }\n                }\n\n                return null;\n            },\n\n            /**\n            * 对队列进行排序，能够控制文件上传顺序。\n            * @grammar sort( fn ) => undefined\n            * @method sort\n            * @param {Function} fn 排序方法\n            */\n            sort: function (fn) {\n                if (typeof fn === 'function') {\n                    this._queue.sort(fn);\n                }\n            },\n\n            /**\n            * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n            * @grammar getFiles( [status1[, status2 ...]] ) => Array\n            * @method getFiles\n            * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n            */\n            getFiles: function () {\n                var sts = [].slice.call(arguments, 0),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n\n                for (; i < len; i++) {\n                    file = this._queue[i];\n\n                    if (sts.length && ! ~$.inArray(file.getStatus(), sts)) {\n                        continue;\n                    }\n\n                    ret.push(file);\n                }\n\n                return ret;\n            },\n\n            _fileAdded: function (file) {\n                var me = this,\n                    existing = this._map[file.id];\n\n                if (!existing) {\n                    this._map[file.id] = file;\n\n                    file.on('statuschange', function (cur, pre) {\n                        me._onFileStatusChange(cur, pre);\n                    });\n                }\n\n                file.setStatus(STATUS.QUEUED);\n            },\n\n            _onFileStatusChange: function (curStatus, preStatus) {\n                var stats = this.stats;\n\n                switch (preStatus) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n\n                    case STATUS.QUEUED:\n                        stats.numOfQueue--;\n                        break;\n\n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n\n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n\n                switch (curStatus) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n\n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n\n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n\n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n\n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n\n        });\n\n        Mediator.installTo(Queue.prototype);\n\n        return Queue;\n    });\n    /**\n    * @fileOverview 队列\n    */\n    define('widgets/queue', [\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function (Base, Uploader, Queue, WUFile, File, RuntimeClient) {\n\n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n\n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n\n            init: function (opts) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n\n                if ($.isPlainObject(opts.accept)) {\n                    opts.accept = [opts.accept];\n                }\n\n                // accept中的中生成匹配正则。\n                if (opts.accept) {\n                    arr = [];\n\n                    for (i = 0, len = opts.accept.length; i < len; i++) {\n                        item = opts.accept[i].extensions;\n                        item && arr.push(item);\n                    }\n\n                    if (arr.length) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace(/,/g, '$|\\\\.')\n                                .replace(/\\*/g, '.*') + '$';\n                    }\n\n                    me.accept = new RegExp(accept, 'i');\n                }\n\n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n\n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if (this.request('predict-runtime-type') !== 'html5') {\n                    return;\n                }\n\n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function () {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n\n\n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function (file) {\n                if (!(file instanceof WUFile)) {\n\n                    if (!(file instanceof File)) {\n                        if (!this._ruid) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File(this._ruid, file);\n                    }\n\n                    file = new WUFile(file);\n                }\n\n                return file;\n            },\n\n            // 判断文件是否可以被加入队列\n            acceptFile: function (file) {\n                var invalid = !file || file.size < 6 || this.accept &&\n\n                // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec(file.name) && !this.accept.test(file.name);\n\n                return !invalid;\n            },\n\n\n            /**\n            * @event beforeFileQueued\n            * @param {File} file File对象\n            * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n            * @for  Uploader\n            */\n\n            /**\n            * @event fileQueued\n            * @param {File} file File对象\n            * @description 当文件被加入队列以后触发。\n            * @for  Uploader\n            */\n\n            _addFile: function (file) {\n                var me = this;\n\n                file = me._wrapFile(file);\n\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if (!me.owner.trigger('beforeFileQueued', file)) {\n                    return;\n                }\n\n                // 类型不匹配，则派送错误事件，并返回。\n                if (!me.acceptFile(file)) {\n                    me.owner.trigger('error', 'Q_TYPE_DENIED', file);\n                    return;\n                }\n\n                me.queue.append(file);\n                me.owner.trigger('fileQueued', file);\n                return file;\n            },\n\n            getFile: function (fileId) {\n                return this.queue.getFile(fileId);\n            },\n\n            /**\n            * @event filesQueued\n            * @param {File} files 数组，内容为原始File(lib/File）对象。\n            * @description 当一批文件添加进队列以后触发。\n            * @for  Uploader\n            */\n\n            /**\n            * @method addFiles\n            * @grammar addFiles( file ) => undefined\n            * @grammar addFiles( [file1, file2 ...] ) => undefined\n            * @param {Array of File or File} [files] Files 对象 数组\n            * @description 添加文件到队列\n            * @for  Uploader\n            */\n            addFiles: function (files) {\n                var me = this;\n\n                if (!files.length) {\n                    files = [files];\n                }\n\n                files = $.map(files, function (file) {\n                    return me._addFile(file);\n                });\n\n                me.owner.trigger('filesQueued', files);\n\n                if (me.options.auto) {\n                    setTimeout(function () {\n                        me.request('start-upload');\n                    }, 20);\n                }\n            },\n\n            getStats: function () {\n                return this.stats;\n            },\n\n            /**\n            * @event fileDequeued\n            * @param {File} file File对象\n            * @description 当文件被移除队列后触发。\n            * @for  Uploader\n            */\n\n            /**\n            * @method removeFile\n            * @grammar removeFile( file ) => undefined\n            * @grammar removeFile( id ) => undefined\n            * @param {File|id} file File对象或这File对象的id\n            * @description 移除某一文件。\n            * @for  Uploader\n            * @example\n            *\n            * $li.on('click', '.remove-this', function() {\n            *     uploader.removeFile( file );\n            * })\n            */\n            removeFile: function (file) {\n                var me = this;\n\n                file = file.id ? file : me.queue.getFile(file);\n\n                file.setStatus(Status.CANCELLED);\n                me.owner.trigger('fileDequeued', file);\n            },\n\n            /**\n            * @method getFiles\n            * @grammar getFiles() => Array\n            * @grammar getFiles( status1, status2, status... ) => Array\n            * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n            * @for  Uploader\n            * @example\n            * console.log( uploader.getFiles() );    // => all files\n            * console.log( uploader.getFiles('error') )    // => all error files.\n            */\n            getFiles: function () {\n                return this.queue.getFiles.apply(this.queue, arguments);\n            },\n\n            fetchFile: function () {\n                return this.queue.fetch.apply(this.queue, arguments);\n            },\n\n            /**\n            * @method retry\n            * @grammar retry() => undefined\n            * @grammar retry( file ) => undefined\n            * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n            * @for  Uploader\n            * @example\n            * function retry() {\n            *     uploader.retry();\n            * }\n            */\n            retry: function (file, noForceStart) {\n                var me = this,\n                    files, i, len;\n\n                if (file) {\n                    file = file.id ? file : me.queue.getFile(file);\n                    file.setStatus(Status.QUEUED);\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n\n                files = me.queue.getFiles(Status.ERROR);\n                i = 0;\n                len = files.length;\n\n                for (; i < len; i++) {\n                    file = files[i];\n                    file.setStatus(Status.QUEUED);\n                }\n\n                me.request('start-upload');\n            },\n\n            /**\n            * @method sort\n            * @grammar sort( fn ) => undefined\n            * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n            * @for  Uploader\n            */\n            sortFiles: function () {\n                return this.queue.sort.apply(this.queue, arguments);\n            },\n\n            /**\n            * @event reset\n            * @description 当 uploader 被重置的时候触发。\n            * @for  Uploader\n            */\n\n            /**\n            * @method reset\n            * @grammar reset() => undefined\n            * @description 重置uploader。目前只重置了队列。\n            * @for  Uploader\n            * @example\n            * uploader.reset();\n            */\n            reset: function () {\n                this.owner.trigger('reset');\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n\n    });\n    /**\n    * @fileOverview 添加获取Runtime相关信息的方法。\n    */\n    define('widgets/runtime', [\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function (Uploader, Runtime) {\n\n        Uploader.support = function () {\n            return Runtime.hasRuntime.apply(Runtime, arguments);\n        };\n\n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n\n            init: function () {\n                if (!this.predictRuntmeType()) {\n                    throw Error('Runtime Error');\n                }\n            },\n\n            /**\n            * 预测Uploader将采用哪个`Runtime`\n            * @grammar predictRuntmeType() => String\n            * @method predictRuntmeType\n            * @for  Uploader\n            */\n            predictRuntmeType: function () {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n\n                if (!type) {\n                    orders = orders.split(/\\s*,\\s*/g);\n\n                    for (i = 0, len = orders.length; i < len; i++) {\n                        if (Runtime.hasRuntime(orders[i])) {\n                            this.type = type = orders[i];\n                            break;\n                        }\n                    }\n                }\n\n                return type;\n            }\n        });\n    });\n    /**\n    * @fileOverview Transport\n    */\n    define('lib/transport', [\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function (Base, RuntimeClient, Mediator) {\n\n        var $ = Base.$;\n\n        function Transport(opts) {\n            var me = this;\n\n            opts = me.options = $.extend(true, {}, Transport.options, opts || {});\n            RuntimeClient.call(this, 'Transport');\n\n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n\n            this.on('progress', this._timeout);\n            this.on('load error', function () {\n                me.trigger('progress', 1);\n                clearTimeout(me._timer);\n            });\n        }\n\n        Transport.options = {\n            server: '',\n            method: 'POST',\n\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n\n        $.extend(Transport.prototype, {\n\n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function (key, blob, filename) {\n                var me = this,\n                    opts = me.options;\n\n                if (me.getRuid()) {\n                    me.disconnectRuntime();\n                }\n\n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime(blob.ruid, function () {\n                    me.exec('init');\n                });\n\n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n\n            // 添加其他字段\n            append: function (key, value) {\n                if (typeof key === 'object') {\n                    $.extend(this._formData, key);\n                } else {\n                    this._formData[key] = value;\n                }\n            },\n\n            setRequestHeader: function (key, value) {\n                if (typeof key === 'object') {\n                    $.extend(this._headers, key);\n                } else {\n                    this._headers[key] = value;\n                }\n            },\n\n            send: function (method) {\n                this.exec('send', method);\n                this._timeout();\n            },\n\n            abort: function () {\n                clearTimeout(this._timer);\n                return this.exec('abort');\n            },\n\n            destroy: function () {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n\n            getResponse: function () {\n                return this.exec('getResponse');\n            },\n\n            getResponseAsJson: function () {\n                return this.exec('getResponseAsJson');\n            },\n\n            getStatus: function () {\n                return this.exec('getStatus');\n            },\n\n            _timeout: function () {\n                var me = this,\n                    duration = me.options.timeout;\n\n                if (!duration) {\n                    return;\n                }\n\n                clearTimeout(me._timer);\n                me._timer = setTimeout(function () {\n                    me.abort();\n                    me.trigger('error', 'timeout');\n                }, duration);\n            }\n\n        });\n\n        // 让Transport具备事件功能。\n        Mediator.installTo(Transport.prototype);\n\n        return Transport;\n    });\n    /**\n    * @fileOverview 负责文件上传相关。\n    */\n    define('widgets/upload', [\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function (Base, Uploader, WUFile, Transport) {\n\n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n\n        // 添加默认配置项\n        $.extend(Uploader.options, {\n\n\n            /**\n            * @property {Boolean} [prepareNextFile=false]\n            * @namespace options\n            * @for Uploader\n            * @description 是否允许在文件传输时提前把下一个文件准备好。\n            * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n            * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n            */\n            prepareNextFile: false,\n\n            /**\n            * @property {Boolean} [chunked=false]\n            * @namespace options\n            * @for Uploader\n            * @description 是否要分片处理大文件上传。\n            */\n            chunked: false,\n\n            /**\n            * @property {Boolean} [chunkSize=5242880]\n            * @namespace options\n            * @for Uploader\n            * @description 如果要分片，分多大一片？ 默认大小为5M.\n            */\n            chunkSize: 5 * 1024 * 1024,\n\n            /**\n            * @property {Boolean} [chunkRetry=2]\n            * @namespace options\n            * @for Uploader\n            * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n            */\n            chunkRetry: 2,\n\n            /**\n            * @property {Boolean} [threads=3]\n            * @namespace options\n            * @for Uploader\n            * @description 上传并发数。允许同时最大上传进程数。\n            */\n            threads: 3,\n\n\n            /**\n            * @property {Object} [formData]\n            * @namespace options\n            * @for Uploader\n            * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n            */\n            formData: null\n\n            /**\n            * @property {Object} [fileVal='file']\n            * @namespace options\n            * @for Uploader\n            * @description 设置文件上传域的name。\n            */\n\n            /**\n            * @property {Object} [method='POST']\n            * @namespace options\n            * @for Uploader\n            * @description 文件上传方式，`POST`或者`GET`。\n            */\n\n            /**\n            * @property {Object} [sendAsBinary=false]\n            * @namespace options\n            * @for Uploader\n            * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n            * 其他参数在$_GET数组中。\n            */\n        });\n\n        // 负责将文件切片。\n        function CuteFile(file, chunkSize) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil(total / chunkSize) : 1,\n                start = 0,\n                index = 0,\n                len;\n\n            while (index < chunks) {\n                len = Math.min(chunkSize, total - start);\n\n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n\n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n\n            return {\n                file: file,\n\n                has: function () {\n                    return !!pending.length;\n                },\n\n                fetch: function () {\n                    return pending.shift();\n                }\n            };\n        }\n\n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n\n            init: function () {\n                var owner = this.owner;\n\n                this.runing = false;\n\n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n\n                // 缓存即将上传的文件。\n                this.pending = [];\n\n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn(this._tick, this);\n\n                owner.on('uploadComplete', function (file) {\n                    // 把其他块取消了。\n                    file.blocks && $.each(file.blocks, function (_, v) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n\n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n\n            /**\n            * @event startUpload\n            * @description 当开始上传流程时触发。\n            * @for  Uploader\n            */\n\n            /**\n            * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n            * @grammar upload() => undefined\n            * @method upload\n            * @for  Uploader\n            */\n            start: function () {\n                var me = this;\n\n                // 移出invalid的文件\n                $.each(me.request('get-files', Status.INVALID), function () {\n                    me.request('remove-file', this);\n                });\n\n                if (me.runing) {\n                    return;\n                }\n\n                me.runing = true;\n\n                // 如果有暂停的，则续传\n                $.each(me.pool, function (_, v) {\n                    var file = v.file;\n\n                    if (file.getStatus() === Status.INTERRUPT) {\n                        file.setStatus(Status.PROGRESS);\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n\n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick(me.__tick);\n            },\n\n            /**\n            * @event stopUpload\n            * @description 当开始上传流程暂停时触发。\n            * @for  Uploader\n            */\n\n            /**\n            * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n            * @grammar stop() => undefined\n            * @grammar stop( true ) => undefined\n            * @method stop\n            * @for  Uploader\n            */\n            stop: function (interrupt) {\n                var me = this;\n\n                if (me.runing === false) {\n                    return;\n                }\n\n                me.runing = false;\n\n                interrupt && $.each(me.pool, function (_, v) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus(Status.INTERRUPT);\n                });\n\n                me.owner.trigger('stopUpload');\n            },\n\n            /**\n            * 判断`Uplaode`r是否正在上传中。\n            * @grammar isInProgress() => Boolean\n            * @method isInProgress\n            * @for  Uploader\n            */\n            isInProgress: function () {\n                return !!this.runing;\n            },\n\n            getStats: function () {\n                return this.request('get-stats');\n            },\n\n            /**\n            * 掉过一个文件上传，直接标记指定文件为已上传状态。\n            * @grammar skipFile( file ) => undefined\n            * @method skipFile\n            * @for  Uploader\n            */\n            skipFile: function (file, status) {\n                file = this.request('get-file', file);\n\n                file.setStatus(status || Status.COMPLETE);\n                file.skipped = true;\n\n                // 如果正在上传。\n                file.blocks && $.each(file.blocks, function (_, v) {\n                    var _tr = v.transport;\n\n                    if (_tr) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n\n                this.owner.trigger('uploadSkip', file);\n            },\n\n            /**\n            * @event uploadFinished\n            * @description 当所有文件上传结束时触发。\n            * @for  Uploader\n            */\n            _tick: function () {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n\n                // 上一个promise还没有结束，则等待完成后再执行。\n                if (me._promise) {\n                    return me._promise.always(me.__tick);\n                }\n\n                // 还有位置，且还有文件要处理的话。\n                if (me.pool.length < opts.threads && (val = me._nextBlock())) {\n                    me._trigged = false;\n\n                    fn = function (val) {\n                        me._promise = null;\n\n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend(val);\n                        Base.nextTick(me.__tick);\n                    };\n\n                    me._promise = isPromise(val) ? val.always(fn) : fn(val);\n\n                    // 没有要上传的了，且没有正在传输的了。\n                } else if (!me.remaning && !me.getStats().numOfQueue) {\n                    me.runing = false;\n\n                    me._trigged || Base.nextTick(function () {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n\n            _nextBlock: function () {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if (act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS) {\n\n                    // 是否提前准备下一个文件\n                    if (opts.prepareNextFile && !me.pending.length) {\n                        me._prepareNextFile();\n                    }\n\n                    return act.fetch();\n\n                    // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if (me.runing) {\n\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if (!me.pending.length && me.getStats().numOfQueue) {\n                        me._prepareNextFile();\n                    }\n\n                    next = me.pending.shift();\n                    done = function (file) {\n                        if (!file) {\n                            return null;\n                        }\n\n                        act = CuteFile(file, opts.chunked ? opts.chunkSize : 0);\n                        me._act = act;\n                        return act.fetch();\n                    };\n\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise(next) ?\n                            next[next.pipe ? 'pipe' : 'then'](done) :\n                            done(next);\n                }\n            },\n\n\n            /**\n            * @event uploadStart\n            * @param {File} file File对象\n            * @description 某个文件开始上传前触发，一个文件只会触发一次。\n            * @for  Uploader\n            */\n            _prepareNextFile: function () {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n\n                if (file) {\n                    promise = me.request('before-send-file', file, function () {\n\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if (file.getStatus() === Status.QUEUED) {\n                            me.owner.trigger('uploadStart', file);\n                            file.setStatus(Status.PROGRESS);\n                            return file;\n                        }\n\n                        return me._finishFile(file);\n                    });\n\n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function () {\n                        var idx = $.inArray(promise, pending);\n\n                        ~idx && pending.splice(idx, 1, file);\n                    });\n\n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function (reason) {\n                        file.setStatus(Status.ERROR, reason);\n                        me.owner.trigger('uploadError', file, reason);\n                        me.owner.trigger('uploadComplete', file);\n                    });\n\n                    pending.push(promise);\n                }\n            },\n\n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function (block) {\n                var idx = $.inArray(block, this.pool);\n\n                this.pool.splice(idx, 1);\n                block.file.remaning--;\n                this.remaning--;\n            },\n\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function (block) {\n                var me = this,\n                    file = block.file,\n                    promise;\n\n                me.pool.push(block);\n                me.remaning++;\n\n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice(block.start, block.end);\n\n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request('before-send', block, function () {\n\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if (file.getStatus() === Status.PROGRESS) {\n                        me._doSend(block);\n                    } else {\n                        me._popBlock(block);\n                        Base.nextTick(me.__tick);\n                    }\n                });\n\n                // 如果为fail了，则跳过此分片。\n                promise.fail(function () {\n                    if (file.remaning === 1) {\n                        me._finishFile(file).always(function () {\n                            block.percentage = 1;\n                            me._popBlock(block);\n                            me.owner.trigger('uploadComplete', file);\n                            Base.nextTick(me.__tick);\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock(block);\n                        Base.nextTick(me.__tick);\n                    }\n                });\n            },\n\n\n            /**\n            * @event uploadBeforeSend\n            * @param {Object} object\n            * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n            * @param {Object} headers 可以扩展此对象来控制上传头部。\n            * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n            * @for  Uploader\n            */\n\n            /**\n            * @event uploadAccept\n            * @param {Object} object\n            * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n            * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n            * @for  Uploader\n            */\n\n            /**\n            * @event uploadProgress\n            * @param {File} file File对象\n            * @param {Number} percentage 上传进度\n            * @description 上传过程中触发，携带上传进度。\n            * @for  Uploader\n            */\n\n\n            /**\n            * @event uploadError\n            * @param {File} file File对象\n            * @param {String} reason 出错的code\n            * @description 当文件上传出错时触发。\n            * @for  Uploader\n            */\n\n            /**\n            * @event uploadSuccess\n            * @param {File} file File对象\n            * @param {Object} response 服务端返回的数据\n            * @description 当文件上传成功时触发。\n            * @for  Uploader\n            */\n\n            /**\n            * @event uploadComplete\n            * @param {File} [file] File对象\n            * @description 不管成功或者失败，文件上传完成时触发。\n            * @for  Uploader\n            */\n\n            // 做上传操作。\n            _doSend: function (block) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport(opts),\n                    data = $.extend({}, opts.formData),\n                    headers = $.extend({}, opts.headers),\n                    requestAccept, ret;\n\n                block.transport = tr;\n\n                tr.on('destroy', function () {\n                    delete block.transport;\n                    me._popBlock(block);\n                    Base.nextTick(me.__tick);\n                });\n\n                // 广播上传进度。以文件为单位。\n                tr.on('progress', function (percentage) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n\n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n\n                    totalPercent = block.percentage = percentage;\n\n                    if (block.chunks > 1) {    // 计算文件的整体速度。\n                        $.each(file.blocks, function (_, v) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n\n                        totalPercent = uploaded / file.size;\n                    }\n\n                    owner.trigger('uploadProgress', file, totalPercent || 0);\n                });\n\n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function (reject) {\n                    var fn;\n\n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function (value) {\n                        reject = value;\n                    };\n\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if (!owner.trigger('uploadAccept', block, ret, fn)) {\n                        reject = reject || 'server';\n                    }\n\n                    return reject;\n                };\n\n                // 尝试重试，然后广播文件上传出错。\n                tr.on('error', function (type, flag) {\n                    block.retried = block.retried || 0;\n\n                    // 自动重试\n                    if (block.chunks > 1 && ~'http,abort'.indexOf(type) &&\n                            block.retried < opts.chunkRetry) {\n\n                        block.retried++;\n                        tr.send();\n\n                    } else {\n\n                        // http status 500 ~ 600\n                        if (!flag && type === 'server') {\n                            type = requestAccept(type);\n                        }\n\n                        file.setStatus(Status.ERROR, type);\n                        owner.trigger('uploadError', file, type);\n                        owner.trigger('uploadComplete', file);\n                    }\n                });\n\n                // 上传成功\n                tr.on('load', function () {\n                    var reason;\n\n                    // 如果非预期，转向上传出错。\n                    if ((reason = requestAccept())) {\n                        tr.trigger('error', reason, true);\n                        return;\n                    }\n\n                    // 全部上传完成。\n                    if (file.remaning === 1) {\n                        me._finishFile(file, ret);\n                    } else {\n                        tr.destroy();\n                    }\n                });\n\n                // 配置默认的上传字段。\n                data = $.extend(data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n\n                block.chunks > 1 && $.extend(data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n\n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger('uploadBeforeSend', block, data, headers);\n\n                // 开始发送。\n                tr.appendBlob(opts.fileVal, block.blob, file.name);\n                tr.append(data);\n                tr.setRequestHeader(headers);\n                tr.send();\n            },\n\n            // 完成上传。\n            _finishFile: function (file, ret, hds) {\n                var owner = this.owner;\n\n                return owner\n                        .request('after-send-file', arguments, function () {\n                            file.setStatus(Status.COMPLETE);\n                            owner.trigger('uploadSuccess', file, ret, hds);\n                        })\n                        .fail(function (reason) {\n\n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if (file.getStatus() === Status.PROGRESS) {\n                                file.setStatus(Status.ERROR, reason);\n                            }\n\n                            owner.trigger('uploadError', file, reason);\n                        })\n                        .always(function () {\n                            owner.trigger('uploadComplete', file);\n                        });\n            }\n\n        });\n    });\n    /**\n    * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\n    */\n\n    define('widgets/validator', [\n        'base',\n        'uploader',\n        'file',\n        'widgets/widget'\n    ], function (Base, Uploader, WUFile) {\n\n        var $ = Base.$,\n            validators = {},\n            api;\n\n        /**\n        * @event error\n        * @param {String} type 错误类型。\n        * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\n        *\n        * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\n        * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\n        * @for  Uploader\n        */\n\n        // 暴露给外面的api\n        api = {\n\n            // 添加验证器\n            addValidator: function (type, cb) {\n                validators[type] = cb;\n            },\n\n            // 移除验证器\n            removeValidator: function (type) {\n                delete validators[type];\n            }\n        };\n\n        // 在Uploader初始化的时候启动Validators的初始化\n        Uploader.register({\n            init: function () {\n                var me = this;\n                Base.nextTick(function () {\n                    $.each(validators, function () {\n                        this.call(me.owner);\n                    });\n                });\n            }\n        });\n\n        /**\n        * @property {int} [fileNumLimit=undefined]\n        * @namespace options\n        * @for Uploader\n        * @description 验证文件总数量, 超出则不允许加入队列。\n        */\n        api.addValidator('fileNumLimit', function () {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = parseInt(opts.fileNumLimit, 10),\n                flag = true;\n\n            if (!max) {\n                return;\n            }\n\n            uploader.on('beforeFileQueued', function (file) {\n\n                if (count >= max && flag) {\n                    flag = false;\n                    this.trigger('error', 'Q_EXCEED_NUM_LIMIT', max, file);\n                    setTimeout(function () {\n                        flag = true;\n                    }, 1);\n                }\n\n                return count >= max ? false : true;\n            });\n\n            uploader.on('fileQueued', function () {\n                count++;\n            });\n\n            uploader.on('fileDequeued', function () {\n                count--;\n            });\n\n            uploader.on('uploadFinished reset', function () {\n                count = 0;\n            });\n        });\n\n\n        /**\n        * @property {int} [fileSizeLimit=undefined]\n        * @namespace options\n        * @for Uploader\n        * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\n        */\n        api.addValidator('fileSizeLimit', function () {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileSizeLimit >> 0,\n                flag = true;\n\n            if (!max) {\n                return;\n            }\n\n            uploader.on('beforeFileQueued', function (file) {\n                var invalid = count + file.size > max;\n\n                if (invalid && flag) {\n                    flag = false;\n                    this.trigger('error', 'Q_EXCEED_SIZE_LIMIT', max, file);\n                    setTimeout(function () {\n                        flag = true;\n                    }, 1);\n                }\n\n                return invalid ? false : true;\n            });\n\n            uploader.on('fileQueued', function (file) {\n                count += file.size;\n            });\n\n            uploader.on('fileDequeued', function (file) {\n                count -= file.size;\n            });\n\n            uploader.on('uploadFinished reset', function () {\n                count = 0;\n            });\n        });\n\n        /**\n        * @property {int} [fileSingleSizeLimit=undefined]\n        * @namespace options\n        * @for Uploader\n        * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\n        */\n        api.addValidator('fileSingleSizeLimit', function () {\n            var uploader = this,\n                opts = uploader.options,\n                max = opts.fileSingleSizeLimit;\n\n            if (!max) {\n                return;\n            }\n\n            uploader.on('beforeFileQueued', function (file) {\n\n                if (file.size > max) {\n                    file.setStatus(WUFile.Status.INVALID, 'exceed_size');\n                    this.trigger('error', 'F_EXCEED_SIZE', file);\n                    return false;\n                }\n\n            });\n\n        });\n\n        /**\n        * @property {int} [duplicate=undefined]\n        * @namespace options\n        * @for Uploader\n        * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\n        */\n        api.addValidator('duplicate', function () {\n            var uploader = this,\n                opts = uploader.options,\n                mapping = {};\n\n            if (opts.duplicate) {\n                return;\n            }\n\n            function hashString(str) {\n                var hash = 0,\n                    i = 0,\n                    len = str.length,\n                    _char;\n\n                for (; i < len; i++) {\n                    _char = str.charCodeAt(i);\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\n                }\n\n                return hash;\n            }\n\n            uploader.on('beforeFileQueued', function (file) {\n                var hash = file.__hash || (file.__hash = hashString(file.name +\n                        file.size + file.lastModifiedDate));\n\n                // 已经重复了\n                if (mapping[hash]) {\n                    this.trigger('error', 'F_DUPLICATE', file);\n                    return false;\n                }\n            });\n\n            uploader.on('fileQueued', function (file) {\n                var hash = file.__hash;\n\n                hash && (mapping[hash] = true);\n            });\n\n            uploader.on('fileDequeued', function (file) {\n                var hash = file.__hash;\n\n                hash && (delete mapping[hash]);\n            });\n\n            uploader.on('reset', function () {\n                mapping = {};\n            });\n        });\n\n        return api;\n    });\n\n    /**\n    * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n    */\n    define('runtime/compbase', [], function () {\n\n        function CompBase(owner, runtime) {\n\n            this.owner = owner;\n            this.options = owner.options;\n\n            this.getRuntime = function () {\n                return runtime;\n            };\n\n            this.getRuid = function () {\n                return runtime.uid;\n            };\n\n            this.trigger = function () {\n                return owner.trigger.apply(owner, arguments);\n            };\n        }\n\n        return CompBase;\n    });\n    /**\n    * @fileOverview Html5Runtime\n    */\n    define('runtime/html5/runtime', [\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function (Base, Runtime, CompBase) {\n\n        var type = 'html5',\n            components = {};\n\n        function Html5Runtime() {\n            var pool = {},\n                me = this,\n                destory = this.destory;\n\n            Runtime.apply(me, arguments);\n            me.type = type;\n\n\n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function (comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice(arguments, 2),\n                    instance;\n\n                if (components[comp]) {\n                    instance = pool[uid] = pool[uid] ||\n                            new components[comp](client, me);\n\n                    if (instance[fn]) {\n                        return instance[fn].apply(instance, args);\n                    }\n                }\n            };\n\n            me.destory = function () {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply(this, arguments);\n            };\n        }\n\n        Base.inherits(Runtime, {\n            constructor: Html5Runtime,\n\n            // 不需要连接其他程序，直接执行callback\n            init: function () {\n                var me = this;\n                setTimeout(function () {\n                    me.trigger('ready');\n                }, 1);\n            }\n\n        });\n\n        // 注册Components\n        Html5Runtime.register = function (name, component) {\n            var klass = components[name] = Base.inherits(CompBase, component);\n            return klass;\n        };\n\n        // 注册html5运行时。\n        // 只有在支持的前提下注册。\n        if (window.Blob && window.FileReader && window.DataView) {\n            Runtime.addRuntime(type, Html5Runtime);\n        }\n\n        return Html5Runtime;\n    });\n    /**\n    * @fileOverview Blob Html实现\n    */\n    define('runtime/html5/blob', [\n        'runtime/html5/runtime',\n        'lib/blob'\n    ], function (Html5Runtime, Blob) {\n\n        return Html5Runtime.register('Blob', {\n            slice: function (start, end) {\n                var blob = this.owner.source,\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\n\n                blob = slice.call(blob, start, end);\n\n                return new Blob(this.getRuid(), blob);\n            }\n        });\n    });\n    /**\n    * @fileOverview FilePaste\n    */\n    define('runtime/html5/dnd', [\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function (Base, Html5Runtime, File) {\n\n        var $ = Base.$,\n            prefix = 'webuploader-dnd-';\n\n        return Html5Runtime.register('DragAndDrop', {\n            init: function () {\n                var elem = this.elem = this.options.container;\n\n                this.dragEnterHandler = Base.bindFn(this._dragEnterHandler, this);\n                this.dragOverHandler = Base.bindFn(this._dragOverHandler, this);\n                this.dragLeaveHandler = Base.bindFn(this._dragLeaveHandler, this);\n                this.dropHandler = Base.bindFn(this._dropHandler, this);\n                this.dndOver = false;\n\n                elem.on('dragenter', this.dragEnterHandler);\n                elem.on('dragover', this.dragOverHandler);\n                elem.on('dragleave', this.dragLeaveHandler);\n                elem.on('drop', this.dropHandler);\n\n                if (this.options.disableGlobalDnd) {\n                    $(document).on('dragover', this.dragOverHandler);\n                    $(document).on('drop', this.dropHandler);\n                }\n            },\n\n            _dragEnterHandler: function (e) {\n                var me = this,\n                    denied = me._denied || false,\n                    items;\n\n                e = e.originalEvent || e;\n\n                if (!me.dndOver) {\n                    me.dndOver = true;\n\n                    // 注意只有 chrome 支持。\n                    items = e.dataTransfer.items;\n\n                    if (items && items.length) {\n                        me._denied = denied = !me.trigger('accept', items);\n                    }\n\n                    me.elem.addClass(prefix + 'over');\n                    me.elem[denied ? 'addClass' :\n                            'removeClass'](prefix + 'denied');\n                }\n\n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\n\n                return false;\n            },\n\n            _dragOverHandler: function (e) {\n                // 只处理框内的。\n                var parentElem = this.elem.parent().get(0);\n                if (parentElem && !$.contains(parentElem, e.currentTarget)) {\n                    return false;\n                }\n\n                clearTimeout(this._leaveTimer);\n                this._dragEnterHandler.call(this, e);\n\n                return false;\n            },\n\n            _dragLeaveHandler: function () {\n                var me = this,\n                    handler;\n\n                handler = function () {\n                    me.dndOver = false;\n                    me.elem.removeClass(prefix + 'over ' + prefix + 'denied');\n                };\n\n                clearTimeout(me._leaveTimer);\n                me._leaveTimer = setTimeout(handler, 100);\n                return false;\n            },\n\n            _dropHandler: function (e) {\n                var me = this,\n                    ruid = me.getRuid(),\n                    parentElem = me.elem.parent().get(0),\n                    dataTransfer, data;\n\n                // 只处理框内的。\n                if (parentElem && !$.contains(parentElem, e.currentTarget)) {\n                    return false;\n                }\n\n                e = e.originalEvent || e;\n                dataTransfer = e.dataTransfer;\n\n                // 如果是页面内拖拽，还不能处理，不阻止事件。\n                // 此处 ie11 下会报参数错误，\n                try {\n                    data = dataTransfer.getData('text/html');\n                } catch (err) {\n                }\n\n                if (data) {\n                    return;\n                }\n\n                me._getTansferFiles(dataTransfer, function (results) {\n                    me.trigger('drop', $.map(results, function (file) {\n                        return new File(ruid, file);\n                    }));\n                });\n\n                me.dndOver = false;\n                me.elem.removeClass(prefix + 'over');\n                return false;\n            },\n\n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\n            _getTansferFiles: function (dataTransfer, callback) {\n                var results = [],\n                    promises = [],\n                    items, files, file, item, i, len, canAccessFolder;\n\n                items = dataTransfer.items;\n                files = dataTransfer.files;\n\n                canAccessFolder = !!(items && items[0].webkitGetAsEntry);\n\n                for (i = 0, len = files.length; i < len; i++) {\n                    file = files[i];\n                    item = items && items[i];\n\n                    if (canAccessFolder && item.webkitGetAsEntry().isDirectory) {\n\n                        promises.push(this._traverseDirectoryTree(\n                                item.webkitGetAsEntry(), results));\n                    } else {\n                        results.push(file);\n                    }\n                }\n\n                Base.when.apply(Base, promises).done(function () {\n\n                    if (!results.length) {\n                        return;\n                    }\n\n                    callback(results);\n                });\n            },\n\n            _traverseDirectoryTree: function (entry, results) {\n                var deferred = Base.Deferred(),\n                    me = this;\n\n                if (entry.isFile) {\n                    entry.file(function (file) {\n                        results.push(file);\n                        deferred.resolve();\n                    });\n                } else if (entry.isDirectory) {\n                    entry.createReader().readEntries(function (entries) {\n                        var len = entries.length,\n                            promises = [],\n                            arr = [],    // 为了保证顺序。\n                            i;\n\n                        for (i = 0; i < len; i++) {\n                            promises.push(me._traverseDirectoryTree(\n                                    entries[i], arr));\n                        }\n\n                        Base.when.apply(Base, promises).then(function () {\n                            results.push.apply(results, arr);\n                            deferred.resolve();\n                        }, deferred.reject);\n                    });\n                }\n\n                return deferred.promise();\n            },\n\n            destroy: function () {\n                var elem = this.elem;\n\n                elem.off('dragenter', this.dragEnterHandler);\n                elem.off('dragover', this.dragEnterHandler);\n                elem.off('dragleave', this.dragLeaveHandler);\n                elem.off('drop', this.dropHandler);\n\n                if (this.options.disableGlobalDnd) {\n                    $(document).off('dragover', this.dragOverHandler);\n                    $(document).off('drop', this.dropHandler);\n                }\n            }\n        });\n    });\n\n    /**\n    * @fileOverview FilePaste\n    */\n    define('runtime/html5/filepaste', [\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function (Base, Html5Runtime, File) {\n\n        return Html5Runtime.register('FilePaste', {\n            init: function () {\n                var opts = this.options,\n                    elem = this.elem = opts.container,\n                    accept = '.*',\n                    arr, i, len, item;\n\n                // accetp的mimeTypes中生成匹配正则。\n                if (opts.accept) {\n                    arr = [];\n\n                    for (i = 0, len = opts.accept.length; i < len; i++) {\n                        item = opts.accept[i].mimeTypes;\n                        item && arr.push(item);\n                    }\n\n                    if (arr.length) {\n                        accept = arr.join(',');\n                        accept = accept.replace(/,/g, '|').replace(/\\*/g, '.*');\n                    }\n                }\n                this.accept = accept = new RegExp(accept, 'i');\n                this.hander = Base.bindFn(this._pasteHander, this);\n                elem.on('paste', this.hander);\n            },\n\n            _pasteHander: function (e) {\n                var allowed = [],\n                    ruid = this.getRuid(),\n                    items, item, blob, i, len;\n\n                e = e.originalEvent || e;\n                items = e.clipboardData.items;\n\n                for (i = 0, len = items.length; i < len; i++) {\n                    item = items[i];\n\n                    if (item.kind !== 'file' || !(blob = item.getAsFile())) {\n                        continue;\n                    }\n\n                    allowed.push(new File(ruid, blob));\n                }\n\n                if (allowed.length) {\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\n                    e.preventDefault();\n                    e.stopPropagation();\n                    this.trigger('paste', allowed);\n                }\n            },\n\n            destroy: function () {\n                this.elem.off('paste', this.hander);\n            }\n        });\n    });\n\n    /**\n    * @fileOverview FilePicker\n    */\n    define('runtime/html5/filepicker', [\n        'base',\n        'runtime/html5/runtime'\n    ], function (Base, Html5Runtime) {\n\n        var $ = Base.$;\n\n        return Html5Runtime.register('FilePicker', {\n            init: function () {\n                var container = this.getRuntime().getContainer(),\n                    me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    lable = $(document.createElement('label')),\n                    input = $(document.createElement('input')),\n                    arr, i, len, mouseHandler;\n\n                input.attr('type', 'file');\n                input.attr('name', opts.name);\n                input.addClass('webuploader-element-invisible');\n\n                lable.on('click', function () {\n                    input.trigger('click');\n                });\n\n                lable.css({\n                    opacity: 0,\n                    width: '100%',\n                    height: '100%',\n                    display: 'block',\n                    cursor: 'pointer',\n                    background: '#ffffff'\n                });\n\n                if (opts.multiple) {\n                    input.attr('multiple', 'multiple');\n                }\n\n                // @todo Firefox不支持单独指定后缀\n                if (opts.accept && opts.accept.length > 0) {\n                    arr = [];\n\n                    for (i = 0, len = opts.accept.length; i < len; i++) {\n                        arr.push(opts.accept[i].mimeTypes);\n                    }\n\n                    input.attr('accept', arr.join(','));\n                }\n\n                container.append(input);\n                container.append(lable);\n\n                mouseHandler = function (e) {\n                    owner.trigger(e.type);\n                };\n\n                input.on('change', function (e) {\n                    var fn = arguments.callee,\n                        clone;\n\n                    me.files = e.target.files;\n\n                    // reset input\n                    clone = this.cloneNode(true);\n                    clone.value = null;\n                    this.parentNode.replaceChild(clone, this);\n\n                    input.off();\n                    input = $(clone).on('change', fn)\n                            .on('mouseenter mouseleave', mouseHandler);\n\n                    owner.trigger('change');\n                });\n\n                lable.on('mouseenter mouseleave', mouseHandler);\n\n            },\n\n\n            getFiles: function () {\n                return this.files;\n            },\n\n            destroy: function () {\n                // todo\n            }\n        });\n    });\n    /**\n    * @fileOverview Transport\n    * @todo 支持chunked传输，优势：\n    * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\n    * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\n    */\n    define('runtime/html5/transport', [\n        'base',\n        'runtime/html5/runtime'\n    ], function (Base, Html5Runtime) {\n\n        var noop = Base.noop,\n            $ = Base.$;\n\n        return Html5Runtime.register('Transport', {\n            init: function () {\n                this._status = 0;\n                this._response = null;\n            },\n\n            send: function () {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    formData, binary, fr;\n\n                if (opts.sendAsBinary) {\n                    server += (/\\?/.test(server) ? '&' : '?') +\n                            $.param(owner._formData);\n\n                    binary = blob.getSource();\n                } else {\n                    formData = new FormData();\n                    $.each(owner._formData, function (k, v) {\n                        formData.append(k, v);\n                    });\n\n                    formData.append(opts.fileVal, blob.getSource(),\n                            opts.filename || owner._formData.name || '');\n                }\n\n                if (opts.withCredentials && 'withCredentials' in xhr) {\n                    xhr.open(opts.method, server, true);\n                    xhr.withCredentials = true;\n                } else {\n                    xhr.open(opts.method, server);\n                }\n\n                this._setRequestHeader(xhr, opts.headers);\n\n                if (binary) {\n                    xhr.overrideMimeType('application/octet-stream');\n\n                    // android直接发送blob会导致服务端接收到的是空文件。\n                    // bug详情。\n                    // https://code.google.com/p/android/issues/detail?id=39882\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\n                    if (Base.os.android) {\n                        fr = new FileReader();\n\n                        fr.onload = function () {\n                            xhr.send(this.result);\n                            fr = fr.onload = null;\n                        };\n\n                        fr.readAsArrayBuffer(binary);\n                    } else {\n                        xhr.send(binary);\n                    }\n                } else {\n                    xhr.send(formData);\n                }\n            },\n\n            getResponse: function () {\n                return this._response;\n            },\n\n            getResponseAsJson: function () {\n                return this._parseJson(this._response);\n            },\n\n            getStatus: function () {\n                return this._status;\n            },\n\n            abort: function () {\n                var xhr = this._xhr;\n\n                if (xhr) {\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    xhr.abort();\n\n                    this._xhr = xhr = null;\n                }\n            },\n\n            destroy: function () {\n                this.abort();\n            },\n\n            _initAjax: function () {\n                var me = this,\n                    xhr = new XMLHttpRequest(),\n                    opts = this.options;\n\n                if (opts.withCredentials && !('withCredentials' in xhr) &&\n                        typeof XDomainRequest !== 'undefined') {\n                    xhr = new XDomainRequest();\n                }\n\n                xhr.upload.onprogress = function (e) {\n                    var percentage = 0;\n\n                    if (e.lengthComputable) {\n                        percentage = e.loaded / e.total;\n                    }\n\n                    return me.trigger('progress', percentage);\n                };\n\n                xhr.onreadystatechange = function () {\n\n                    if (xhr.readyState !== 4) {\n                        return;\n                    }\n\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    me._xhr = null;\n                    me._status = xhr.status;\n\n                    if (xhr.status >= 200 && xhr.status < 300) {\n                        me._response = xhr.responseText;\n                        return me.trigger('load');\n                    } else if (xhr.status >= 500 && xhr.status < 600) {\n                        me._response = xhr.responseText;\n                        return me.trigger('error', 'server');\n                    }\n\n\n                    return me.trigger('error', me._status ? 'http' : 'abort');\n                };\n\n                me._xhr = xhr;\n                return xhr;\n            },\n\n            _setRequestHeader: function (xhr, headers) {\n                $.each(headers, function (key, val) {\n                    xhr.setRequestHeader(key, val);\n                });\n            },\n\n            _parseJson: function (str) {\n                var json;\n\n                try {\n                    json = JSON.parse(str);\n                } catch (ex) {\n                    json = {};\n                }\n\n                return json;\n            }\n        });\n    });\n    /**\n    * @fileOverview FlashRuntime\n    */\n    define('runtime/flash/runtime', [\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function (Base, Runtime, CompBase) {\n\n        var $ = Base.$,\n            type = 'flash',\n            components = {};\n\n\n        function getFlashVersion() {\n            var version;\n\n            try {\n                version = navigator.plugins['Shockwave Flash'];\n                version = version.description;\n            } catch (ex) {\n                try {\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\n                            .GetVariable('$version');\n                } catch (ex2) {\n                    version = '0.0';\n                }\n            }\n            version = version.match(/\\d+/g);\n            return parseFloat(version[0] + '.' + version[1], 10);\n        }\n\n        function FlashRuntime() {\n            var pool = {},\n                clients = {},\n                destory = this.destory,\n                me = this,\n                jsreciver = Base.guid('webuploader_');\n\n            Runtime.apply(me, arguments);\n            me.type = type;\n\n\n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function (comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice(arguments, 2),\n                    instance;\n\n                clients[uid] = client;\n\n                if (components[comp]) {\n                    if (!pool[uid]) {\n                        pool[uid] = new components[comp](client, me);\n                    }\n\n                    instance = pool[uid];\n\n                    if (instance[fn]) {\n                        return instance[fn].apply(instance, args);\n                    }\n                }\n\n                return me.flashExec.apply(client, arguments);\n            };\n\n            function handler(evt, obj) {\n                var type = evt.type || evt,\n                    parts, uid;\n\n                parts = type.split('::');\n                uid = parts[0];\n                type = parts[1];\n\n                // console.log.apply( console, arguments );\n\n                if (type === 'Ready' && uid === me.uid) {\n                    me.trigger('ready');\n                } else if (clients[uid]) {\n                    clients[uid].trigger(type.toLowerCase(), evt, obj);\n                }\n\n                // Base.log( evt, obj );\n            }\n\n            // flash的接受器。\n            window[jsreciver] = function () {\n                var args = arguments;\n\n                // 为了能捕获得到。\n                setTimeout(function () {\n                    handler.apply(null, args);\n                }, 1);\n            };\n\n            this.jsreciver = jsreciver;\n\n            this.destory = function () {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply(this, arguments);\n            };\n\n            this.flashExec = function (comp, fn) {\n                var flash = me.getFlash(),\n                    args = Base.slice(arguments, 2);\n\n                return flash.exec(this.uid, comp, fn, args);\n            };\n\n            // @todo\n        }\n\n        Base.inherits(Runtime, {\n            constructor: FlashRuntime,\n\n            init: function () {\n                var container = this.getContainer(),\n                    opts = this.options,\n                    html;\n\n                // if not the minimal height, shims are not initialized\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\n                container.css({\n                    position: 'absolute',\n                    top: '-8px',\n                    left: '-8px',\n                    width: '9px',\n                    height: '9px',\n                    overflow: 'hidden'\n                });\n\n                // insert flash object\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\n                        'x-shockwave-flash\" data=\"' + opts.swf + '\" ';\n\n                if (Base.browser.ie) {\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\n                }\n\n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">' +\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\n                    '&jsreciver=' + this.jsreciver + '\" />' +\n                    '<param name=\"wmode\" value=\"transparent\" />' +\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\n                '</object>';\n\n                container.html(html);\n            },\n\n            getFlash: function () {\n                if (this._flash) {\n                    return this._flash;\n                }\n\n                this._flash = $('#' + this.uid).get(0);\n                return this._flash;\n            }\n\n        });\n\n        FlashRuntime.register = function (name, component) {\n            component = components[name] = Base.inherits(CompBase, $.extend({\n\n                // @todo fix this later\n                flashExec: function () {\n                    var owner = this.owner,\n                        runtime = this.getRuntime();\n\n                    return runtime.flashExec.apply(owner, arguments);\n                }\n            }, component));\n\n            return component;\n        };\n\n        if (getFlashVersion() >= 11.4) {\n            Runtime.addRuntime(type, FlashRuntime);\n        }\n\n        return FlashRuntime;\n    });\n    /**\n    * @fileOverview FilePicker\n    */\n    define('runtime/flash/filepicker', [\n        'base',\n        'runtime/flash/runtime'\n    ], function (Base, FlashRuntime) {\n        var $ = Base.$;\n\n        return FlashRuntime.register('FilePicker', {\n            init: function (opts) {\n                var copy = $.extend({}, opts),\n                    len, i;\n\n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\n                len = copy.accept && copy.accept.length;\n                for (i = 0; i < len; i++) {\n                    if (!copy.accept[i].title) {\n                        copy.accept[i].title = 'Files';\n                    }\n                }\n\n                delete copy.button;\n                delete copy.id;\n                delete copy.container;\n\n                this.flashExec('FilePicker', 'init', copy);\n            },\n\n            destroy: function () {\n                // todo\n            }\n        });\n    });\n    /**\n    * @fileOverview  Transport flash实现\n    */\n    define('runtime/flash/transport', [\n        'base',\n        'runtime/flash/runtime',\n        'runtime/client'\n    ], function (Base, FlashRuntime, RuntimeClient) {\n        var $ = Base.$;\n\n        return FlashRuntime.register('Transport', {\n            init: function () {\n                this._status = 0;\n                this._response = null;\n                this._responseJson = null;\n            },\n\n            send: function () {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    binary;\n\n                xhr.connectRuntime(blob.ruid);\n\n                if (opts.sendAsBinary) {\n                    server += (/\\?/.test(server) ? '&' : '?') +\n                            $.param(owner._formData);\n\n                    binary = blob.uid;\n                } else {\n                    $.each(owner._formData, function (k, v) {\n                        xhr.exec('append', k, v);\n                    });\n\n                    xhr.exec('appendBlob', opts.fileVal, blob.uid,\n                            opts.filename || owner._formData.name || '');\n                }\n\n                this._setRequestHeader(xhr, opts.headers);\n                xhr.exec('send', {\n                    method: opts.method,\n                    url: server,\n                    mimeType: 'application/octet-stream'\n                }, binary);\n            },\n\n            getStatus: function () {\n                return this._status;\n            },\n\n            getResponse: function () {\n                return this._response || '';\n            },\n\n            getResponseAsJson: function () {\n                return this._responseJson;\n            },\n\n            abort: function () {\n                var xhr = this._xhr;\n\n                if (xhr) {\n                    xhr.exec('abort');\n                    xhr.destroy();\n                    this._xhr = xhr = null;\n                }\n            },\n\n            destroy: function () {\n                this.abort();\n            },\n\n            _initAjax: function () {\n                var me = this,\n                    xhr = new RuntimeClient('XMLHttpRequest');\n\n                xhr.on('uploadprogress progress', function (e) {\n                    var percent = e.loaded / e.total;\n                    percent = Math.min(1, Math.max(0, percent));\n                    return me.trigger('progress', percent);\n                });\n\n                xhr.on('load', function () {\n                    var status = xhr.exec('getStatus'),\n                        err = '';\n\n                    xhr.off();\n                    me._xhr = null;\n\n                    if (status >= 200 && status < 300) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                    } else if (status >= 500 && status < 600) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                        err = 'server';\n                    } else {\n                        err = 'http';\n                    }\n\n                    me._response = decodeURIComponent(me._response);\n                    xhr.destroy();\n                    xhr = null;\n\n                    return err ? me.trigger('error', err) : me.trigger('load');\n                });\n\n                xhr.on('error', function () {\n                    xhr.off();\n                    me._xhr = null;\n                    me.trigger('error', 'http');\n                });\n\n                me._xhr = xhr;\n                return xhr;\n            },\n\n            _setRequestHeader: function (xhr, headers) {\n                $.each(headers, function (key, val) {\n                    xhr.exec('setRequestHeader', key, val);\n                });\n            }\n        });\n    });\n    /**\n    * @fileOverview 没有图像处理的版本。\n    */\n    define('preset/withoutimage', [\n        'base',\n\n    // widgets\n        'widgets/filednd',\n        'widgets/filepaste',\n        'widgets/filepicker',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'widgets/validator',\n\n    // runtimes\n    // html5\n        'runtime/html5/blob',\n        'runtime/html5/dnd',\n        'runtime/html5/filepaste',\n        'runtime/html5/filepicker',\n        'runtime/html5/transport',\n\n    // flash\n        'runtime/flash/filepicker',\n        'runtime/flash/transport'\n    ], function (Base) {\n        return Base;\n    });\n    define('webuploader', [\n        'preset/withoutimage'\n    ], function (preset) {\n        return preset;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "public/stylesheets/common.css",
    "content": "body, p, input, textarea {\n    font-size: 14px;\n    word-break: break-word;\n}\n\ntextarea, input[type=\"text\"],\ninput[type=\"password\"],\ninput[type=\"datetime\"],\ninput[type=\"datetime-local\"],\ninput[type=\"date\"],\ninput[type=\"month\"],\ninput[type=\"time\"],\ninput[type=\"week\"],\ninput[type=\"number\"],\ninput[type=\"email\"],\ninput[type=\"url\"],\ninput[type=\"search\"],\ninput[type=\"tel\"],\ninput[type=\"color\"],\n.uneditable-input {\n    background: hsla(0, 0%, 0%, 0)\n}\n\npre {\n    background: #fee9cc;\n    border: 1px dashed #ccc;\n    line-height: 22px;\n}\n\ncode {\n    padding: 0;\n    border: none;\n}\n\np code {\n    background: none;\n    color: hsl(0, 0%, 50%);\n    margin: 0 1px;\n    padding: 1px 4px;\n    border-radius: 1px;\n}\n\ndiv pre.prettyprint {\n    font-size: 14px;\n    border-radius: 0px;\n    padding: 0 15px;\n    border: none;\n    margin: 20px -10px;\n    border-width: 1px 0px;\n    background: #f7f7f7;\n    -o-tab-size: 4;\n    -moz-tab-size: 4;\n    tab-size: 4;\n}\n\nform {\n    margin-bottom: 0;\n}\n\ntextarea {\n    margin-bottom: 0;\n}\n\ninput, textarea {\n    background: hsla(0, 0%, 0%, 0);\n}\n"
  },
  {
    "path": "public/stylesheets/jquery.atwho.css",
    "content": ".atwho-view {\n    position:absolute;\n    top: 0;\n    left: 0;\n    display: none;\n    margin-top: 18px;\n    background: white;\n    color: black;\n    border: 1px solid #DDD;\n    border-radius: 3px;\n    box-shadow: 0 0 5px rgba(0,0,0,0.1);\n    min-width: 120px;\n    z-index: 11110 !important;\n}\n\n.atwho-view .cur {\n    background: #3366FF;\n    color: white;\n}\n.atwho-view .cur small {\n    color: white;\n}\n.atwho-view strong {\n    color: #3366FF;\n}\n.atwho-view .cur strong {\n    color: white;\n    font:bold;\n}\n.atwho-view ul {\n    /* width: 100px; */\n    list-style:none;\n    padding:0;\n    margin:auto;\n}\n.atwho-view ul li {\n    display: block;\n    padding: 5px 10px;\n    border-bottom: 1px solid #DDD;\n    cursor: pointer;\n    /* border-top: 1px solid #C8C8C8; */\n}\n.atwho-view small {\n    font-size: smaller;\n    color: #777;\n    font-weight: normal;\n}\n"
  },
  {
    "path": "public/stylesheets/responsive.css",
    "content": "@-ms-viewport {\n  width: device-width;\n}\n\n#sidebar-mask {\n  background-color: #333;\n  width: 100%;\n  height: 100%;\n  filter: alpha(opacity=60);\n  opacity: .6;\n  z-index: 99;\n  position: absolute;\n  top: 0;\n  left: 0;\n  display: none;\n}\n\n@media (max-width: 400px) {\n  .navbar .brand {\n      float: none;\n      margin: 0 auto;\n  }\n\n  .navbar .navbar-search {\n      clear: both;\n      margin: 0 auto;\n      float: none;\n  }\n\n  .navbar .search-query {\n      display: block;\n      margin: 0 auto;\n  }\n}\n\n@media (max-width: 979px) {\n\n  .navbar {\n      margin: 0 5px;\n      z-index: 999;\n      width: auto !important;\n  }\n\n  .navbar .container, #main,\n  #content, #footer_main {\n      width: 100%;\n      min-width: 0;\n  }\n\n  .navbar .nav.pull-right {\n      float: none;\n      clear: both;\n  }\n\n  #responsive-sidebar-trigger {\n      display: none;\n  }\n\n  #main {\n      /*overflow: hidden;*/\n      margin: 20px auto;\n      min-height: 0;\n  }\n\n  #content .panel {\n      margin: 0 5px;\n  }\n\n  #sidebar {\n      float: none;\n      position: absolute;\n      right: -100%;\n      top: 0;\n      background-color: #fff;\n      z-index: 999;\n      border: 5px solid #ccc;\n      border-right: 0;\n      -webkit-transition: .3s right;\n      -moz-transition: .3s right;\n      -ms-transition: .3s right;\n      -o-transition: .3s right;\n      transition: .3s right;\n      display: none;\n  }\n\n  #content .topic_title {\n      font-size: 1em;\n      width: 100%;\n  }\n\n  #content .last_time {\n      position: absolute;\n      bottom: 0;\n      right: 10px;\n      font-size: .8em;\n  }\n\n  #content .last_time img {\n      display: none;\n  }\n\n  #content .reply_count {\n      position: absolute;\n      bottom: 0;\n      left: 85px;\n      text-align: left;\n      line-height: 2em;\n      font-size: 10px;\n  }\n\n  .topic_title_wrapper {\n      padding-left: 40px;\n  }\n\n  #main .topic_content p a.content_img,\n  #main .reply_content p a.content_img {\n      width: 100%;\n  }\n\n  #footer {\n      margin: 0 5px 5px;\n  }\n\n  #footer_main {\n    display: none;\n  }\n\n  #backtotop {\n      background-color: #f5f5f5;\n      border: 1px solid #ccc;\n      border-right: 0;\n  }\n\n  .form-horizontal .control-label {\n      float: none;\n      width: auto;\n      padding-top: 0;\n      text-align: left;\n  }\n\n  .form-horizontal .controls {\n      margin-left: 0;\n  }\n\n  .form-horizontal .control-list {\n      padding-top: 0;\n  }\n\n  .form-horizontal .form-actions {\n      padding-right: 10px;\n      padding-left: 10px;\n  }\n\n  #content .reply_content {\n      clear: both;\n      padding-left: 0;\n      padding-top: 5px;\n  }\n\n  #content .action {\n    display: none;\n  }\n\n  .user_profile {\n    margin-top: 0;\n  }\n}\n"
  },
  {
    "path": "public/stylesheets/style.less",
    "content": "@gray1: #e1e1e1;\n@gray2: #f6f6f6;\n@node_green: #80bd01;\n@node_black: #444444;\n\n/* base */\nbody {\n  background-color: @gray1;\n  font-family: \"Helvetica Neue\", \"Luxi Sans\", \"DejaVu Sans\", Tahoma, \"Hiragino Sans GB\", STHeiti, sans-serif !important;\n}\n\n#main {\n  width: 90%;\n  max-width: 1400px;\n  min-width: 960px;\n  margin: 15px auto;\n  min-height: 400px;\n  position: relative;\n}\n\n#topic_list {\n  margin: 0;\n\n  .cell:hover {\n    background: #f5f5f5;\n  }\n}\n\n#content {\n  padding: 0;\n  margin-right: 305px;\n}\n\n#sidebar {\n  width: 290px;\n  font-size: 14px;\n  float: right;\n  margin-bottom: 20px;\n}\n\n#content .panel {\n}\n\n#content .topic_full_title {\n  font-size: 22px;\n  font-weight: bold;\n  margin: 8px 0;\n  display: inline-block;\n  vertical-align: bottom;\n  width: 75%;\n  line-height: 130%;\n}\n\n#content .changes {\n  font-size: 12px;\n  color: #838383;\n  overflow: hidden;\n\n  span:before {\n    content: \"•\"\n  }\n\n  a {\n    color: inherit;\n  }\n}\n\n#content .action {\n  float: right;\n  margin-top: 8px;\n}\n#content .share {\n  text-align: right;\n}\n#content h3 {\n}\n\n#manage_topic a {\n  text-decoration: none;\n  margin-right: 0.5em;\n}\n\n#sidebar .panel {\n  font-size: 13px;\n}\n\n#sidebar .header {\n  color: hsl(202, 6%, 34%);\n  border-radius: 3px 3px 0 0;\n}\n\n#sidebar .topic_title {\n  max-width: 270px;\n  font-size: 14px;\n}\n\n#footer {\n  clear: both;\n  position: relative;\n  background: white;\n  &.fix-bottom{\n      position: fixed;\n      bottom: 0;\n      width:100%;\n  }\n}\n\n#footer_main {\n  width: 90%;\n  max-width: 1400px;\n  min-width: 960px;\n  margin: 0px auto;\n  color: #e2e2e2;\n  padding: 20px 0px;\n  font-size: 13px;\n  line-height: 2em;\n  .col_fade {\n    a {\n      vertical-align: bottom;\n    }\n    p {\n      line-height: 20px;\n      vertical-align: bottom;\n    }\n  }\n}\n\n#mark_all_messages_btn {\n  margin: 5px 0px;\n}\n\n#forgot_password {\n  margin-left: 1em;\n}\n\n/* common */\n.col_fade {\n  color: hsl(0, 0%, 67%);\n}\n\n.header .col_fade {\n  color: @node_black;\n}\n\n.bold {\n  font-weight: bold;\n}\n\na.dark {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  text-decoration: none;\n  color: #666;\n}\n\na.dark:link, a.dark:visited, a.dark:active {\n  color: #666;\n  text-decoration: none;\n}\n\na.dark:hover {\n  color: #385f8a;\n}\n\na.light:link, a.light:visited, a.light:active {\n  color: #005580;\n  text-decoration: none;\n}\n\na.light:hover {\n  color: gray;\n  text-decoration: none;\n}\n\n.unstyled li div {\n  overflow: hidden;\n}\n\n.panel {\n  margin-bottom: 13px;\n}\n\n.breadcrumb > li {\n  text-shadow: none;\n}\n\n.breadcrumb a {\n  color: @node_green;\n}\n\n.panel .header {\n  padding: 10px 10px;\n  background-color: @gray2;\n  border-radius: 3px 3px 0 0;\n\n  &.topic_header {\n    background-color: white;\n  }\n}\n\n.panel .moon {\n  padding: 10px;\n}\n\n.panel .moon_tags {\n  padding-left: 10px;\n}\n\n.panel .inner .unstyled li div {\n  text-overflow: ellipsis;\n}\n\n.panel .inner.topic,\n.panel .inner.reply,\n.panel .inner.userinfo,\n.panel .inner.post {\n  padding: 10px;\n  border-top: 1px solid #e5e5e5;\n}\n\n.panel .inner {\n  line-height: 2em;\n  padding: 10px;\n  background-color: white;\n  border-radius: 0 0 3px 3px;\n  a {\n    color: #778087;\n  }\n}\n\n.panel .inner > form.form-horizontal {\n  margin-top: 40px;\n}\n\n.panel .inner.no-padding {\n  padding: 0;\n}\n\n.panel .inner li {\n  line-height: 2em;\n}\n\n.panel > .cell:last-child {\n  box-shadow: none;\n}\n\n.cell {\n  overflow: hidden;\n  position: relative;\n  padding: 10px 0px 10px 10px;\n  font-size: 14px;\n}\n\n.panel .cell {\n  padding-right: 10px;\n  background: white;\n  border-top: 1px solid rgb(240, 240, 240);\n  &:nth-child(1) {\n    border-top: none;\n  }\n}\n\n.cell .reply_count {\n  width: 70px;\n  display: inline-block;\n  text-align: center;\n}\n\n.cell .count_of_replies {\n  color: rgb(158, 120, 192);\n}\n\n.cell .count_seperator {\n  margin: 0 -3px;\n  font-size: 10px;\n}\n\n.cell .count_of_visits {\n  font-size: 10px;\n  color: rgb(180, 180, 180);\n}\n\n.cell .cell {\n  padding: 10px 0px 0px 10px;\n}\n\n.cell:last-child {\n  border-bottom: none;\n}\n\n.cell.message, .cell[message_id] {\n  padding: 10px;\n}\n\n.cell.message a, .cell[message_id] a {\n  max-width: 460px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: inline-block;\n  vertical-align: middle;\n}\n\n.cell.more {\n  padding: 10px;\n}\n\n.last_time {\n  font-size: 11px;\n  display: inline-block;\n  margin-left: 20px;\n  color: #777;\n}\n\n.last_time .last_active_time {\n  text-align: right;\n  min-width: 50px;\n  display: inline-block;\n  white-space: nowrap;\n}\n\na.last_time {\n  text-decoration: none;\n}\n\n.last_time .user_small_avatar {\n  height: 18px;\n  width: 18px;\n  vertical-align: middle;\n  margin-right: 0.5em;\n  border-radius: 3px;\n}\n\n.board .floor {\n}\n\n.board a {\n  color: #404040;\n}\n\n.board a:hover {\n  text-decoration: none;\n}\n\n.big {\n  font-size: 14px;\n}\n\n.messages_count {\n  -webkit-border-radius: 8px;\n  -moz-border-radius: 8px;\n  -o-border-radius: 8px;\n  border-radius: 8px;\n\n  padding: 1px 5px;\n  background-color: @node_green;\n  color: white;\n  margin-right: 0.5em;\n}\n\n.edit_tag {\n  margin-left: 8px;\n}\n\n.tag a {\n  color: #fff;\n  text-decoration: none;\n  font-weight: bold;\n}\n\n.tag a:hover {\n  color: #ccc;\n}\n\na.tag_big {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  border-radius: 3px;\n\n  padding: 2px 5px;\n  margin: 0 8px 5px 0;\n  display: inline-block;\n  text-decoration: none;\n}\n\n.tag_selectable {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  border-radius: 3px;\n\n  display: inline-block;\n  padding: 3px 5px;\n  margin: 0px 10px 10px 0px;\n  border: 1px solid #ccc;\n  cursor: pointer;\n}\n\n.tag_select {\n  color: #0099ff;\n}\n\n.tag_in_topic {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  border-radius: 3px;\n\n  font-size: 14px;\n  padding: 1px 3px;\n  margin: 0px 10px 10px 0px;\n  background: #bfbfbf;\n  display: inline-block;\n  cursor: pointer;\n}\n\n.tag_in_topic a {\n  color: #fff;\n  text-decoration: none;\n}\n\n.tag_in_topic a:hover {\n  color: #0099ff;\n}\n\n.tag_in_abstract {\n  color: #fff;\n  background: hsl(221, 28%, 83%);\n  font-size: 14px;\n  font-weight: bold;\n  padding: 1px 2px;\n  text-decoration: none;\n  margin: 0px 2px;\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  border-radius: 3px;\n}\n\n.tag_in_abstract:hover {\n  background: hsl(221, 28%, 73%);\n}\n\n.tag_with_num {\n  margin-right: 10px;\n  display: inline-block;\n}\n\n.tag_num {\n  font-size: 11px;\n  margin-left: -10px;\n}\n\n.tag_highlight {\n  font-size: 14px;\n  font-weight: bold;\n  color: #fff;\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  border-radius: 3px;\n  padding: 1px 2px;\n}\n\n.user_avatar img, .user_big_avatar img {\n  width: 30px;\n  height: 30px;\n  border-radius: 3px;\n}\n\na.user_avatar:hover {\n  text-decoration: none;\n}\n\n.user_card .user_avatar {\n  vertical-align: middle;\n  margin-right: 0.5em;\n  img {\n    width: 48px;\n    height: 48px;\n  }\n}\n\n.user_card .user_name {\n  font-size: 16px;\n}\n\n.cell .user_avatar {\n}\n\n.user_big_avatar {\n  float: left;\n  margin-right: 10px;\n}\n\n.user_big_avatar img {\n  width: 40px;\n  height: 40px;\n}\n\n.fa {\n  color: #000000;\n  opacity: 0.4;\n}\n\n.fa:hover {\n  color: #000000;\n  opacity: 0.6;\n}\n\n.user {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  border-radius: 3px;\n\n  -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.15);\n  -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.15);\n  -o-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.15);\n  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.15);\n\n  width: 200px;\n  height: 80px;\n  border: 1px dashed #e2e2e2;\n  padding: 10px;\n  margin-right: 20px;\n  margin-bottom: 20px;\n  display: inline-block;\n}\n\n.user .user_avatar {\n  width: 32px;\n  height: 32px;\n}\n\n.user .user_name {\n  font-size: 14px;\n  margin-left: 1em;\n}\n\n.user_name {\n  max-width: 120px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  display: inline-block;\n  vertical-align: middle;\n}\n\n.reply_author {\n  font-size: 12px;\n  font-weight: bold;\n}\n\n.reply_by_author {\n  color: #fff;\n  background-color: #6ba44e;\n  padding: 2px;\n  font-size: 12px;\n}\n\n.reply_time {\n  font-size: 11px;\n}\n\n.cell .anchor {\n  position: absolute;\n  top: -5px;\n}\n\n.author_content .user_avatar {\n  display: inline-block;\n  float: left;\n}\n\n.user_info {\n  margin-left: 10px;\n  display: inline-block;\n}\n\n.user_action {\n  float: right;\n  margin-left: 20px;\n  font-size: 15px;\n\n  a {\n    text-decoration: none;\n  }\n\n  .up-count {\n    color: gray;\n  }\n}\n\n.reply_content {\n  padding-left: 50px;\n  color: #333;\n}\n\n.reply_editor {\n  height: 200px;\n}\n\n.cell.reply_highlight {\n  background-color: #f4fcf0;\n}\n\n.reply2_btn, .reply2_at_btn {\n  opacity: 0.4;\n  cursor: pointer;\n}\n\n.reply2_btn:hover, .reply2_at_btn:hover {\n  opacity: 0.6;\n}\n\n.reply2_area {\n  margin-left: 42px;\n}\n\n.reply2_form {\n  display: none;\n  margin-top: 2px;\n}\n\n.submit_control {\n  height: 48px;\n  line-height: 48px;\n}\n\na.topic_title {\n  max-width: 70%;\n  text-overflow: ellipsis;\n  -o-text-overflow: ellipsis;\n  white-space: nowrap;\n  overflow: hidden;\n  display: inline-block;\n  vertical-align: middle;\n  font-size: 16px;\n  line-height: 30px;\n}\n\n#topic_list a.topic_title {\n  color: #333;\n}\n\n#topic_list a.topic_title:visited {\n  color: #888;\n}\n\n.topic_title_wrapper {\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.put_top, .put_good {\n  background: @node_green;\n  padding: 2px 4px;\n  border-radius: 3px;\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  color: white;\n  font-size: 12px;\n}\n\n.star_name {\n}\n\nimg.unread {\n  opacity: 0.1;\n  cursor: pointer;\n}\n\n/* single */\n#create_topic_btn {\n}\n\n.board {\n  margin-top: 10px;\n  width: 80%;\n}\n\n.tags_select {\n  border: 1px dashed #ccc;\n  padding: 10px;\n}\n\n.user_profile {\n  margin-top: 20px;\n  clear: left;\n}\n\n#backtotop {\n  width: 24px;\n  color: gray;\n  padding: 12px 0px 12px 5px;\n  display: none;\n  position: fixed;\n  cursor: pointer;\n  text-align: center;\n  z-index: 20;\n  background-color: white;\n  border-radius: 12px 0px 0px 12px;\n}\n\n.tabbable ul {\n  max-height: 40px;\n}\n\n/* markdown editor */\n.markdown-text p, .preview p {\n  white-space: pre-wrap; /* CSS3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  line-height: 2em;\n  margin: 1em 0;\n}\n\n.markdown-text > *:first-child, .preview > *:first-child {\n  margin-top: 0;\n}\n\n.markdown-text > *:last-child, .preview > *:last-child {\n  margin-bottom: 1em;\n}\n\n.markdown-text li, .preview li {\n  font-size: 14px;\n  line-height: 2em;\n}\n\n.markdown-text p code, .preview p code,\n.markdown-text li code, .preview li code {\n  color: black;\n  background-color: #fcfafa;\n  padding: 4px 6px;\n}\n\n\n.markdown-text img {\n  cursor: pointer;\n}\n\n.markdown-text {\n  h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n    font-size: inherit;\n    color: inherit;\n  }\n}\n\n.panel .markdown-text a {\n  color: #08c;\n}\n\n.preview {\n  padding: 0.5em;\n  font-size: 15px;\n  min-height: 200px;\n  word-break: break-all;\n}\n\n.preview p > img {\n  display: block;\n\n  box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.6);\n}\n\n.markdown_editor .markdown_in_preview,\n.markdown_editor .markdown_in_editor {\n  display: none;\n}\n\n.markdown_editor.in_preview .markdown_in_preview {\n  display: block;\n}\n\n.markdown_editor.in_editor .markdown_in_editor {\n  display: block;\n}\n\ntextarea#title {\n  width: 98%;\n  border: none;\n  margin-bottom: 1em;\n  resize: none;\n  height: 20px;\n}\n\n.editor_buttons {\n  margin-top: 10px;\n}\n\n.editor_buttons > button {\n  vertical-align: baseline;\n}\n\n/* custom bootstrap */\n.navbar {\n  margin-bottom: 0px;\n  z-index: 9;\n  width: 100%;\n  position: relative;\n  background: @node_black;\n  font-size: 13px;\n}\n\n.navbar-inner {\n  background: none;\n  border-radius: 0px;\n  border: none;\n  box-shadow: none;\n  width: 90%;\n  margin: auto;\n  padding: 5px;\n}\n\n.navbar .container {\n  width: 100%;\n  min-width: 960px;\n  margin: 0px auto;\n  max-width: 1400px;\n}\n\n.navbar .brand {\n  padding: 4px 0px 0px 20px;\n  width: 120px;\n  padding: 3px 20px;\n  height: 34px;\n  line-height: 34px;\n  text-shadow: none;\n  color: #cccccc;\n  font-weight: 700;\n\n  img {\n    vertical-align: initial;   \n  }\n}\n\n.navbar .navbar-search {\n  margin-top: 6px;\n}\n\n.navbar .search-query {\n  -webkit-box-shadow: none;\n  -moz-box-shadow: none;\n  background: #888 url('../images/search.png') no-repeat 4px 4px;\n  padding: 3px 5px 3px 22px;\n  color: #666;\n  border: 0px;\n  margin-top: 2px;\n\n  &:hover {\n    background-color: white;\n  }\n  transition: all 0.5s;\n\n  &:focus, &.focused {\n    background-color: white;\n  }\n}\n\n.navbar .nav .divider-vertical {\n  background-color: #2b3741;\n  border-right: 1px solid #2f3b46;\n}\n\n.navbar .nav > li > a {\n  text-shadow: none;\n  color: #cccccc;\n  &:hover {\n    color: #fff;\n  }\n}\n\n.form-actions {\n  border-top: none;\n  background-color: white;\n  max-height: 40px;\n}\n\n.btn {\n  padding: 3px 14px;\n}\n\n.breadcrumb {\n  padding: 0px;\n  margin: 0px;\n  border: none;\n  background: none;\n  box-shadow: none;\n}\n\n.nav-pills {\n  margin: 0px 0px 10px 0px;\n}\n\n.nav-pills > li > a {\n  padding: 2px 10px;\n  line-height: 22px;\n}\n\n.pagination {\n  height: 40px;\n  margin: 10px 0px 0px 10px;\n  ul {\n    box-shadow: none;\n  }\n}\n\n.pagination a {\n  line-height: 30px;\n  color: @gray2;\n}\n\n.pagination li.active a {\n  color: @node_green;\n}\n\n.topic_content {\n  margin: 0 10px;\n}\n\n.preview h1, .preview h2, .preview h3,\n.preview h4, .preview h5, .preview h6,\n.reply_area h1, .reply_area h2, .reply_area h3,\n.reply_area h4, .reply_area h5, .reply_area h6,\n.topic_content h1, .topic_content h2, .topic_content h3,\n.topic_content h4, .topic_content h5, .topic_content h6 {\n  margin: 30px 0 15px 0;\n  border-bottom: 1px solid #eee;\n}\n\n.topic_content h1 {\n  font-size: 13 * 2.5px;\n}\n\n.topic_content h2 {\n  font-size: 26px;\n}\n\n.topic_content p,\n.reply_content p,\n.reply_form p,\n.preview p {\n  font-size: 15px;\n  line-height: 1.7em;\n  overflow: auto;\n}\n\n#main .topic_content p a.content_img,\n#main .reply_content p a.content_img {\n  color: hsl(0, 0%, 40%);\n  box-shadow: 0px 1px 2px hsla(0, 0%, 55%, 0.6);\n  display: inline-block;\n  margin: 10px 0px;\n}\n\n#main p a.content_img {\n  max-width: 100%;\n}\n\ndiv[class$=content] > blockquote > p {\n  font-size: 14px;\n  line-height: 18px;\n}\n\n.index_notify {\n  font-size: 14px;\n}\n\n/* about score on sidebar */\n.top_score {\n  color: gray;\n  padding: 2px;\n}\n\n#sidebar .inner ol {\n  margin: 4px 0px;\n  list-style: none;\n}\n\n.top_score {\n  margin-right: 10px;\n}\n\n/* format the additional msgs of topics */\ndiv[class$=part] {\n  display: inline-block;\n}\n\n.click_part {\n  width: 80px;\n}\n\n.reply_part {\n  width: 300px;\n}\n\n.form-horizontal .control-group:before,\n.form-horizontal .control-group:after {\n  display: none;\n  line-height: 0;\n  content: \"\";\n}\n\ntextarea, textarea:focus, input, input:focus {\n  box-shadow: 0px 0px 2px rgba(60, 60, 60, 0.5);\n  // border: none;\n}\n\ntextarea.editor {\n  width: 98%;\n  padding: 0px;\n  line-height: 2em;\n  height: 200px;\n  resize: vertical;\n  font-size: 15px;\n  padding: 0.5em;\n  border: none;\n}\n\n#edit_reply_form textarea.editor,\n#create_topic_form textarea.editor {\n  height: 400px;\n}\n\n#edit_reply_form .preview,\n#create_topic_form .preview {\n  min-height: 400px;\n}\n\n.marked_icon {\n  float: right;\n}\n\n.topic_content table,\n.reply_content table {\n  padding: 0;\n  border-collapse: collapse;\n  border-spacing: 0;\n  font-size: 100%;\n  font: inherit;\n}\n\n.topic_content table tr,\n.reply_content table tr {\n  border-top: 1px solid #ccc;\n  background-color: #fff;\n  margin: 0;\n  padding: 0;\n}\n\n.topic_content table tr:nth-child(2n),\n.reply_content table tr:nth-child(2n) {\n  background-color: #f8f8f8;\n}\n\n.topic_content table tr th,\n.reply_content table tr th {\n  font-weight: bold;\n}\n\n.topic_content table tr th,\n.topic_content table tr td,\n.reply_content table tr th,\n.reply_content table tr td {\n  border: 1px solid #ccc;\n  text-align: left;\n  margin: 0;\n  padding: 6px 13px;\n}\n\n.topic_content table tr th > :first-child,\n.topic_content table tr td > :first-child,\n.reply_content table tr th > :first-child,\n.reply_content table tr td > :first-child {\n  margin-top: 0;\n}\n\n.topic_content table tr th > :last-child,\n.topic_content table tr td > :last-child,\n.reply_content table tr th > :last-child,\n.reply_content table tr td > :last-child {\n  margin-bottom: 0;\n}\n\n.span-common {\n  display: inline-block;\n  float: none;\n  padding: 3px 10px;\n  border: none;\n  margin: 0;\n  font-size: 14px;\n  transition: all 0.2s ease-in-out;\n  cursor: pointer;\n  letter-spacing: 2px;\n  box-shadow: none;\n  border-radius: 3px;\n  line-height: 2em;\n  vertical-align: middle;\n\n  background-color: #e5e5e5;\n  &:hover {\n    background-color: #909090;\n    color: white;\n  }\n}\n\n.span-success {\n  .span-common();\n\n  color: white;\n  background-color: @node_green;\n  &:hover {\n    background-color: #6ba44e;\n  }\n}\n\n.span-primary {\n  .span-common();\n\n  color: white;\n  background-color: #08c;\n  &:hover {\n    background-color: #05c;\n  }\n}\n\n.span-info {\n  .span-common();\n  color: white;\n  background-color: #5bc0de;\n  &:hover {\n    background-color: #2f96b4;\n  }\n}\n\n.alert {\n  border-radius: 0px;\n}\n\n.signature {\n  font-style: italic;\n}\n\n.topic-tab {\n  margin: 0 10px;\n  color: @node_green;\n  &.current-tab {\n    background-color: @node_green;\n    color: white;\n    padding: 3px 4px;\n    border-radius: 3px;\n  }\n  &:hover {\n    text-decoration: none;\n  }\n}\n\n.topiclist-tab {\n  background-color: #e5e5e5;\n  color: #999;\n  padding: 2px 4px;\n  border-radius: 3px;\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  -o-border-radius: 3px;\n  font-size: 12px;\n}\n\n.up_btn {\n  cursor: pointer;\n  opacity: 0.4;\n\n  &.uped {\n    opacity: 1;\n  }\n}\n\n.reply .CodeMirror , .reply2_area .CodeMirror {\n  height: 160px;\n\n  &:-webkit-full-screen {\n    height: 100%;\n  }\n  &:-moz-full-screen {\n    height: 100%;\n  }\n  &:fullscreen {\n    height: 100%;\n  }\n}\n\n.sep10 {\n  height: 10px;\n}\n\n.replies_history {\n  position: absolute;\n  border: 1px solid #aaa;\n  background-color: #FcFcFc;\n  box-shadow: 2px 2px 6px #aaa;\n  padding: 10px;\n  min-width: 130px;\n  max-width: 300px;\n\n  .inner_content{\n    overflow-y: auto;\n    max-height: 400px;\n    &::-webkit-scrollbar\n    {\n      width: 6px;\n      background-color: #F5F5F5;\n    }\n    &::-webkit-scrollbar-track\n    {\n      -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);\n      background-color: #F5F5F5;\n    }\n    &::-webkit-scrollbar-thumb\n    {\n      background-color: #999;\n    }\n  }\n\n  .item {\n    border-top: 1px solid #ddd;\n    padding: 5px;\n    img {\n      width: 30px;\n      height: 30px;\n      margin-right: 10px;\n    }\n\n    .scroll_to_original{\n      cursor: pointer;\n      visibility: hidden;\n      font-size: 1.2em;\n      margin: 0 5px;\n    }\n  }\n  .item:hover{\n    .scroll_to_original{\n      visibility: visible;\n    }\n  }\n  .title {\n    margin: 0 auto;\n    width: 100%;\n    text-align: center;\n    .with {\n      font-weight: bold;\n      color: #0088cc;\n    }\n  }\n  .anchor {\n    width: 0;\n    height: 0;\n    border-right: 10px solid #aaa;\n    border-top: 6px solid transparent;\n    border-bottom: 6px solid transparent;\n    border-left: 0;\n    position: absolute;\n    top: 12px;\n    left: -10px;\n  }\n}\n\n.btn-info {\n  color: white !important;\n}\n\n.friendship-community {\n  img {\n    width: 150px;\n  }\n}\n\n.access_token {\n  width: 200px;\n  height: 200px;\n}\n\n.cnode-app-download {\n  text-align: center;\n}\n\n// 图片预览\n#preview-modal {\n  // 左右两遍各预留 1%，因为一般屏幕比较宽，所以只留下了 1%\n  width: 98%;\n  margin-left: -49%;\n  top: 2%;\n  // 上面 2% 下面 2% 中间就剩下 96%\n  max-height: 96%;\n\n  text-align: center;\n  overflow-y: scroll;\n  display: none;\n\n  img {\n    // just for 增强好看度\n    box-shadow: 0 0 10px 5px grey;\n    cursor: pointer;\n  }\n}\n\n.about-friend-links img {\n  width: 250px;\n  height: 60px;\n}\n\n.sponsor_outlink:hover {\n  text-decoration: none;\n}\n"
  },
  {
    "path": "test/api/v1/message.test.js",
    "content": "var support = require('../../support/support');\nvar message = require('../../../common/message');\nvar MessageProxy = require('../../../proxy').Message;\nvar app = require('../../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar should = require('should');\n\ndescribe('test/api/v1/message.test.js', function () {\n\n  var mockUser;\n\n  before(function (done) {\n    support.ready(function () {\n      support.createUser(function (err, user) {\n        mockUser = user;\n        done();\n      });\n    });\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  it('should get unread messages', function (done) {\n    mm(MessageProxy, 'getMessageById', function (id, callback) {\n      callback(null, {reply: {author: {}}});\n    });\n    message.sendReplyMessage(mockUser.id, mockUser.id, mockUser.id, mockUser.id,\n      function (err) {\n        should.not.exists(err);\n        request.get('/api/v1/messages')\n          .query({\n            accesstoken: mockUser.accessToken\n          })\n          .end(function (err, res) {\n            res.body.data.hasnot_read_messages.length.should.above(0);\n            done();\n          });\n      });\n  });\n\n  it('should get unread messages count', function (done) {\n    mm(MessageProxy, 'getMessageById', function (id, callback) {\n      callback(null, {reply: {author: {}}});\n    });\n    request.get('/api/v1/message/count')\n      .query({\n        accesstoken: mockUser.accessToken\n      })\n      .end(function (err, res) {\n        res.body.data.should.equal(1);\n        done();\n      });\n  });\n\n  it('should mark all messages read', function (done) {\n    request.post('/api/v1/message/mark_all')\n      .send({\n        accesstoken: mockUser.accessToken\n      })\n      .end(function (err, res) {\n        // 第一次查询有一个\n        res.body.marked_msgs.length.should.equal(1);\n        request.post('/api/v1/message/mark_all')\n          .send({\n            accesstoken: mockUser.accessToken\n          })\n          .end(function (err, res) {\n            // 第二次查询没了\n            res.body.marked_msgs.length.should.equal(0);\n            done();\n          });\n      });\n  });\n  \n});\n"
  },
  {
    "path": "test/api/v1/reply.test.js",
    "content": "var app = require('../../../app');\nvar request = require('supertest')(app);\nvar pedding = require('pedding');\nvar support =  require('../../support/support');\nvar should  = require('should');\n\ndescribe('test/api/v1/reply.test.js', function () {\n  \n  var mockTopic, mockReplyId;\n  \n  before(function (done) {\n    support.ready(function () {\n      support.createTopic(support.normalUser.id, function (err, topic) {\n        mockTopic = topic;\n        done();\n      });\n    });\n  });\n\n  describe('create reply', function () {\n\n    it('should success', function (done) {\n      request.post('/api/v1/topic/' + mockTopic.id + '/replies')\n        .send({\n          content: 'reply a topic from api',\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          mockReplyId = res.body.reply_id;\n          done();\n        });\n    });\n\n    it('should success with repli_id', function (done) {\n      request.post('/api/v1/topic/' + mockTopic.id + '/replies')\n        .send({\n          content: 'reply a topic from api',\n          accesstoken: support.normalUser.accessToken,\n          repli_id: mockReplyId\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          done();\n        });\n    });\n\n    it('should 401 when no accessToken', function (done) {\n      request.post('/api/v1/topic/' + mockTopic.id + 'not_valid' + '/replies')\n        .send({\n          content: 'reply a topic from api'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(401);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic_id is not valid', function (done) {\n      request.post('/api/v1/topic/' + mockTopic.id + 'not_valid' + '/replies')\n        .send({\n          content: 'reply a topic from api',\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when no content', function (done) {\n      request.post('/api/v1/topic/' + mockTopic.id + '/replies')\n        .send({\n          content: '',\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic not found', function (done) {\n      var notFoundTopicId = mockTopic.id.split(\"\").reverse().join(\"\");\n      request.post('/api/v1/topic/' + notFoundTopicId + '/replies')\n        .send({\n          content: 'reply a topic from api',\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          if (mockTopic.id === notFoundTopicId) { // 小概率事件id反转之后还不变\n            res.body.success.should.true();\n          } else {\n            res.status.should.equal(404);\n            res.body.success.should.false();\n          }\n          done();\n        });\n    });\n\n    it('should fail when topic is locked', function (done) {\n      // 锁住 topic\n      mockTopic.lock = !mockTopic.lock;\n      mockTopic.save(function () {\n        request.post('/api/v1/topic/' + mockTopic.id + '/replies')\n          .send({\n            content: 'reply a topic from api',\n            accesstoken: support.normalUser.accessToken\n          })\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.status.should.equal(403);\n            res.body.success.should.false();\n            // 解锁 topic\n            mockTopic.lock = !mockTopic.lock;\n            mockTopic.save(function () {\n              done();\n            });\n          });\n      });\n    });\n\n  });\n  \n  describe('create ups', function () {\n\n    it('should up', function (done) {\n      request.post('/api/v1/reply/' + mockReplyId + '/ups')\n        .send({\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.action.should.equal(\"up\");\n          done();\n        });\n    });\n\n    it('should down', function (done) {\n      request.post('/api/v1/reply/' + mockReplyId + '/ups')\n        .send({\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.action.should.equal(\"down\");\n          done();\n        });\n    });\n\n    it('should 401 when no accessToken', function (done) {\n      request.post('/api/v1/reply/' + mockReplyId + '/ups')\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(401);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when reply_id is not valid', function (done) {\n      request.post('/api/v1/reply/' + mockReplyId + 'not_valid' + '/ups')\n        .send({\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when reply_id is not found', function (done) {\n      var notFoundReplyId = mockReplyId.split(\"\").reverse().join(\"\");\n      request.post('/api/v1/reply/' + notFoundReplyId + '/ups')\n        .send({\n          accesstoken: support.normalUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          if (mockReplyId === notFoundReplyId) { // 小概率事件id反转之后还不变\n            res.body.success.should.true();\n          } else {\n            res.status.should.equal(404);\n            res.body.success.should.false();\n          }\n          done();\n        });\n    });\n\n  });\n  \n});\n"
  },
  {
    "path": "test/api/v1/tools.test.js",
    "content": "var app = require('../../../app');\nvar request = require('supertest')(app);\nvar support = require('../../support/support');\nvar should = require('should');\n\ndescribe('test/api/v1/tools.test.js', function () {\n\n  var mockUser;\n\n  before(function (done) {\n    support.createUser(function (err, user) {\n      mockUser = user;\n      done();\n    });\n  });\n\n  it('should response with loginname', function (done) {\n    request.post('/api/v1/accesstoken')\n      .send({\n        accesstoken: mockUser.accessToken\n      })\n      .end(function (err, res) {\n        should.not.exists(err);\n        res.status.should.equal(200);\n        res.body.success.should.true();\n        res.body.loginname.should.equal(mockUser.loginname);\n        res.body.id.should.equal(mockUser.id);\n        done();\n      });\n  });\n\n  it('should 401 when accessToken is wrong', function (done) {\n    request.post('/api/v1/accesstoken')\n      .send({\n        accesstoken: 'not_exists'\n      })\n      .end(function (err, res) {\n        should.not.exists(err);\n        res.status.should.equal(401);\n        res.body.success.should.false();\n        done();\n      });\n  });\n\n});\n"
  },
  {
    "path": "test/api/v1/topic.test.js",
    "content": "var app = require('../../../app');\nvar request = require('supertest')(app);\nvar should = require('should');\nvar support = require('../../support/support');\n\ndescribe('test/api/v1/topic.test.js', function () {\n  \n  var mockUser, mockTopic;\n\n  var createdTopicId = null;\n\n  before(function (done) {\n    support.createUser(function (err, user) {\n      mockUser = user;\n      support.createTopic(user.id, function (err, topic) {\n        mockTopic = topic;\n        support.createReply(topic.id, user.id, function (err, reply) {\n          support.createSingleUp(reply.id, user.id, function (err, reply) {\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('get /api/v1/topics', function () {\n\n    it('should return topics', function (done) {\n      request.get('/api/v1/topics')\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.data.length.should.above(0);\n          done();\n        });\n    });\n\n    it('should return topics with limit 2', function (done) {\n      request.get('/api/v1/topics')\n        .query({\n          limit: 2\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.data.length.should.equal(2);\n          done();\n        });\n    });\n\n  });\n\n  describe('get /api/v1/topic/:topicid', function () {\n\n    it('should return topic info', function (done) {\n      request.get('/api/v1/topic/' + mockTopic.id)\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.data.id.should.equal(mockTopic.id);\n          done();\n        });\n    });\n\n    it('should fail when topic_id is not valid', function (done) {\n      request.get('/api/v1/topic/' + mockTopic.id + 'not_valid')\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic not found', function (done) {\n      var notFoundTopicId = mockTopic.id.split(\"\").reverse().join(\"\");\n      request.get('/api/v1/topic/' + notFoundTopicId)\n        .end(function (err, res) {\n          should.not.exists(err);\n          if (mockTopic.id === notFoundTopicId) { // 小概率事件id反转之后还不变\n            res.body.success.should.true();\n            res.body.data.id.should.equal(mockTopic.id);\n          } else {\n            res.status.should.equal(404);\n            res.body.success.should.false();\n          }\n          done();\n        });\n    });\n\n    it('should is_uped to be false without accesstoken', function (done) {\n      request.get('/api/v1/topic/' + mockTopic.id)\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.data.replies[0].is_uped.should.false();\n          done();\n        });\n    });\n\n    it('should is_uped to be false with wrong accesstoken', function (done) {\n      request.get('/api/v1/topic/' + mockTopic.id)\n        .query({\n          accesstoken: support.normalUser2.accesstoken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.data.replies[0].is_uped.should.false();\n          done();\n        });\n    });\n\n    it('should is_uped to be true with right accesstoken', function (done) {\n      request.get('/api/v1/topic/' + mockTopic.id)\n        .query({\n          accesstoken: mockUser.accessToken\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.data.replies[0].is_uped.should.true();\n          done();\n        });\n    });\n\n  });\n\n  describe('post /api/v1/topics', function () {\n\n    it('should create a topic', function (done) {\n      request.post('/api/v1/topics')\n        .send({\n          accesstoken: mockUser.accessToken,\n          title: '我是API测试标题',\n          tab: 'share',\n          content: '我是API测试内容'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.topic_id.should.be.String();\n          createdTopicId = res.body.topic_id\n          done();\n        });\n    });\n\n    it('should 401 with no accessToken', function (done) {\n      request.post('/api/v1/topics')\n        .send({\n          title: '我是API测试标题',\n          tab: 'share',\n          content: '我是API测试内容'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(401);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail with no title', function (done) {\n      request.post('/api/v1/topics')\n        .send({\n          accesstoken: mockUser.accessToken,\n          title: '',\n          tab: 'share',\n          content: '我是API测试内容'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail with error tab', function (done) {\n      request.post('/api/v1/topics')\n        .send({\n          accesstoken: mockUser.accessToken,\n          title: '我是API测试标题',\n          tab: '',\n          content: '我是API测试内容'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail with no content', function (done) {\n      request.post('/api/v1/topics')\n        .send({\n          accesstoken: mockUser.accessToken,\n          title: '我是API测试标题',\n          tab: 'share',\n          content: ''\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n  });\n\n  describe('post /api/v1/topics/update', function () {\n    it('should update a topic', function (done) {\n      request.post('/api/v1/topics/update')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: createdTopicId,\n          title: '我是API测试标题',\n          tab: 'share',\n          content: '我是API测试内容 /api/v1/topics/update'\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.topic_id.should.eql(createdTopicId);\n          done();\n        });\n    })\n  })\n  \n});\n"
  },
  {
    "path": "test/api/v1/topic_collect.test.js",
    "content": "var app = require('../../../app');\nvar request = require('supertest')(app);\nvar should = require('should');\nvar support = require('../../support/support');\n\ndescribe('test/api/v1/topic_collect.test.js', function () {\n\n  var mockUser, mockTopic;\n\n  before(function (done) {\n    support.createUser(function (err, user) {\n      mockUser = user;\n      support.createTopic(user.id, function (err, topic) {\n        mockTopic = topic;\n        done();\n      });\n    });\n  });\n\n  // 主题被收藏之前\n  describe('before collect topic', function () {\n\n    describe('get /topic_collect/:loginname', function () {\n\n      it('should list topic with length = 0', function (done) {\n        request.get('/api/v1/topic_collect/' + mockUser.loginname)\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.length.should.equal(0);\n            done();\n          });\n      });\n\n    });\n\n    describe('get /api/v1/topic/:topicid', function () {\n\n      it('should return topic info with is_collect = false', function (done) {\n        request.get('/api/v1/topic/' + mockTopic.id)\n          .query({\n            accesstoken: mockUser.accessToken\n          })\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.is_collect.should.false();\n            done();\n          });\n      });\n\n    });\n\n  });\n\n  // 收藏主题\n  describe('post /topic_collect/collect', function () {\n\n    it('should 401 with no accessToken', function (done) {\n      request.post('/api/v1/topic_collect/collect')\n        .send({\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(401);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should collect topic with correct accessToken', function (done) {\n      request.post('/api/v1/topic_collect/collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          done();\n        });\n    });\n\n    it('should not collect topic twice', function (done) {\n      request.post('/api/v1/topic_collect/collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic_id is not valid', function (done) {\n      request.post('/api/v1/topic_collect/collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id + \"not_valid\"\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic not found', function (done) {\n      var notFoundTopicId = mockTopic.id.split(\"\").reverse().join(\"\");\n      request.post('/api/v1/topic_collect/collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: notFoundTopicId\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          if (mockTopic.id === notFoundTopicId) { // 小概率事件id反转之后还不变\n            res.body.success.should.true();\n          } else {\n            res.status.should.equal(404);\n            res.body.success.should.false();\n          }\n          done();\n        });\n    });\n\n  });\n\n  // 主题被收藏之后\n  describe('after collect topic', function () {\n\n    describe('get /topic_collect/:loginname', function () {\n\n      it('should list topic with length = 1', function (done) {\n        request.get('/api/v1/topic_collect/' + mockUser.loginname)\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.length.should.equal(1);\n            res.body.data[0].id.should.equal(mockTopic.id);\n            done();\n          });\n      });\n\n      it('should fail when user not found', function (done) {\n        request.get('/api/v1/topic_collect/' + mockUser.loginname + 'not_found')\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.status.should.equal(404);\n            res.body.success.should.false();\n            done();\n          });\n      });\n\n    });\n\n    describe('get /api/v1/topic/:topicid', function () {\n\n      it('should return topic info with is_collect = true', function (done) {\n        request.get('/api/v1/topic/' + mockTopic.id)\n          .query({\n            accesstoken: mockUser.accessToken\n          })\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.is_collect.should.true();\n            done();\n          });\n      });\n\n    });\n\n  });\n\n  // 取消收藏主题\n  describe('post /topic_collect/de_collect', function () {\n\n    it('should 401 with no accessToken', function (done) {\n      request.post('/api/v1/topic_collect/de_collect')\n        .send({\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(401);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should decollect topic with correct accessToken', function (done) {\n      request.post('/api/v1/topic_collect/de_collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          done();\n        });\n    });\n\n    it('should not decollect topic twice', function (done) {\n      request.post('/api/v1/topic_collect/de_collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic_id is not valid', function (done) {\n      request.post('/api/v1/topic_collect/de_collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: mockTopic.id + \"not_valid\"\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(400);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n    it('should fail when topic not found', function (done) {\n      var notFoundTopicId = mockTopic.id.split(\"\").reverse().join(\"\");\n      request.post('/api/v1/topic_collect/de_collect')\n        .send({\n          accesstoken: mockUser.accessToken,\n          topic_id: notFoundTopicId\n        })\n        .end(function (err, res) {\n          should.not.exists(err);\n          if (mockTopic.id === notFoundTopicId) { // 小概率事件id反转之后还不变\n            res.body.success.should.true();\n          } else {\n            res.status.should.equal(404);\n            res.body.success.should.false();\n          }\n          done();\n        });\n    });\n\n  });\n\n  // 主题被取消收藏之后\n  describe('after decollect topic', function () {\n\n    describe('get /topic_collect/:loginname', function () {\n\n      it('should list topic with length = 0', function (done) {\n        request.get('/api/v1/topic_collect/' + mockUser.loginname)\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.length.should.equal(0);\n            done();\n          });\n      });\n\n    });\n\n    describe('get /api/v1/topic/:topicid', function () {\n\n      it('should return topic info with is_collect = false', function (done) {\n        request.get('/api/v1/topic/' + mockTopic.id)\n          .query({\n            accesstoken: mockUser.accessToken\n          })\n          .end(function (err, res) {\n            should.not.exists(err);\n            res.body.success.should.true();\n            res.body.data.is_collect.should.false();\n            done();\n          });\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/api/v1/user.test.js",
    "content": "var app = require('../../../app');\nvar request = require('supertest')(app);\nvar support = require('../../support/support');\nvar should = require('should');\nvar async = require('async');\n\ndescribe('test/api/v1/user.test.js', function () {\n\n  var mockUser;\n\n  before(function (done) {\n    async.auto({\n      create_user: function(callback){\n        support.createUser(function (err, user) {\n          mockUser = user;\n          callback(null, user);\n        });\n      },\n      create_topic: ['create_user', function(callback, result){\n        support.createTopic(result['create_user']._id, function(err, topic){\n          callback(null, topic);\n        });\n      }],\n      create_replies: ['create_topic', function(callback, result){\n        support.createReply(result['create_topic']._id, result['create_topic'].author_id, function(err, replay){\n          callback(null, replay);\n        });\n      }]\n    }, function(err, results){\n      done();\n    });\n  });\n  \n  describe('get /api/v1/user/:loginname', function () {\n\n    it('should return user info', function (done) {\n      request.get('/api/v1/user/' + mockUser.loginname)\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.body.success.should.true();\n          res.body.data.loginname.should.equal(mockUser.loginname);\n          should(res.body.data.recent_topics.length).be.exactly(1);\n          should(res.body.data.recent_replies.length).be.exactly(1); \n          done();\n        });\n    });\n\n    it('should fail when user is not found', function (done) {\n      request.get('/api/v1/user/' + mockUser.loginname + 'not_found')\n        .end(function (err, res) {\n          should.not.exists(err);\n          res.status.should.equal(404);\n          res.body.success.should.false();\n          done();\n        });\n    });\n\n  });\n  \n});\n"
  },
  {
    "path": "test/app.test.js",
    "content": "var request = require('supertest');\nvar app = require('../app');\nvar config = require('../config');\n\ndescribe('test/app.test.js', function () {\n  it('should / status 200', function (done) {\n    request(app).get('/').end(function (err, res) {\n      res.status.should.equal(200);\n      res.text.should.containEql(config.description);\n      done();\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/common/at.test.js",
    "content": "\nvar should = require('should');\nvar mm = require('mm');\nvar support = require('../support/support');\nvar eventproxy = require('eventproxy');\nvar _ = require('lodash');\n\nvar at = require('../../common/at');\nvar message = require('../../common/message');\nvar multiline = require('multiline');\nvar pedding = require('pedding');\n\ndescribe('test/common/at.test.js', function () {\n  var testTopic, normalUser, normalUser2, adminUser;\n  before(function (done) {\n    support.ready(function () {\n      testTopic = support.testTopic;\n      normalUser = support.normalUser;\n      normalUser2 = support.normalUser2;\n      adminUser = support.adminUser;\n      done();\n    });\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  var text = multiline.stripIndent(function(){/*\n    @A-aZ-z0-9_\n    @中文\n      @begin_with_spaces @multi_in_oneline\n    Text More Text @around_text ![Pic](/public/images/cnode_icon_32.png)\n    @end_with_no_space中文\n    Text 中文@begin_with_no_spaces\n    @end_with_no_space2@begin_with_no_spaces2\n\n    jysperm@gmail.com @alsotang\n\n    https://medium.com/@nodejs/announcing-a-new-experimental-modules-1be8d2d6c2ff\n\n    @alsotang2\n\n\n    ```\n    呵呵 ```\n    @alsotang3\n    ```\n\n    ```js\n       @flow\n    ```\n\n    ```@alsotang4```\n\n    @\n    @@\n\n    `@code_begin_with_no_space`\n    code: `@in_code`\n\n        @in_pre\n\n    ```\n    @in_oneline_pre\n    ```\n\n    ```\n      Some Code\n      Code @in_multi_line_pre\n    ```\n\n    [@be_link](/user/be_link) [@be_link2](/user/be_link2)\n\n    @alsotang @alsotang\n    aldjf\n    @alsotang @tangzhanli\n\n    [@alsotang](/user/alsotang)\n\n    @liveinjs 没事儿，能力和热情更重要，北京雍和宫，想的就邮件给我i5ting@126.com\n  */});\n\n  var matched_users = ['A-aZ-z0-9_', 'begin_with_spaces',\n    'multi_in_oneline', 'around_text', 'end_with_no_space',\n    'begin_with_no_spaces', 'end_with_no_space2',\n    'begin_with_no_spaces2', 'alsotang', 'alsotang2',\n    'tangzhanli', 'liveinjs'];\n\n  var linkedText = multiline.stripIndent(function(){/*\n[@A-aZ-z0-9_](/user/A-aZ-z0-9_)\n@中文\n  [@begin_with_spaces](/user/begin_with_spaces) [@multi_in_oneline](/user/multi_in_oneline)\nText More Text [@around_text](/user/around_text) ![Pic](/public/images/cnode_icon_32.png)\n[@end_with_no_space](/user/end_with_no_space)中文\nText 中文[@begin_with_no_spaces](/user/begin_with_no_spaces)\n[@end_with_no_space2](/user/end_with_no_space2)[@begin_with_no_spaces2](/user/begin_with_no_spaces2)\n\njysperm@gmail.com [@alsotang](/user/alsotang)\n\nhttps://medium.com/@nodejs/announcing-a-new-experimental-modules-1be8d2d6c2ff\n\n[@alsotang2](/user/alsotang2)\n\n\n```\n呵呵 ```\n@alsotang3\n```\n\n```js\n   @flow\n```\n\n```@alsotang4```\n\n@\n@@\n\n`@code_begin_with_no_space`\ncode: `@in_code`\n\n    @in_pre\n\n```\n@in_oneline_pre\n```\n\n```\n  Some Code\n  Code @in_multi_line_pre\n```\n\n[@be_link](/user/be_link) [@be_link2](/user/be_link2)\n\n[@alsotang](/user/alsotang) [@alsotang](/user/alsotang)\naldjf\n[@alsotang](/user/alsotang) [@tangzhanli](/user/tangzhanli)\n\n[@alsotang](/user/alsotang)\n\n[@liveinjs](/user/liveinjs) 没事儿，能力和热情更重要，北京雍和宫，想的就邮件给我i5ting@126.com\n  */});\n\n  describe('#fetchUsers()', function () {\n    var fetchUsers = at.fetchUsers;\n    it('should found 6 users', function () {\n      var users = fetchUsers(text);\n      should.exist(users);\n      users.should.eql(matched_users);\n    });\n\n    it('should found 0 user in text', function () {\n      var users = fetchUsers('no users match in text @ @@@@ @ @@@ @哈哈 @ testuser1');\n      users.should.length(0);\n    });\n  });\n\n  describe('#linkUsers()', function () {\n    it('should link all mention users', function (done) {\n      at.linkUsers(text, function (err, text2) {\n        should.not.exist(err);\n        text2.should.equal(linkedText);\n        done();\n      });\n    });\n  });\n\n  describe('sendMessageToMentionUsers()', function () {\n    it('should send message to all mention users', function (done) {\n      done = pedding(done, 2);\n      var atUserIds = [String(adminUser._id), String(normalUser2._id)];\n\n      var ep  = new eventproxy();\n      ep.after('user_id', atUserIds.length, function (user_ids) {\n        user_ids.sort().should.eql(atUserIds.sort());\n        done();\n      });\n      mm(message, 'sendAtMessage',\n        function (atUserId, authorId, topicId, replyId, callback) {\n          // String(atUserId).should.equal(String(atUserIds[count++]));\n          ep.emit('user_id', String(atUserId));\n          callback();\n        });\n\n      var text = '@' + adminUser.loginname + ' @' + normalUser2.loginname + ' @notexitstuser 你们好';\n      at.sendMessageToMentionUsers(text,\n        testTopic._id,\n        normalUser._id,\n        function (err) {\n          should.not.exist(err);\n          done();\n        });\n    });\n\n    it('should not send message to no mention users', function (done) {\n      mm(message, 'sendAtMessage', function () {\n        throw new Error('should not call me');\n      });\n      at.sendMessageToMentionUsers('abc no mentions', testTopic._id, normalUser._id,\n        function (err) {\n          should.not.exist(err);\n          done();\n        });\n    });\n\n    it('should not send at msg to author', function (done) {\n      mm(message, 'sendAtMessage', function () {\n        throw new Error('should not call me');\n      });\n\n      at.sendMessageToMentionUsers('@' + normalUser.loginname + ' hello',\n        testTopic._id, normalUser._id,\n        function (err) {\n          should.not.exist(err);\n          done();\n        });\n    });\n\n    describe('mock message.sendAtMessage() error', function () {\n      beforeEach(function () {\n        mm(message, 'sendAtMessage', function () {\n          var callback = arguments[arguments.length - 1];\n          process.nextTick(function () {\n            callback(new Error('mock sendAtMessage() error'));\n          });\n        });\n      });\n      it('should return error', function (done) {\n        var text = '@' + normalUser.loginname + ' @' + normalUser2.loginname + ' @notexitstuser 你们好';\n\n        at.sendMessageToMentionUsers(text, testTopic._id, normalUser._id,\n          function (err) {\n            should.exist(err);\n            err.message.should.equal('mock sendAtMessage() error');\n            done();\n          });\n      });\n    });\n\n  });\n});\n"
  },
  {
    "path": "test/common/cache.test.js",
    "content": "var cache = require('../../common/cache');\nvar should = require('should');\n\ndescribe('test/common/cache.test.js', function () {\n  it('should set && get', function (done) {\n    cache.set('alsotang', {age: 23}, function () {\n      cache.get('alsotang', function (err, data) {\n        data.should.eql({age: 23});\n        done();\n      });\n    });\n  });\n\n  it('should expire', function (done) {\n    cache.set('alsotang', {age: 23}, 1, function () {\n      setTimeout(function () {\n        cache.get('alsotang', function (err, data) {\n          should.not.exist(data);\n          done();\n        });\n      }, 1.5 * 1000);\n    });\n  });\n});\n"
  },
  {
    "path": "test/common/mail.test.js",
    "content": "var mail = require('../../common/mail');\n\ndescribe('test/common/mail.test.js', function () {\n  describe('sendActiveMail', function () {\n    it('should ok', function () {\n      mail.sendActiveMail('shyvo1987@gmail.com', 'token', 'jacksontian');\n    });\n  });\n\n  describe('sendResetPassMail', function () {\n    it('should ok', function () {\n      mail.sendResetPassMail('shyvo1987@gmail.com', 'token', 'jacksontian');\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/common/message.test.js",
    "content": "var should = require('should');\nvar app = require('../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar support = require('../support/support');\nvar _ = require('lodash');\nvar pedding = require('pedding');\nvar multiline = require('multiline');\nvar MessageService = require('../../common/message');\nvar eventproxy = require('eventproxy');\nvar ReplyProxy = require('../../proxy').Reply;\n\ndescribe('test/common/message.test.js', function () {\n  var atUser;\n  var author;\n  var topic;\n  var reply;\n  before(function (done) {\n    var ep = new eventproxy();\n\n    ep.all('topic', function (_topic) {\n      topic = _topic;\n      done();\n    });\n    support.ready(function () {\n      atUser = support.normalUser;\n      author = atUser;\n      reply = {};\n      support.createTopic(author._id, ep.done('topic'));\n    });\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  describe('#sendReplyMessage', function () {\n    it('should send reply message', function (done) {\n      mm(ReplyProxy, 'getReplyById', function (id, callback) {\n        callback(null, {author: {}});\n      });\n      MessageService.sendReplyMessage(atUser._id, author._id, topic._id, reply._id,\n        function (err, msg) {\n          request.get('/my/messages')\n          .set('Cookie', support.normalUserCookie)\n          .expect(200, function (err, res) {\n            var texts = [\n              author.loginname,\n              '回复了你的话题',\n              topic.title,\n            ];\n            texts.forEach(function (text) {\n              res.text.should.containEql(text)\n            })\n            done(err);\n          });\n        });\n    });\n  });\n\n  describe('#sendAtMessage', function () {\n    it('should send at message', function (done) {\n      mm(ReplyProxy, 'getReplyById', function (id, callback) {\n        callback(null, {author: {}});\n      });\n      MessageService.sendAtMessage(atUser._id, author._id, topic._id, reply._id,\n        function (err, msg) {\n          request.get('/my/messages')\n          .set('Cookie', support.normalUserCookie)\n          .expect(200, function (err, res) {\n            var texts = [\n              author.loginname,\n              '在话题',\n              topic.title,\n              '中@了你',\n            ];\n            texts.forEach(function (text) {\n              res.text.should.containEql(text)\n            })\n            done(err);\n          });\n        });\n    });\n  });\n})\n"
  },
  {
    "path": "test/common/render_helper.test.js",
    "content": "var should = require('should');\nvar app = require('../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar support = require('../support/support');\nvar _ = require('lodash');\nvar pedding = require('pedding');\nvar multiline = require('multiline');\nvar renderHelper = require('../../common/render_helper');\n\ndescribe('test/common/render_helper.test.js', function () {\n  describe('#markdown', function () {\n    it('should render code inline', function () {\n      var text = multiline(function () {;\n    /*\n`var a = 1;`\n    */\n      });\n\n      var rendered = renderHelper.markdown(text);\n      rendered.should.equal('<div class=\"markdown-text\"><p><code>var a = 1;</code></p>\\n</div>');\n    });\n\n    it('should render fence', function () {\n      var text = multiline(function () {;\n    /*\n```js\nvar a = 1;\n```\n    */\n      });\n\n      var rendered = renderHelper.markdown(text);\n      rendered.should.equal('<div class=\\\"markdown-text\\\"><pre class=\\\"prettyprint language-js\\\"><code>var a = 1;\\n</code></pre></div>');\n    });\n\n    it('should render code block', function () {\n      var text = multiline(function () {;\n/*\n    var a = 1;\n*/\n      });\n\n      var rendered = renderHelper.markdown(text);\n      rendered.should.equal('<div class=\"markdown-text\"><pre class=\"prettyprint\"><code>var a = 1;</code></pre></div>');\n    });\n  });\n\n  describe('#escapeSignature', function () {\n    it('should escape content', function () {\n      var signature = multiline(function () {;\n/*\n我爱北京天安门<script>alert(1)\n</script>\n*/\n      });\n      var escaped = renderHelper.escapeSignature(signature);\n      escaped.should.equal('我爱北京天安门&lt;script&gt;alert(1)<br>&lt;/script&gt;');\n    })\n  })\n\n  describe('#tabName', function () {\n    it('should translate', function () {\n      renderHelper.tabName('share')\n        .should.equal('分享')\n    })\n  })\n\n\n});\n"
  },
  {
    "path": "test/common/store_local.test.js",
    "content": "var path = require('path');\nvar fs = require('fs');\nvar storeLocal = require('../../common/store_local');\nvar config = require('../../config');\n\ndescribe('test/common/store_local.test.js', function () {\n  it('should upload a file', function (done) {\n    var file = fs.createReadStream(path.join(__dirname, 'at.test.js'));\n    var filename = 'at.test.js';\n    storeLocal.upload(file, {filename: filename}, function (err, data) {\n      var newFilename = data.url.match(/([^\\/]+\\.js)$/)[1];\n      var newFilePath = path.join(config.upload.path, newFilename);\n      setTimeout(function () {\n        fs.existsSync(newFilePath)\n          .should.ok();\n        fs.unlinkSync(newFilePath);\n        done(err);\n      }, 1 * 1000);\n    });\n  });\n});\n"
  },
  {
    "path": "test/common/tools.test.js",
    "content": "/*!\n * nodeclub - onehost plugins unit tests.\n * Copyright(c) 2012 dead-horse <dead_horse@qq.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\nvar tools = require('../../common/tools');\n\ndescribe('test/common/tools.test.js', function () {\n  it('should format date', function () {\n    tools.formatDate(new Date(0)).should.match(/1970\\-01\\-01 0\\d:00/);\n  });\n  it('should format date friendly', function () {\n    tools.formatDate(new Date(), true).should.equal('几秒前');\n  });\n});\n"
  },
  {
    "path": "test/controllers/github.test.js",
    "content": "var app = require('../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar github = require('../../controllers/github');\nvar Models = require('../../models');\nvar User = Models.User;\nvar config = require('../../config');\nvar support = require('../support/support');\n\ndescribe('test/controllers/github.test.js', function () {\n  before(function (done) {\n    support.ready(done);\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  it('should 302 when get /auth/github', function (done) {\n    var _clientID = config.GITHUB_OAUTH.clientID;\n    config.GITHUB_OAUTH.clientID = 'aldskfjo2i34j2o3';\n    request.get('/auth/github')\n      .expect(302, function (err, res) {\n        if (err) {\n          return done(err);\n        }\n        res.headers.should.have.property('location')\n          .with.startWith('https://github.com/login/oauth/authorize?');\n        config.GITHUB_OAUTH.clientID = _clientID;\n        done();\n      });\n  });\n\n  describe('get /auth/github/callback', function () {\n    before(function () {\n      app.get('/auth/github/test_callback',\n        function (req, res, next) {\n          req.user = {\n            id: 'notexists',\n            emails: [\n              {value: 'notexists@gmail.com'}\n            ],\n            _json: {avatar_url: 'http://avatar_url'}\n          };\n          next();\n        },\n        github.callback);\n    });\n    it('should redirect to /auth/github/new when the github id not in database', function (done) {\n      request.get('/auth/github/test_callback?code=123456')\n        .expect(302, function (err, res) {\n          if (err) {\n            return done(err);\n          }\n          res.headers.should.have.property('location')\n            .with.endWith('/auth/github/new');\n          done();\n        });\n    });\n\n    it('should redirect to / when the user is registed', function (done) {\n      mm.data(User, 'findOne', {save: function (callback) {\n        process.nextTick(callback);\n      }});\n\n      request.get('/auth/github/test_callback?code=123456')\n        .expect(302, function (err, res) {\n          if (err) {\n            return done(err);\n          }\n          res.headers.should.have.property('location')\n            .with.endWith('/');\n          done();\n        });\n    });\n  });\n\n  describe('get /auth/github/new', function () {\n    it('should 200', function (done) {\n      request.get('/auth/github/new')\n        .expect(200, function (err, res) {\n          if (err) {\n            return done(err);\n          }\n          res.text.should.containEql('/auth/github/create');\n          done();\n        });\n    });\n  });\n\n  describe('post /auth/github/create', function () {\n    before(function () {\n      var displayName = 'alsotang' + +new Date();\n      var username = 'alsotang' + +new Date();\n      var email = 'alsotang@gmail.com' + +new Date();\n      app.post('/auth/github/test_create', function (req, res, next) {\n        req.session.profile = {\n          displayName: displayName,\n          username: req.body.githubName || username,\n          accessToken: 'a3l24j23lk5jtl35tkjglfdsf',\n          emails: [\n            {value: email}\n          ],\n          _json: {avatar_url: 'http://avatar_url.com/1.jpg'},\n          id: 22\n        };\n        next();\n      }, github.create);\n    });\n    it('should create a new user', function (done) {\n      var userCount;\n      User.countDocuments(function (err, count) {\n        userCount = count;\n        request.post('/auth/github/test_create')\n          .send({isnew: '1'})\n          .expect(302, function (err, res) {\n            if (err) {\n              return done(err);\n            }\n            res.headers.should.have.property('location')\n              .with.endWith('/');\n            User.countDocuments(function (err, count) {\n              count.should.equal(userCount + 1);\n              done();\n            });\n          });\n      });\n    });\n\n    it('should not create a new user when loginname or email conflict', function (done) {\n      request.post('/auth/github/test_create')\n        .send({isnew: '1'})\n        .expect(500, function (err, res) {\n          if (err) {\n            return done(err);\n          }\n          res.text.should.match(/您 GitHub 账号的.*与之前在 CNodejs 注册的.*重复了/);\n          done();\n        });\n    });\n\n    it('should link a old user', function (done) {\n      var username = 'alsotang' + +new Date();\n      var pass = 'hehe';\n      support.createUserByNameAndPwd(username, pass, function (user) {\n        request.post('/auth/github/test_create')\n          .send({name: username, pass: pass, githubName: username})\n          .end(function (err, res) {\n            res.status.should.equal(302);\n            res.headers.location.should.equal('/');\n            done(err);\n          });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/controllers/message.test.js",
    "content": "var request = require('supertest');\nvar app = require('../../app');\nvar support = require('../support/support');\n\ndescribe('test/controllers/message.test.js', function () {\n  before(function (done) {\n    support.ready(done);\n  });\n\n  describe('index', function () {\n    it('should 403 without session', function (done) {\n      request(app).get('/my/messages').end(function (err, res) {\n        res.statusCode.should.equal(403);\n        res.type.should.equal('text/html');\n        res.text.should.containEql('forbidden!');\n        done(err);\n      });\n    });\n\n    it('should 200', function (done) {\n      request(app).get('/my/messages')\n      .set('Cookie', support.normalUserCookie)\n      .expect(200)\n      .end(function (err, res) {\n        res.text.should.containEql('新消息');\n        done(err);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/controllers/reply.test.js",
    "content": "var app = require('../../app');\nvar request = require('supertest')(app);\nvar support = require('../support/support');\nvar ReplyProxy = require('../../proxy/reply');\n\ndescribe('test/controllers/reply.test.js', function () {\n  before(function (done) {\n    support.ready(done);\n  });\n\n  var reply1Id;\n\n  describe('reply1', function () {\n    it('should add a reply1', function (done) {\n      var topic = support.testTopic;\n      request.post('/' + topic._id + '/reply')\n      .set('Cookie', support.normalUserCookie)\n      .send({\n        r_content: 'test reply 1'\n      })\n      .expect(302)\n      .end(function (err, res) {\n        res.headers['location'].should.match(new RegExp('/topic/' + topic.id + '#\\\\w+'));\n\n        // 记录下这个 reply1 的 id\n        reply1Id = res.headers['location'].match(/#(\\w+)/)[1];\n\n        done(err);\n      });\n    });\n\n    it('should 422 when add a empty reply1', function (done) {\n      var topic = support.testTopic;\n      request.post('/' + topic._id + '/reply')\n      .set('Cookie', support.normalUserCookie)\n      .send({\n        r_content: ''\n      })\n      .expect(422)\n      .end(done);\n    });\n\n    it('should not add a reply1 when not login', function (done) {\n      request.post('/' + support.testTopic._id + '/reply')\n      .send({\n        r_content: 'test reply 1'\n      })\n      .expect(403)\n      .end(done);\n    });\n  });\n\n  describe('edit reply', function () {\n    it('should not show edit page when not author', function (done) {\n      request.get('/reply/' + reply1Id + '/edit')\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(403)\n      .end(done);\n    });\n\n    it('should show edit page when is author', function (done) {\n      request.get('/reply/' + reply1Id + '/edit')\n      .set('Cookie', support.normalUserCookie)\n      .expect(200)\n      .end(function (err, res) {\n        res.text.should.containEql('test reply 1');\n        done(err);\n      });\n    });\n\n    it('should update edit', function (done) {\n      var topic = support.testTopic;\n      request.post('/reply/' + reply1Id + '/edit')\n      .send({\n        t_content: 'been update',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .end(function (err, res) {\n        res.status.should.equal(302);\n        res.headers['location'].should.match(new RegExp('/topic/' + topic.id + '#\\\\w+'));\n        done(err);\n      });\n    });\n  });\n\n  describe('upvote reply', function () {\n    var reply1, reply1UpCount;\n    before(function (done) {\n      ReplyProxy.getReply(reply1Id, function (err, reply) {\n        reply1 = reply;\n        reply1UpCount = reply1.ups.length;\n        done(err);\n      });\n    });\n\n    it('should increase', function (done) {\n      request.post('/reply/' + reply1Id + '/up')\n      .send({replyId: reply1Id})\n      .set('Cookie', support.normalUser2Cookie)\n      .end(function (err, res) {\n        res.status.should.equal(200);\n        res.body.should.eql({\n          success: true,\n          action: 'up',\n        });\n        done(err);\n      });\n    });\n\n    it('should decrease', function (done) {\n      request.post('/reply/' + reply1Id + '/up')\n      .send({replyId: reply1Id})\n      .set('Cookie', support.normalUser2Cookie)\n      .end(function (err, res) {\n        res.status.should.equal(200);\n        res.body.should.eql({\n          success: true,\n          action: 'down',\n        });\n        done(err);\n      });\n    });\n\n  });\n\n  describe('delete reply', function () {\n    it('should should not delete when not author', function (done) {\n      request.post('/reply/' + reply1Id + '/delete')\n      .send({\n        reply_id: reply1Id\n      })\n      .expect(403)\n      .end(done);\n    });\n\n    it('should delete reply when author', function (done) {\n      request.post('/reply/' + reply1Id + '/delete')\n      .send({\n        reply_id: reply1Id\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(200)\n      .end(function (err, res) {\n        res.body.should.eql({status: 'success'});\n        done(err);\n      });\n    });\n  });\n});\n\n"
  },
  {
    "path": "test/controllers/rss.test.js",
    "content": "/*!\n * nodeclub - rss controller test\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar request = require('supertest');\nvar app = require('../../app');\nvar config = require('../../config');\n\ndescribe('test/controllers/rss.test.js', function () {\n\n  describe('/rss', function () {\n    it('should return `application/xml` Content-Type', function (done) {\n      request(app).get('/rss').end(function (err, res) {\n        res.status.should.equal(200);\n        res.headers.should.property('content-type', 'application/xml; charset=utf-8');\n        res.text.indexOf('<?xml version=\"1.0\" encoding=\"utf-8\"?>').should.equal(0);\n        res.text.should.containEql('<rss version=\"2.0\">');\n        res.text.should.containEql('<channel><title>' + config.rss.title + '</title>');\n        done(err);\n      });\n    });\n\n    describe('mock `config.rss` not set', function () {\n      var rss = config.rss;\n      before(function () {\n        config.rss = null;\n      });\n      after(function () {\n        config.rss = rss;\n      });\n\n      it('should return waring message', function (done) {\n        request(app).get('/rss').end(function (err, res) {\n          res.status.should.equal(404);\n          res.text.should.equal('Please set `rss` in config.js');\n          done(err);\n        });\n      });\n    });\n\n    describe('mock `topic.getTopicsByQuery()` error', function () {\n      var topic = require('../../proxy').Topic;\n      var getTopicsByQuery = topic.getTopicsByQuery;\n      before(function () {\n        topic.getTopicsByQuery = function () {\n          var callback = arguments[arguments.length - 1];\n          process.nextTick(function () {\n            callback(new Error('mock getTopicsByQuery() error'));\n          });\n        };\n      });\n      after(function () {\n        topic.getTopicsByQuery = getTopicsByQuery;\n      });\n\n      it('should return error', function (done) {\n        request(app).get('/rss').end(function (err, res) {\n          res.status.should.equal(500);\n          res.text.should.containEql('mock getTopicsByQuery() error');\n          done(err);\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/controllers/search.test.js",
    "content": "var app = require('../../app');\nvar request = require('supertest')(app);\n\ndescribe('test/controllers/search.test.js', function () {\n  it('should redirect to google search', function (done) {\n    request.get('/search').query({q: 'node 中文'})\n      .expect(302)\n      .end(function (err, res) {\n        res.headers['location'].should.equal('https://www.google.com.hk/#hl=zh-CN&q=site:cnodejs.org+node%20%E4%B8%AD%E6%96%87');\n        done(err);\n      });\n  });\n});\n"
  },
  {
    "path": "test/controllers/sign.test.js",
    "content": "var app = require('../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar config = require('../../config');\nvar passport = require('passport');\nvar configMiddleware = require('../../middlewares/conf');\nvar should = require('should');\nvar UserProxy = require('../../proxy/user');\nvar mailService = require('../../common/mail');\nvar pedding = require('pedding');\nvar utility = require('utility');\nvar tools = require('../../common/tools');\n\ndescribe('test/controllers/sign.test.js', function () {\n  var now = +new Date();\n  var loginname = 'testuser' + now;\n  var email = 'testuser' + now + '@gmail.com';\n  var pass = 'wtffffffffffff';\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  describe('sign up', function () {\n\n    it('should visit sign up page', function (done) {\n      request.get('/signup')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('确认密码');\n        done(err);\n      });\n    });\n\n    it('should redirect to github oauth page', function (done) {\n      mm(config.GITHUB_OAUTH, 'clientID', 'clientID chenged');\n      app.get('/signup_github', configMiddleware.github, passport.authenticate('github'));\n      request.get('/signup_github')\n      .expect(302, function (err, res) {\n        res.headers.location.should.containEql('https://github.com/login/oauth/authorize?');\n        done(err);\n      });\n    });\n\n    it('should sign up a user', function (done) {\n      done = pedding(done, 2);\n\n      mm(mailService, 'sendMail', function (data) {\n        data.to.should.match(new RegExp(loginname + '@gmail\\\\.com'));\n        done();\n      });\n      request.post('/signup')\n        .send({\n          loginname: loginname,\n          email: email,\n          pass: pass,\n          re_pass: pass,\n        })\n        .expect(200, function (err, res) {\n          should.not.exists(err);\n          res.text.should.containEql('欢迎加入');\n          UserProxy.getUserByLoginName(loginname, function (err, user) {\n            should.not.exists(err);\n            user.should.ok();\n            done();\n          });\n        });\n    });\n\n    it('should not sign up a user when loginname is exists', function (done) {\n      request.post('/signup')\n        .send({\n          loginname: loginname,\n          email: email + '1',\n          pass: pass,\n          re_pass: pass,\n        })\n        .expect(422, done);\n    });\n\n    it('should not sign up a user when email is exists', function (done) {\n      request.post('/signup')\n        .send({\n          loginname: loginname + '1',\n          email: email,\n          pass: pass,\n          re_pass: pass,\n        })\n        .expect(422, done);\n    });\n  });\n\n  describe('login in', function () {\n    it('should visit sign in page', function (done) {\n      request.get('/signin').end(function (err, res) {\n        res.text.should.containEql('登录');\n        res.text.should.containEql('通过 GitHub 登录');\n        done(err);\n      });\n    });\n\n    it('should error when no loginname or no pass', function (done) {\n      request.post('/signin')\n      .send({\n        name: loginname,\n        pass: '',\n      })\n      .end(function (err, res) {\n        res.status.should.equal(422);\n        res.text.should.containEql('信息不完整。');\n        done(err);\n      });\n    });\n\n    it('should not login in when not actived', function (done) {\n      request.post('/signin')\n      .send({\n        name: loginname,\n        pass: pass,\n      })\n      .end(function (err, res) {\n        res.status.should.equal(403);\n        res.text.should.containEql('此帐号还没有被激活，激活链接已发送到');\n        done(err);\n      });\n    });\n  });\n\n  describe('sign out', function () {\n    it('should sign out', function (done) {\n      request.post('/signout')\n      .set('Cookie', config.auth_cookie_name + ':something;')\n      .expect(302, function (err, res) {\n        res.headers['set-cookie'].should.eql([ config.auth_cookie_name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT' ]);\n        done(err);\n      });\n    });\n  });\n\n  describe('active', function () {\n    it('should active account', function (done) {\n      UserProxy.getUserByLoginName(loginname, function (err, user) {\n        var key = utility.md5(user.email + user.pass + config.session_secret);\n        request.get('/active_account')\n        .query({\n          key: key,\n          name: loginname,\n        })\n        .expect(200, function (err, res) {\n          res.text.should.containEql('帐号已被激活，请登录');\n          done(err);\n        });\n      });\n    });\n  });\n\n  describe('when new user is actived', function () {\n    it('should login in successful', function (done) {\n      request.post('/signin')\n      .send({\n        name: loginname,\n        pass: pass,\n      })\n      .end(function (err, res) {\n        res.status.should.equal(302);\n        res.headers.location.should.equal('/');\n        done(err);\n      });\n    });\n  });\n\n  describe('search pass', function () {\n    var resetKey;\n\n    it('should 200 when get /search_pass', function (done) {\n      request.get('/search_pass')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('找回密码');\n        done(err);\n      });\n    });\n\n    it('should update search pass', function (done) {\n      done = pedding(done, 2);\n      mm(mailService, 'sendMail', function (data) {\n        data.from.should.equal('Nodeclub <club@126.com>');\n        data.to.should.match(new RegExp(loginname));\n        data.subject.should.equal('Nodeclub社区密码重置');\n        data.html.should.match(new RegExp('<p>您好：' + loginname));\n        resetKey = data.html.match(/key=(.+?)&/)[1];\n        done();\n      });\n\n      request.post('/search_pass')\n      .send({\n        email: email\n      })\n      .expect(200, function (err, res) {\n        res.text.should.containEql('我们已给您填写的电子邮箱发送了一封邮件，请在24小时内点击里面的链接来重置密码。');\n        done(err);\n      });\n    });\n\n    it('should 200 when get /reset_pass', function (done) {\n      request.get('/reset_pass')\n      .query({\n        key : resetKey,\n        name : loginname\n      })\n      .expect(200, function (err, res) {\n        res.text.should.containEql('重置密码');\n        done(err);\n      });\n    });\n\n    it('should 403 get /reset_pass when with wrong resetKey', function (done) {\n      request.get('/reset_pass')\n      .query({\n        key : 'wrong key',\n        name : loginname\n      })\n      .expect(403, function (err, res) {\n        res.text.should.containEql('信息有误，密码无法重置。');\n        done(err);\n      });\n    });\n\n    it('should update passwork', function (done) {\n      request.post('/reset_pass')\n      .send({\n        psw: 'jkljkljkl',\n        repsw: 'jkljkljkl',\n        key: resetKey,\n        name: loginname,\n      })\n      .expect(200, function (err, res) {\n        res.text.should.containEql('你的密码已重置。');\n        done(err);\n      })\n    })\n  });\n});\n"
  },
  {
    "path": "test/controllers/site.test.js",
    "content": "/*!\n * nodeclub - site controller test\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar should = require('should');\nvar config = require('../../config');\nvar app = require('../../app');\nvar request = require('supertest')(app);\n\n\ndescribe('test/controllers/site.test.js', function () {\n\n  it('should / 200', function (done) {\n    request.get('/').end(function (err, res) {\n      res.status.should.equal(200);\n      res.text.should.containEql('积分榜');\n      res.text.should.containEql('友情社区');\n      done(err);\n    });\n  });\n\n  it('should /?page=-1 200', function (done) {\n    request.get('/?page=-1').end(function (err, res) {\n      res.status.should.equal(200);\n      res.text.should.containEql('积分榜');\n      res.text.should.containEql('友情社区');\n      done(err);\n    });\n  });\n\n  it('should /sitemap.xml 200', function (done) {\n    request.get('/sitemap.xml')\n    .expect(200, function (err, res) {\n      res.text.should.containEql('<url>');\n      done(err);\n    });\n  });\n\n  it('should /app/download', function (done) {\n    request.get('/app/download')\n      .expect(302, function (err, res) {\n        done(err);\n      });\n  });\n});\n"
  },
  {
    "path": "test/controllers/static.test.js",
    "content": "var app = require('../../app');\nvar request = require('supertest')(app);\n\ndescribe('test/controllers/static.test.js', function () {\n  it('should get /about', function (done) {\n    request.get('/about').expect(200)\n      .end(function (err, res) {\n        res.text.should.containEql('CNode 社区由一批热爱 Node.js 技术的工程师发起');\n        done(err);\n      });\n  });\n\n  it('should get /faq', function (done) {\n    request.get('/faq').expect(200)\n      .end(function (err, res) {\n        res.text.should.containEql('CNode 社区和 Node Club 是什么关系？');\n        done(err);\n      });\n  });\n\n  it('should get /getstart', function (done) {\n    request.get('/getstart').expect(200)\n    .end(function (err, res) {\n      res.text.should.containEql('Node.js 新手入门');\n      done(err);\n    });\n  });\n\n  it('should get /robots.txt', function (done) {\n    request.get('/robots.txt').expect(200)\n      .end(function (err, res) {\n        res.text.should.containEql('User-Agent');\n        done(err);\n      });\n  });\n});\n"
  },
  {
    "path": "test/controllers/topic.test.js",
    "content": "\nvar should = require('should');\nvar app = require('../../app');\nvar request = require('supertest')(app);\nvar support = require('../support/support');\nvar mm = require('mm');\nvar store = require('../../common/store');\nvar pedding = require('pedding');\n\ndescribe('test/controllers/topic.test.js', function () {\n\n  before(function (done) {\n    support.ready(done);\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  describe('#index', function () {\n    it('should get /topic/:tid 200', function (done) {\n      request.get('/topic/' + support.testTopic._id)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('test topic content');\n        res.text.should.containEql('alsotang');\n        done(err);\n      });\n    });\n\n    it('should get /topic/:tid 200 when login in', function (done) {\n      request.get('/topic/' + support.testTopic._id)\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('test topic content');\n        res.text.should.containEql('alsotang');\n        done(err);\n      });\n    });\n  });\n\n  describe('#create', function () {\n    it('should show a create page', function (done) {\n      request.get('/topic/create')\n        .set('Cookie', support.normalUserCookie)\n        .expect(200, function (err, res) {\n          res.text.should.containEql('发布话题');\n          done(err);\n        });\n    });\n  });\n\n  describe('#put', function () {\n    it('should not create a topic when no title', function (done) {\n      request.post('/topic/create')\n      .send({\n        title: '',\n        tab: 'share',\n        t_content: '木耳敲回车',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(422, function (err, res) {\n        res.text.should.containEql('标题不能是空的。');\n        done(err);\n      });\n    });\n\n    it('should not create a topic when no tab', function (done) {\n      request.post('/topic/create')\n      .send({\n        title: '呵呵复呵呵',\n        tab: '',\n        t_content: '木耳敲回车',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(422, function (err, res) {\n        res.text.should.containEql('必须选择一个版块。');\n        done(err);\n      });\n    });\n\n    it('should not create a topic when no content', function (done) {\n      request.post('/topic/create')\n      .send({\n        title: '呵呵复呵呵',\n        tab: 'share',\n        t_content: '',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(422, function (err, res) {\n        res.text.should.containEql('内容不可为空');\n        done(err);\n      });\n    });\n\n    it('should create a topic', function (done) {\n      request.post('/topic/create')\n      .send({\n        title: '呵呵复呵呵' + new Date(),\n        tab: 'share',\n        t_content: '木耳敲回车',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(302, function (err, res) {\n        res.headers.location.should.match(/^\\/topic\\/\\w+$/);\n        done(err);\n      });\n    });\n  });\n\n  describe('#showEdit', function () {\n    it('should show a edit page', function (done) {\n      request.get('/topic/' + support.testTopic._id + '/edit')\n      .set('Cookie', support.normalUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('编辑话题');\n        done(err);\n      });\n    });\n  });\n\n  describe('#update', function () {\n    it('should update a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/edit')\n      .send({\n        title: '修改后的 topic title',\n        tab: 'share',\n        t_content: '修改后的木耳敲回车',\n      })\n      .set('Cookie', support.normalUserCookie)\n      .expect(302, function (err, res) {\n        res.headers.location.should.match(/^\\/topic\\/\\w+$/);\n        done(err);\n      });\n    });\n  });\n\n  describe('#delete', function () {\n    var wouldBeDeleteTopic;\n    before(function (done) {\n      support.createTopic(support.normalUser._id, function (err, topic) {\n        wouldBeDeleteTopic = topic;\n        done(err);\n      });\n    });\n\n    it('should not delete a topic when not author', function (done) {\n      request.post('/topic/' + wouldBeDeleteTopic._id + '/delete')\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(403, function (err, res) {\n        res.body.should.eql({success: false, message: '无权限'});\n        done(err);\n      });\n    });\n\n    it('should delele a topic', function (done) {\n      request.post('/topic/' + wouldBeDeleteTopic._id + '/delete')\n      .set('Cookie', support.normalUserCookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({ success: true, message: '话题已被删除。' });\n        done(err);\n      });\n    });\n  });\n\n  describe('#top', function () {\n    it('should top a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/top')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已置顶。');\n        done(err);\n      });\n    });\n\n    it('should untop a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/top')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已取消置顶');\n        done(err);\n      });\n    });\n  });\n\n  describe('#good', function () {\n    it('should good a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/good')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已加精。');\n        done(err);\n      });\n    });\n\n    it('should ungood a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/good')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已取消加精。');\n        done(err);\n      });\n    });\n  });\n\n  describe('#collect', function () {\n    it('should collect a topic', function (done) {\n      request.post('/topic/collect')\n      .send({\n        topic_id: support.testTopic._id,\n      })\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'success'});\n        done(err);\n      })\n    })\n\n    it('should not collect a topic twice', function (done) {\n      request.post('/topic/collect')\n      .send({\n        topic_id: support.testTopic._id,\n      })\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'failed'});\n        done(err);\n      })\n    })\n  })\n\n  describe('#de_collect', function () {\n    it('should decollect a topic', function (done) {\n      request.post('/topic/de_collect')\n      .send({\n        topic_id: support.testTopic._id,\n      })\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'success'});\n        done(err);\n      });\n    });\n\n    it('should not decollect a non-exist topic_collect', function (done) {\n      request.post('/topic/de_collect')\n      .send({\n        topic_id: support.testTopic._id,\n      })\n      .set('Cookie', support.normalUser2Cookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'failed'});\n        done(err);\n      });\n    });\n  });\n\n  describe('#upload', function () {\n    it('should upload a file', function (done) {\n\n      mm(store, 'upload', function (file, options, callback) {\n        callback(null, {\n          url: 'upload_success_url'\n        });\n      });\n      request.post('/upload')\n      .attach('selffile', __filename)\n      .set('Cookie', support.normalUser2Cookie)\n      .end(function (err, res) {\n        res.body.should.eql({\"success\": true, \"url\": \"upload_success_url\"});\n        done(err);\n      });\n    });\n  });\n\n  describe('#lock', function () {\n    it('should lock a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/lock')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已锁定。');\n        done(err);\n      });\n    });\n\n    it('should not reply a locked topic', function (done) {\n      var topic = support.testTopic;\n      request.post('/' + topic._id + '/reply')\n      .set('Cookie', support.normalUserCookie)\n      .send({\n        r_content: 'test reply 1'\n      })\n      .expect(403)\n      .end(function (err, res) {\n        res.text.should.equal('此主题已锁定。');\n        done(err);\n      });\n    });\n\n    it('should unlock a topic', function (done) {\n      request.post('/topic/' + support.testTopic._id + '/lock')\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('此话题已取消锁定。');\n        done(err);\n      });\n    });\n\n    it('should reply a unlocked topic', function (done) {\n      var topic = support.testTopic;\n      request.post('/' + topic._id + '/reply')\n      .set('Cookie', support.normalUserCookie)\n      .send({\n        r_content: 'test reply 1'\n      })\n      .expect(302)\n      .end(function (err, res) {\n        done(err);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/controllers/user.test.js",
    "content": "/*!\n * nodeclub - user controller test\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar should = require('should');\nvar app = require('../../app');\nvar request = require('supertest')(app);\nvar mm = require('mm');\nvar support = require('../support/support');\nvar _ = require('lodash');\nvar pedding = require('pedding');\nvar UserProxy = require('../../proxy/user');\nvar ReplyModel = require('../../models').Reply;\n\ndescribe('test/controllers/user.test.js', function () {\n  var testUser;\n  before(function (done) {\n    done = pedding(done, 2);\n    support.ready(done);\n    support.createUser(function (err, user) {\n      testUser = user;\n      done(err);\n    });\n  });\n\n  describe('#index', function () {\n    it('should show user index', function (done) {\n      request.get('/user/' + testUser.loginname)\n      .expect(200, function (err, res) {\n        var texts = [\n          '注册时间',\n          '这家伙很懒，什么个性签名都没有留下。',\n          '最近创建的话题',\n          '无话题',\n          '最近参与的话题',\n          '无话题'\n        ];\n        texts.forEach(function (text) {\n          res.text.should.containEql(text);\n        });\n        done(err);\n      });\n    });\n  });\n\n  describe('#listStars', function () {\n    it('should show star uses', function (done) {\n      request.get('/stars')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('社区达人');\n        done(err);\n      });\n    });\n  });\n\n  describe('#showSetting', function () {\n    it('should show setting page', function (done) {\n      request.get('/setting')\n      .set('Cookie', support.normalUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('同时决定了 Gravatar 头像');\n        res.text.should.containEql('Access Token');\n        done(err);\n      });\n    });\n\n    it('should show success info', function (done) {\n      request.get('/setting')\n      .query({save: 'success'})\n      .set('Cookie', support.normalUserCookie)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('保存成功。');\n        done(err);\n      });\n    });\n  });\n\n  describe('#setting', function () {\n    var userInfo;\n    before(function () {\n      userInfo = {\n        url: 'http://fxck.it',\n        location: 'west lake',\n        weibo: 'http://weibo.com/tangzhanli',\n        github: '@alsotang',\n        signature: '仍然很懒',\n        name: support.normalUser.loginname,\n        email: support.normalUser.email,\n      };\n    });\n\n    it('should change user setting', function (done) {\n      userInfo = _.cloneDeep(userInfo);\n      userInfo.action = 'change_setting';\n      request.post('/setting')\n      .set('Cookie', support.normalUserCookie)\n      .send(userInfo)\n      .expect(302, function (err, res) {\n        res.headers.location.should.equal('/setting?save=success');\n        done(err);\n      });\n    });\n\n    it('should change user password', function (done) {\n      userInfo = _.cloneDeep(userInfo);\n      userInfo.action = 'change_password';\n      userInfo.old_pass = 'pass';\n      userInfo.new_pass = 'passwordchanged';\n      request.post('/setting')\n      .set('Cookie', support.normalUserCookie)\n      .send(userInfo)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('密码已被修改。');\n        done(err);\n      });\n    });\n\n    it('should not change user password when old_pass is wrong', function (done) {\n      userInfo = _.cloneDeep(userInfo);\n      userInfo.action = 'change_password';\n      userInfo.old_pass = 'wrong_old_pass';\n      userInfo.new_pass = 'passwordchanged';\n      request.post('/setting')\n      .set('Cookie', support.normalUserCookie)\n      .send(userInfo)\n      .expect(200, function (err, res) {\n        res.text.should.containEql('当前密码不正确。');\n        done(err);\n      });\n    });\n  });\n\n  describe('#toggleStar', function () {\n    it('should not set star user when no user_id', function (done) {\n      request.post('/user/set_star')\n      .set('Cookie', support.adminUserCookie)\n      .expect(500, function (err, res) {\n        res.text.should.containEql('user is not exists');\n        done(err);\n      });\n    });\n\n    it('should set star user', function (done) {\n      request.post('/user/set_star')\n      .send({\n        user_id: support.normalUser._id\n      })\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'success'});\n\n        UserProxy.getUserById(support.normalUser._id, function (err, user) {\n          user.is_star.should.be.true();\n          done(err);\n        });\n      });\n    });\n\n    it('should unset star user', function (done) {\n      request.post('/user/set_star')\n      .send({\n        user_id: support.normalUser._id\n      })\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'success'});\n\n        UserProxy.getUserById(support.normalUser._id, function (err, user) {\n          user.is_star.should.be.false();\n          done(err);\n        });\n      });\n    });\n  });\n\n  describe('#getCollectTopics', function () {\n    it('should get /user/:name/collections ok', function (done) {\n      request.get('/user/' + support.normalUser.loginname + '/collections')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('收藏的话题');\n        done(err);\n      });\n    });\n  });\n\n  describe('#top100', function () {\n    it('should get /users/top100', function (done) {\n      request.get('/users/top100')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('Top100 积分榜');\n        done(err);\n      });\n    });\n  });\n\n  describe('#list_topics', function () {\n    it('should get /user/:name/topics ok', function (done) {\n      request.get('/user/' + support.normalUser.loginname + '/topics')\n      .expect(200, function (err, res) {\n        res.text.should.containEql('创建的话题');\n        done(err);\n      });\n    });\n  });\n\n  describe('#listReplies', function () {\n    it('should get /user/:name/replies ok', function (done) {\n      request.get('/user/' + support.normalUser.loginname + '/replies')\n      .expect(200, function (err, res) {\n        res.text.should.containEql(support.normalUser.loginname + ' 参与的话题');\n        done(err);\n      });\n    });\n  });\n\n  describe('#block', function () {\n    it('should block user', function (done) {\n      support.createUser(function (err, newuser) {\n        request.post('/user/' + newuser.loginname + '/block')\n        .send({\n          action: 'set_block'\n        })\n        .set('Cookie', support.adminUserCookie)\n        .expect(200, function (err, res) {\n          res.body.should.eql({status: 'success'});\n          UserProxy.getUserById(newuser._id, function (err, user) {\n            user.is_block.should.be.true();\n            done(err);\n          });\n        });\n      });\n    });\n\n    it('should unblock user', function (done) {\n      request.post('/user/' + support.normalUser.loginname + '/block')\n      .send({\n        action: 'cancel_block'\n      })\n      .set('Cookie', support.adminUserCookie)\n      .expect(200, function (err, res) {\n        res.body.should.eql({status: 'success'});\n        done(err);\n      })\n    })\n\n    it('should wrong when user is not exists', function (done) {\n      request.post('/user/not_exists_user/block')\n      .send({\n        action: 'set_block'\n      })\n      .set('Cookie', support.adminUserCookie)\n      .expect(500, function (err, res) {\n        res.text.should.containEql('user is not exists')\n        done(err);\n      })\n    })\n  })\n\n  describe('#delete_all', function () {\n    it('should delele all ups', function (done) {\n      support.createUser(function (err, user) {\n        var userId = user._id;\n        ReplyModel.findOne(function (err, reply) {\n          should.not.exists(err);\n          reply.ups.push(userId);\n          reply.save(function (err, reply) {\n            reply.ups.should.containEql(userId)\n\n            request.post('/user/' + user.loginname + '/delete_all')\n              .set('Cookie', support.adminUserCookie)\n              .expect(200, function (err, res) {\n                res.body.should.eql({ status: 'success' });\n\n                ReplyModel.findOne({_id: reply._id}, function (err, reply) {\n                  reply.ups.should.not.containEql(userId)\n                  done();\n                })\n              })\n          })\n        })\n      })\n    })\n  })\n});\n"
  },
  {
    "path": "test/env.js",
    "content": "var nock = require('nock');\nvar redis = require('../common/redis');\n\nnock.enableNetConnect(); // 允许真实的网络连接\n\nredis.flushdb(); // 清空 db 里面的所有内容\n"
  },
  {
    "path": "test/middlewares/conf.test.js",
    "content": "var conf = require('../../middlewares/conf');\nvar config = require('../../config');\n\ndescribe('test/middlewares/conf.test.js', function () {\n  it('should alert no github oauth', function (done) {\n    var _clientID = config.GITHUB_OAUTH.clientID;\n    config.GITHUB_OAUTH.clientID = 'your GITHUB_CLIENT_ID';\n    conf.github({}, {send: function (str) {\n      str.should.equal('call the admin to set github oauth.');\n      config.GITHUB_OAUTH.clientID = _clientID;\n      done();\n    }});\n  });\n});\n"
  },
  {
    "path": "test/middlewares/limit.test.js",
    "content": "var limitMiddleware = require('../../middlewares/limit');\nvar app = require('../../app');\nvar supertest;\nvar support = require('../support/support');\nvar pedding = require('pedding');\nvar visitor = 'visit' + Date.now();\n\ndescribe('test/middlewares/limit.test.js', function () {\n  before(function (done) {\n    support.ready(done);\n  });\n\n  before(function () {\n    app.get('/test_peripperday',\n      limitMiddleware.peripperday(visitor, 3, {showJson: true}), function (req, res) {\n        res.send('hello');\n      });\n\n    supertest = require('supertest')(app);\n  });\n  describe('#peripperday', function () {\n    it('should visit', function (done) {\n      supertest.get('/test_peripperday').set('x-real-ip', '127.0.0.1').end(function () {\n        supertest.get('/test_peripperday').set('x-real-ip', '127.0.0.1').end(function () {\n          supertest.get('/test_peripperday').set('x-real-ip', '127.0.0.1').end(function (err, res) {\n            res.text.should.eql('hello');\n            done();\n          });\n        });\n      });\n    });\n    it('should not visit', function (done) {\n      supertest.get('/test_peripperday')\n        .set('x-real-ip', '127.0.0.1')\n        .end(function (err, res) {\n          res.status.should.equal(403);\n          res.body.success.should.false();\n          done(err);\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "test/middlewares/proxy.test.js",
    "content": "var proxyMiddleware = require('../../middlewares/proxy');\nvar app = require('../../app');\nvar support = require('../support/support');\nvar supertest = require('supertest')(app);\nvar mm = require('mm');\nvar nock = require('nock');\n\ndescribe('test/middlewares/proxy.test.js', function () {\n  before(function (done) {\n    support.ready(done);\n  });\n\n  afterEach(function () {\n    mm.restore();\n  });\n\n  it('should forbidden google.com', function (done) {\n    supertest.get('/agent')\n      .query({\n        url: 'https://www.google.com.hk/#newwindow=1&q=%E5%85%AD%E5%9B%9B%E4%BA%8B%E4%BB%B6',\n      })\n      .end(function (err, res) {\n        res.text.should.equal('www.google.com.hk is not allowed');\n        done(err);\n      });\n  });\n\n  it('should allow githubusercontent.com', function (done) {\n    var url = 'https://avatars.githubusercontent.com/u/1147375?v=3&s=120';\n\n    nock('https://avatars.githubusercontent.com')\n      .get('/u/1147375?v=3&s=120')\n      .reply(200, 'githubusercontent');\n\n    supertest.get('/agent')\n      .query({\n        url: url,\n      })\n      .end(function (err, res) {\n        res.text.should.eql('githubusercontent');\n        done(err);\n      });\n  });\n\n  it('should allow gravatar.com', function (done) {\n    var url = 'https://gravatar.com/avatar/28d69c69c1c1a040436124238f7cc937?size=48';\n    nock('https://gravatar.com')\n      .get('/avatar/28d69c69c1c1a040436124238f7cc937?size=48')\n      .reply(200, 'gravatar');\n\n    supertest.get('/agent')\n      .query({\n        url: url,\n      })\n      .end(function (err, res) {\n        res.text.should.eql('gravatar');\n        done(err);\n      });\n  });\n\n});\n"
  },
  {
    "path": "test/models/user.test.js",
    "content": "var UserModel = require('../../models').User;\n\ndescribe('test/models/user.test.js', function () {\n  it('should return proxy avatar url', function () {\n    var user = new UserModel({email: 'alsotang@gmail.com'});\n    user.avatar_url.should.eql('https://gravatar.com/avatar/eeb90e7b92f78e01cac07087165e3640?size=48');\n  });\n});\n"
  },
  {
    "path": "test/proxy/message.test.js",
    "content": "var Message = require('../../proxy/message');\nvar should = require('should');\n\ndescribe('test/proxy/message.test.js', function () {\n});\n"
  },
  {
    "path": "test/proxy/reply.test.js",
    "content": "var Reply = require('../../proxy/reply');\nvar support = require('../support/support');\nvar should = require('should');\n\ndescribe('test/proxy/reply.test.js', function () {\n});\n"
  },
  {
    "path": "test/proxy/topic.test.js",
    "content": "var Topic = require('../../proxy/topic');\nvar support = require('../support/support');\nvar should = require('should');\n\ndescribe('test/proxy/topic.test.js', function () {\n});\n"
  },
  {
    "path": "test/proxy/user.test.js",
    "content": "var User = require('../../proxy/user');\nvar should = require('should');\nvar support = require('../support/support');\n\ndescribe('test/proxy/user.test.js', function () {\n});\n"
  },
  {
    "path": "test/support/support.js",
    "content": "var User = require('../../proxy/user');\nvar Topic = require('../../proxy/topic');\nvar Reply = require('../../proxy/reply');\nvar ready = require('ready');\nvar eventproxy = require('eventproxy');\nvar utility = require('utility');\nvar tools = require('../../common/tools');\n\nfunction randomInt() {\n  return (Math.random() * 10000).toFixed(0);\n}\n\nvar createUser = exports.createUser = function (callback) {\n  var key = new Date().getTime() + '_' + randomInt();\n  tools.bhash('pass', function (err, passhash) {\n    User.newAndSave('alsotang' + key, 'alsotang' + key, passhash, 'alsotang' + key + '@gmail.com', '', false, callback);\n  });\n};\n\nexports.createUserByNameAndPwd = function (loginname, pwd, callback) {\n  tools.bhash(pwd, function (err, passhash) {\n    User.newAndSave(loginname, loginname, passhash, loginname + +new Date() + '@gmail.com', '', true, callback);\n  });\n};\n\nvar createTopic = exports.createTopic = function (authorId, callback) {\n  var key = new Date().getTime() + '_' + randomInt();\n  Topic.newAndSave('topic title' + key, 'test topic content' + key, 'share', authorId, callback);\n};\n\nvar createReply = exports.createReply = function (topicId, authorId, callback) {\n  Reply.newAndSave('I am content', topicId, authorId, callback);\n};\n\nvar createSingleUp = exports.createSingleUp = function (replyId, userId, callback) {\n  Reply.getReply(replyId, function (err, reply) {\n    reply.ups = [];\n    reply.ups.push(userId);\n    reply.save(function (err, reply) {\n      callback(err, reply);\n    });\n  });\n};\n\nfunction mockUser(user) {\n  return 'mock_user=' + JSON.stringify(user) + ';';\n}\n\nready(exports);\n\nvar ep = new eventproxy();\nep.fail(function (err) {\n  console.error(err);\n});\n\nep.all('user', 'user2', 'admin', function (user, user2, admin) {\n  exports.normalUser = user;\n  exports.normalUserCookie = mockUser(user);\n\n  exports.normalUser2 = user2;\n  exports.normalUser2Cookie = mockUser(user2);\n\n  var adminObj = JSON.parse(JSON.stringify(admin));\n  adminObj.is_admin = true;\n  exports.adminUser = admin;\n  exports.adminUserCookie = mockUser(adminObj);\n\n  createTopic(user._id, ep.done('topic'));\n});\ncreateUser(ep.done('user'));\ncreateUser(ep.done('user2'));\ncreateUser(ep.done('admin'));\n\nep.all('topic', function (topic) {\n  exports.testTopic = topic;\n  createReply(topic._id, exports.normalUser._id, ep.done('reply'));\n});\n\nep.all('reply', function (reply) {\n  exports.testReply = reply;\n  exports.ready(true);\n});\n\n\n\n"
  },
  {
    "path": "views/_ads.html",
    "content": ""
  },
  {
    "path": "views/_sponsors.html",
    "content": "<div class='col_fade'>\n  <p>CNode 社区为国内最专业的 Node.js 开源技术社区，致力于 Node.js 的技术研究。</p>\n  <p>服务器搭建在\n    <a href=\"https://www.digitalocean.com/?refcode=eba02656eeb3\" target=\"_blank\"\n      class=\"sponsor_outlink\" data-label=\"digitalocean\">\n      <img src=\"<%- staticFile('/public/images/digitalocean.png') %>\" title=\"digitalocean\"\n      alt=\"digitalocean\" width=\"92px\"/>\n    </a>\n  ，存储赞助商为\n    <a href=\"http://www.qiniu.com/?ref=cnode\" target=\"_blank\"\n      class=\"sponsor_outlink\" data-label=\"qiniu_bottom\">\n      <img src=\"<%- staticFile('/public/images/qiniu.png') %>\" title=\"七牛云存储\"\n      alt=\"七牛云存储\" width=\"115px\"/>\n    </a>\n  </p>\n  <p>新手搭建 Node.js 服务器，推荐使用无需备案的 <a href=\"https://www.digitalocean.com/?refcode=eba02656eeb3\">DigitalOcean(https://www.digitalocean.com/)</a></p>\n</div>\n"
  },
  {
    "path": "views/editor_sidebar.html",
    "content": "<div id='sidebar'>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>Markdown 语法参考</span>\n    </div>\n    <div class='inner'>\n      <ol>\n        <li><tt>### 单行的标题</tt></li>\n        <li><tt>**粗体**</tt></li>\n        <li><tt>`console.log('行内代码')`</tt></li>\n        <li><tt>```js\\n code \\n```</tt> 标记代码块</li>\n        <li><tt>[内容](链接)</tt></li>\n        <li><tt>![文字说明](图片链接)</tt></li>\n      </ol>\n      <span><a href='https://segmentfault.com/markdown' target='_blank'>Markdown 文档</a></span>\n    </div>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>话题发布指南</span>\n    </div>\n    <div class='inner'>\n      <ol>\n        <li>尽量把话题要点浓缩到标题里</li>\n        <li>代码含义和报错可在 <a href=\"http://segmentfault.com/t/node.js\">SegmentFault</a> 提问</li>\n      </ol>\n    </div>\n  </div>\n\n</div>\n"
  },
  {
    "path": "views/includes/editor.html",
    "content": "<%- Loader('/public/editor.min.js')\n.js('/public/libs/editor/editor.js')\n.js('/public/libs/webuploader/webuploader.withoutimage.js')\n.js('/public/libs/editor/ext.js')\n.done(assets, config.site_static_host, config.mini_assets)\n%>\n"
  },
  {
    "path": "views/index.html",
    "content": "<%- partial('sidebar') %>\n\n<div id=\"content\">\n  <div class=\"panel\">\n    <div class=\"header\">\n      <% [['all', '全部'], ['good', '精华']].concat(tabs).forEach(function (pair) {\n        var value = pair[0];\n        var text = pair[1]; %>\n        <a href=\"<%= value ? '/?tab=' + value : '/' %>\"\n          class=\"topic-tab <%= value === tab ? 'current-tab' : '' %>\"><%= text %></a>\n      <% }) %>\n    </div>\n    <% if (typeof(topics) !== 'undefined' && topics.length > 0) { %>\n    <div class=\"inner no-padding\">\n      <%- partial('topic/list', {\n      topics: topics,\n      pages: pages,\n      current_page: current_page,\n      base: '/'\n      }) %>\n    </div>\n    <% } else { %>\n    <div class=\"inner\">\n      <p>无话题</p>\n    </div>\n    <% } %>\n  </div>\n</div>\n"
  },
  {
    "path": "views/layout.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <!-- meta -->\n  <meta charset=\"utf-8\"/>\n  <meta name='description' content='<%= config.description %>'>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n  <meta name=\"keywords\" content=\"<%= config.keywords %>\"/>\n  <!-- see http://smerity.com/articles/2013/where_did_all_the_http_referrers_go.html -->\n  <meta name=\"referrer\" content=\"always\">\n\n  <%\n  var headers = config.site_headers || [];\n  for (var i = 0, l = headers.length; i < l; i++) {\n  %>\n  <%- headers[i] %>\n  <% } %>\n\n  <link title=\"RSS\" type=\"application/rss+xml\" rel=\"alternate\" href=\"/rss\"/>\n\n  <% if (config.site_icon) { %>\n  <link rel=\"icon\" href=\"<%- staticFile(config.site_icon) %>\" type=\"image/x-icon\"/>\n  <% } %>\n\n  <!-- style -->\n  <%- Loader('/public/stylesheets/index.min.css')\n  .css('/public/libs/bootstrap/css/bootstrap.css')\n  .css('/public/stylesheets/common.css')\n  .css('/public/stylesheets/style.less')\n  .css('/public/stylesheets/responsive.css')\n  .css('/public/stylesheets/jquery.atwho.css')\n  .css('/public/libs/editor/editor.css')\n  .css('/public/libs/webuploader/webuploader.css')\n  .css('/public/libs/code-prettify/prettify.css')\n  .css('/public/libs/font-awesome/css/font-awesome.css')\n  .done(assets, config.site_static_host, config.mini_assets)\n  %>\n\n  <!-- scripts -->\n  <%- Loader('/public/index.min.js')\n  .js('/public/libs/jquery-2.1.0.js')\n  .js('/public/libs/lodash.compat.js')\n  .js('/public/libs/jquery-ujs.js')\n  .js('/public/libs/bootstrap/js/bootstrap.js')\n  .js('/public/libs/jquery.caret.js')\n  .js('/public/libs/jquery.atwho.js')\n  .js('/public/libs/markdownit.js')\n  .js('/public/libs/code-prettify/prettify.js')\n  .js('/public/libs/qrcode.js')\n  .js('/public/javascripts/main.js')\n  .js('/public/javascripts/responsive.js')\n  .done(assets, config.site_static_host, config.mini_assets)\n  %>\n\n  <% if (typeof(topic) !== 'undefined') { %>\n  <title><%= topic.title %> - <%= config.name %></title>\n  <% } else if (typeof(pageTitle) !== 'undefined') { %>\n  <title><%= pageTitle %> - <%= config.name %></title>\n  <% } else { %>\n  <title><%= config.description %></title>\n  <% } %>\n  <meta content=\"_csrf\" name=\"csrf-param\">\n  <meta content=\"<%= csrf %>\" name=\"csrf-token\">\n</head>\n<body>\n<!-- navbar -->\n<div class='navbar'>\n  <div class='navbar-inner'>\n    <div class='container'>\n      <a class='brand' href='/'>\n        <% if (config.site_logo) {%>\n          <img src=\"<%- staticFile(config.site_logo) %>\" />\n        <%} else {%>\n          <%- config.name %>\n        <%}%>\n      </a>\n\n      <form id='search_form' class='navbar-search' action=\"/search\">\n        <input type='text' id='q' name='q' class='search-query span3' value=''/>\n      </form>\n      <ul class='nav pull-right'>\n        <li><a href='/'>首页</a></li>\n        <% if (current_user){ %>\n        <li>\n            <a href='/my/messages'>\n              <% if (current_user.messages_count) { %>\n                <span class='big messages_count'><%= current_user.messages_count %></span>\n              <% } %>\n              未读消息\n            </a>\n        </li>\n        <% } %>\n        <li><a href='/getstart'>新手入门</a></li>\n        <li><a href='/api'>API</a></li>\n        <%\n        var site_navs = config.site_navs || [];\n        for (var i = 0, l = site_navs.length; i < l; i++) {\n        var nav = site_navs[i];\n        %>\n        <li><a href=\"<%- nav[0] %>\" target=\"<%- nav[2] %>\"><%- nav[1] %></a></li>\n        <% } %>\n        <% if (current_user) { %>\n        <li><a href='/setting'>设置</a></li>\n        <li>\n          <a href='/signout' data-method=\"post\" rel=\"nofollow\">退出</a>\n        </li>\n        <% } else { %>\n        <li><a href='/signup'>注册</a></li>\n        <li><a href='/signin'>登录</a></li>\n        <% } %>\n      </ul>\n      <a class=\"btn btn-navbar\" id=\"responsive-sidebar-trigger\">\n        <span class=\"icon-bar\"></span>\n        <span class=\"icon-bar\"></span>\n        <span class=\"icon-bar\"></span>\n      </a>\n    </div>\n  </div>\n</div>\n<div id='main'>\n  <%- body %>\n</div>\n<div id='backtotop'>回到顶部</div>\n<div id='footer'>\n  <div id='footer_main'>\n    <div class=\"links\">\n      <a class='dark' href='/rss'>RSS</a>\n      |\n      <a class='dark' href='https://github.com/cnodejs/nodeclub/'>源码地址</a>\n    </div>\n\n    <%- partial('./_sponsors') %>\n\n  </div>\n</div>\n<div id=\"sidebar-mask\"></div>\n\n<% if (config.google_tracker_id) { %>\n<script>\n  // google analytics\n  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n  })(window,document,'script', \"<%- proxy('https://www.google-analytics.com/analytics.js') %>\",'ga');\n\n  ga('create', '<%-config.google_tracker_id%>', 'auto');\n  ga('send', 'pageview');\n</script>\n<% } %>\n\n<% if (config.cnzz_tracker_id) { %>\n<div style=\"display:none;\">\n  <script src=\"//s95.cnzz.com/z_stat.php?id=<%- config.cnzz_tracker_id %>&web_id=<%- config.cnzz_tracker_id %>\" language=\"JavaScript\"></script>\n</div>\n<% } %>\n\n</body>\n</html>\n"
  },
  {
    "path": "views/message/index.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>新消息</li>\n      </ul>\n    </div>\n    <% if (typeof(hasnot_read_messages) !== 'undefined' && hasnot_read_messages.length > 0) { %>\n    <%- partial('../message/message', { collection: hasnot_read_messages, as: 'message' }) %>\n    <% } else { %>\n    <div class='inner'>\n      <p>无消息</p>\n    </div>\n    <% } %>\n  </div>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>过往信息</span>\n    </div>\n    <% if (typeof(has_read_messages) !== 'undefined' && has_read_messages.length > 0) { %>\n    <%- partial('../message/message', { collection: has_read_messages, as: 'message' }) %>\n    <% } else { %>\n    <div class='inner'>\n      <p>无消息</p>\n    </div>\n    <% } %>\n  </div>\n</div>\n"
  },
  {
    "path": "views/message/message.html",
    "content": "<% if (message.has_read) { %>\n<div class='cell' message_id='<%= message._id %>'>\n  <% } else { %>\n  <div class='cell message' message_id='<%= message._id %>'>\n    <% } %>\n    <% if(message.type == 'reply'){ %>\n\t\t<span>\n\t\t\t<a href=\"/user/<%= message.author.loginname %>\" target='_blank'><%= message.author.loginname %></a>\n\t\t\t回复了你的话题\n\t\t\t<a href=\"/topic/<%- message.topic._id + (message.reply ? '#' + message.reply._id : '') %>\" target='_blank'><%=\n        message.topic.title %></a>\n\t\t</span>\n    <% } %>\n    <% if(message.type == 'reply2'){ %>\n\t\t<span>\n\t\t\t<a href=\"/user/<%= message.author.loginname %>\" target='_blank'><%= message.author.loginname %></a>\n\t\t\t在话题\n\t\t\t<a href=\"/topic/<%= message.topic._id + (message.reply ? '#' + message.reply._id : '') %>\" target='_blank'><%=\n        message.topic.title %></a>\n\t\t\t中回复了你的回复\n\t\t</span>\n    <% } %>\n    <% if(message.type == 'follow'){ %>\n\t<span>\n\t\t<a href=\"/user/<%= message.author.loginname %>\" target='_blank'><%= message.author.loginname %></a>\n\t\t关注了你\n\t</span>\n    <% } %>\n    <% if (message.type == 'at'){ %>\n\t\t<span>\n\t\t\t<a href=\"/user/<%= message.author.loginname %>\" target='_blank'><%= message.author.loginname %></a>\n\t\t\t在话题\n\t\t\t<a href=\"/topic/<%= message.topic._id + (message.reply ? '#' + message.reply._id : '') %>\" target='_blank'><%=\n        message.topic.title %></a>\n\t\t\t中@了你\n\t\t</span>\n    <% } %>\n\n  </div>\n"
  },
  {
    "path": "views/notify/notify.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>通知</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if(typeof(error) !== 'undefined' && error){ %>\n      <div class=\"alert alert-error\">\n        <strong><%= error %></strong>\n      </div>\n      <% } %>\n      <% if(typeof(success) !== 'undefined' && success){ %>\n      <div class=\"alert alert-success\">\n        <strong><%= success %></strong>\n      </div>\n      <% } %>\n\n        <a href=\"<%- typeof(referer) !== 'undefined' ? referer : '/' %>\"><span class=\"span-common\">返回</span></a>\n\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/reply/edit.html",
    "content": "<%- partial('../editor_sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ol class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>编辑回复</li>\n      </ol>\n    </div>\n    <div class='inner post'>\n      <% if(typeof(edit_error) !== 'undefined' && edit_error){ %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= edit_error %></strong>\n      </div>\n      <% } %>\n      <% if(typeof(error) !== 'undefined' && error){ %>\n      <div class=\"alert alert-error\">\n        <strong><%= error %></strong>\n      </div>\n      <% }else{ %>\n      <form id='edit_reply_form' action='/reply/<%= reply_id %>/edit' method='post'>\n        <fieldset>\n          <div class='markdown_editor in_editor'>\n            <div class='markdown_in_editor'>\n              <textarea class='editor' name='t_content' rows='20'\n                        placeholder='回复支持 Markdown 语法, 请注意标记代码'\n                        autofocus\n                      ><%= typeof(content) !== 'undefined' && content || '' %></textarea>\n\n              <div class='editor_buttons'>\n                <input type=\"submit\" class='span-primary submit_btn' data-loading-text=\"提交中..\"\n                       value=\"提交\">\n              </div>\n            </div>\n\n          </div>\n\n          <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n        </fieldset>\n      </form>\n    </div>\n    <% } %>\n  </div>\n</div>\n\n<!-- markdown editor -->\n<%- partial('../includes/editor') %>\n<script>\n  (function () {\n    var editor = new Editor();\n    editor.render($('.editor')[0]);\n  })();\n</script>\n"
  },
  {
    "path": "views/reply/reply.html",
    "content": "<div class='cell reply_area reply_item\n  <%- reply.ups && reply.ups.length >= topic.reply_up_threshold ? 'reply_highlight' : '' %>'\n    reply_id=\"<%= reply._id %>\" reply_to_id=\"<%= reply.reply_id || '' %>\" id=\"<%= reply._id %>\">\n  <div class='author_content'>\n    <a href=\"/user/<%= reply.author.loginname %>\" class=\"user_avatar\">\n      <img src=\"<%= proxy(reply.author.avatar_url) %>\" title=\"<%= reply.author.loginname %>\"/></a>\n\n    <div class='user_info'>\n      <a class='dark reply_author' href=\"/user/<%= reply.author.loginname %>\"><%= reply.author.loginname %></a>\n      <a class=\"reply_time\" href=\"#<%= reply._id %>\"><%= indexInCollection + 1 %>楼•<%= reply.create_at_ago()\n        %></a>\n      <% if(reply.author.loginname == topic.author.loginname){ %>\n        <span class=\"reply_by_author\">作者</span>\n      <% } %>\n    </div>\n    <div class='user_action'>\n      <span>\n        <i class=\"fa up_btn\n          <%= (current_user && is_uped(current_user, reply)) ? 'fa-thumbs-up uped' : 'fa-thumbs-o-up' %>\n          <%= (!reply.ups || !reply.ups.length) ? 'invisible' : '' %>\" title=\"喜欢\"></i>\n        <span class=\"up-count\">\n          <%= reply.ups && reply.ups.length ? reply.ups.length : '' %>\n        </span>\n      </span>\n      <% if (current_user && current_user.is_admin ||\n      (current_user && current_user._id.toString() == reply.author._id.toString())\n      ) { %>\n      <a href='/reply/<%= reply._id %>/edit' class='edit_reply_btn'>\n        <i class=\"fa fa-pencil-square-o\" title='编辑'></i>\n      </a>\n      <a href='javascript:void(0);' class='delete_reply_btn'>\n        <i class=\"fa fa-trash\" title='删除'></i>\n      </a>\n      <% } %>\n      <span>\n        <% if (current_user){ %>\n          <i class=\"fa fa-reply reply2_btn\" title=\"回复\"></i>\n        <% } %>\n      </span>\n    </div>\n  </div>\n  <div class='reply_content from-<%= reply.author.loginname %>'>\n    <%- markdown(reply.content) %>\n  </div>\n  <div class='clearfix'>\n    <div class='reply2_area'>\n      <% if (current_user) { %>\n      <form class='reply2_form' action='/<%= topic._id %>/reply' method='post'>\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n        <input type='hidden' name='reply_id' value='<%= reply._id %>'/>\n\n        <div class='markdown_editor in_editor'>\n          <div class='markdown_in_editor'>\n            <textarea class='span8 editor reply_editor'\n                      id=\"reply2_editor_<%= reply._id %>\" name='r_content' rows='4'></textarea>\n\n            <div class='editor_buttons'>\n              <input class='span-primary reply2_submit_btn submit_btn'\n                     type=\"submit\" data-id='<%= reply._id %>' data-loading-text=\"回复中..\" value=\"回复\">\n            </div>\n          </div>\n\n        </div>\n\n      </form>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/sidebar.html",
    "content": "<div id='sidebar'>\n\n  <div class='panel'>\n    <% if (typeof(user) !== 'undefined' || current_user) { %>\n    <div class='header'>\n      <span class='col_fade'>个人信息</span>\n    </div>\n    <div class='inner'>\n      <%- partial('user/card',{ object: typeof(user) === 'undefined' ? current_user : user, as: 'user' }) %>\n    </div>\n    <% } else { %>\n    <div class='inner'>\n      <p><%= config.description %></p>\n\n      <div>\n        您可以\n        <a href='/signin'>登录</a>\n        或\n        <a href='/signup'>注册</a>\n        , 也可以\n        <a href=\"/auth/github\">\n          <span class=\"span-info\">\n            通过 GitHub 登录\n          </span>\n        </a>\n      </div>\n    </div>\n    <% } %>\n  </div>\n\n  <% if (current_user) { %>\n  <div class=\"panel\">\n    <div class='inner'>\n      <a href='/topic/create' id='create_topic_btn'>\n        <span class='span-success'>发布话题</span>\n      </a>\n    </div>\n  </div>\n  <% } %>\n\n  <% if (!current_user || !current_user.isAdvanced) { %>\n    <%- partial('./_ads') %>\n  <% } %>\n\n  <% if (typeof(no_reply_topics) !== 'undefined') { %>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>无人回复的话题</span>\n    </div>\n    <div class='inner'>\n      <% if (no_reply_topics.length > 0) { %>\n      <ul class=\"unstyled\">\n        <%- partial('topic/small', { collection: no_reply_topics, as: 'topic' }) %>\n      </ul>\n      <% } else { %>\n      <p>无</p>\n      <% } %>\n    </div>\n  </div>\n  <% } %>\n\n  <% if (typeof(tops) !== 'undefined') { %>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>积分榜</span>\n      &nbsp;\n      <a class='dark' href='/users/top100'>TOP 100 &gt;&gt;</a>\n    </div>\n    <div class='inner'>\n      <% if (tops.length > 0) { %>\n      <ol>\n        <%- partial('user/top', { collection: tops, as: 'user' }) %>\n      </ol>\n      <% } else { %>\n      <p>无</p>\n      <% } %>\n    </div>\n  </div>\n  <% } %>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>友情社区</span>\n    </div>\n    <div class='inner'>\n      <ol class=\"friendship-community\">\n        <li>\n          <a href=\"https://ruby-china.org/\" target=\"_blank\">\n            <img src=\"<%- staticFile('/public/images/ruby-china-20150529.png') %>\">\n          </a>\n        </li>\n        <div class=\"sep10\"></div>\n        <li>\n          <a href=\"http://golangtc.com/\" target=\"_blank\">\n            <img src=\"<%- staticFile('/public/images/golangtc-logo.png') %>\">\n          </a>\n        </li>\n        <div class=\"sep10\"></div>\n        <li>\n          <a href=\"http://phphub.org/\" target=\"_blank\">\n            <img src=\"<%- staticFile('/public/images/phphub-logo.png') %>\">\n          </a>\n        </li>\n      </ol>\n    </div>\n  </div>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>客户端二维码</span>\n    </div>\n    <div class='inner cnode-app-download'>\n      <img width='200' src=\"//static.cnodejs.org/FtG0YVgQ6iginiLpf9W4_ShjiLfU\">\n      <br />\n      <a href=\"https://github.com/soliury/noder-react-native\" target=\"_blank\">客户端源码地址</a>\n    </div>\n  </div>\n\n</div>\n"
  },
  {
    "path": "views/sign/new_oauth.html",
    "content": "<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>通过GitHub登录成功</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <form id='signin_form' class='form-horizontal' action=<%= actionPath\n      %> method='post'>\n      <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n      <input type='hidden' name='isnew' value='1'/>\n\n      <div class='control-group'>\n        <label class='control-label'>通过 GitHub 帐号</label>\n\n        <div class='controls'>\n          <input type='submit' class='span-info' value=\"直接进入\">\n        </div>\n      </div>\n      </form>\n\n      <form id='signin_form' class='form-horizontal' action=<%= actionPath\n      %> method='post'>\n      <div class='control-group'>\n        <label class='controls'>或者</label>\n      </div>\n      <div class='control-group'>\n        <label class='control-label' for='name'>用户名</label>\n\n        <div class='controls'>\n          <input class='input-xlarge' id='name' name='name' size='30' type='text'/>\n        </div>\n      </div>\n      <div class='control-group'>\n        <label class='control-label' for='pass'>密码</label>\n\n        <div class='controls'>\n          <input class='input-xlarge' id='pass' name='pass' size='30' type='password'/>\n        </div>\n      </div>\n      <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n\n      <div class='form-actions'>\n        <input type='submit' class='span-primary' value='关联旧账号'/>\n      </div>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/sign/no_github_email.html",
    "content": "GitHub 登陆出错\n\n<ul>\n  <li>可能是您 GitHub 账号的 Email 已经在 CNode 注册过了。</li>\n  <li>\n    <p>也可能是您的 GitHub 没有提供公开的 Profile Email 导致注册失败。</p>\n\n    <p>请访问：https://github.com/settings/profile 设置您的公开 Email 地址。请确保您 GitHub 的个人主页有可见的 Email，如下图：</p></li>\n  <a href=\"https://github.com/alsotang\" target=\"_blank\"><img src=\"//static.cnodejs.org/QQ20140807-1.png\"></a>\n</ul>\n\n\n"
  },
  {
    "path": "views/sign/reset.html",
    "content": "<%- partial('../sign/sidebar') %>\r\n\r\n<div id='content'>\r\n  <div class='panel'>\r\n    <div class='header'>\r\n      <ul class='breadcrumb'>\r\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\r\n        <li class='active'>重置密码</li>\r\n      </ul>\r\n    </div>\r\n    <div class='inner'>\r\n      <% if(typeof(error) !== 'undefined' && error){ %>\r\n      <div class=\"alert alert-error\">\r\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\r\n        <strong><%= error %></strong>\r\n      </div>\r\n      <% } %>\r\n      <form id='signin_form' class='form-horizontal' action='/reset_pass' method='post'>\r\n        <div class='control-group'>\r\n          <label class='control-label' for='psw'>新密码</label>\r\n\r\n          <div class='controls'>\r\n            <input class='input-xlarge' id='psw' name='psw' size='30' type='password'/>\r\n          </div>\r\n        </div>\r\n        <div class='control-group'>\r\n          <label class='control-label' for='repsw'>确认密码</label>\r\n\r\n          <div class='controls'>\r\n            <input class='input-xlarge' id='repsw' name='repsw' size='30' type='password'/>\r\n          </div>\r\n        </div>\r\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\r\n        <input type='hidden' name='name' id='name' value='<%= name%>'>\r\n        <input type='hidden' name='key' id='key' value='<%= key%>'>\r\n\r\n        <div class='form-actions'>\r\n          <input type='submit' class='span-primary' value='确定'/>\r\n        </div>\r\n      </form>\r\n    </div>\r\n  </div>\r\n</div>\r\n"
  },
  {
    "path": "views/sign/search_pass.html",
    "content": "<%- partial('../sign/sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>找回密码</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if (typeof(error) !== 'undefined' && error) { %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= error %></strong>\n      </div>\n      <% } %>\n      <form id='search_pass_form' class='form-horizontal' action='/search_pass' method='post'>\n        <div class='control-group'>\n          <label class='control-label' for='email'>电子邮箱</label>\n\n          <div class='controls'>\n            <% if (typeof(email) !== 'undefined') { %>\n            <input class='input-xlarge' id='email' name='email' size='30' type='text' value='<%= email %>'/>\n            <% } else { %>\n            <input class='input-xlarge' id='email' name='email' size='30' type='text'/>\n            <% } %>\n            <p class='help-block'>请输入您注册帐户时使用的电子邮箱</p>\n          </div>\n          <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n        </div>\n        <div class='form-actions'>\n          <input type='submit' class='span-primary' value='提交'/>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/sign/sidebar.html",
    "content": "<div id='sidebar'>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>关于</span>\n    </div>\n    <div class='inner'>\n      <p><%= config.description %></p>\n\n      <p>在这里你可以：</p>\n      <ul>\n        <li>向别人提出你遇到的问题</li>\n        <li>帮助遇到问题的人</li>\n        <li>分享自己的知识</li>\n        <li>和其它人一起进步</li>\n      </ul>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "views/sign/signin.html",
    "content": "<%- partial('../sign/sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>登录</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if(typeof(error) !== 'undefined' && error){ %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= error %></strong>\n      </div>\n      <% } %>\n      <form id='signin_form' class='form-horizontal' action='/signin' method='post'>\n        <div class='control-group'>\n          <label class='control-label' for='name'>用户名</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='name' name='name' size='30' type='text'/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='pass'>密码</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='pass' name='pass' size='30' type='password'/>\n          </div>\n        </div>\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n\n        <div class='form-actions'>\n          <input type='submit' class='span-primary' value='登录'/>\n          <a href=\"/auth/github\">\n            <span class=\"span-info\">\n              通过 GitHub 登录\n            </span>\n          </a>\n          <a id=\"forgot_password\" href='/search_pass'>忘记密码了?</a>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/sign/signup.html",
    "content": "<%- partial('../sign/sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>注册</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if (typeof(error) !== 'undefined' && error) { %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= error %></strong>\n      </div>\n      <% } %>\n      <% if (typeof(success) !== 'undefined' && success) { %>\n      <div class=\"alert alert-success\">\n        <strong><%= success %></strong>\n      </div>\n      <% } else { %>\n      <form id='signup_form' class='form-horizontal' action='/signup' method='post'>\n        <div class='control-group'>\n          <label class='control-label' for='loginname'>用户名</label>\n\n          <div class='controls'>\n            <% if (typeof(loginname) !== 'undefined') { %>\n            <input class='input-xlarge' id='loginname' name='loginname' size='30' type='text' value='<%= loginname %>'/>\n            <% } else { %>\n            <input class='input-xlarge' id='loginname' name='loginname' size='30' type='text' value=''/>\n            <% } %>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='pass'>密码</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='pass' name='pass' size='30' type='password'/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='re_pass'>确认密码</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='re_pass' name='re_pass' size='30' type='password'/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='email'>电子邮箱</label>\n\n          <div class='controls'>\n            <% if (typeof(email) !== 'undefined') { %>\n            <input class='input-xlarge' id='email' name='email' size='30' type='text' value='<%= email %>'/>\n            <% } else { %>\n            <input class='input-xlarge' id='email' name='email' size='30' type='text'/>\n            <% } %>\n          </div>\n        </div>\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n\n        <div class='form-actions'>\n          <input type='submit' class='span-primary' value='注册'/>\n          <a href=\"/auth/github\">\n            <span class=\"span-info\">\n              通过 GitHub 登录\n            </span>\n          </a>\n        </div>\n      </form>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/static/about.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>关于</li>\n      </ul>\n    </div>\n    <div class='inner topic'>\n      <div class=\"topic_content\">\n        <%- markdown(multiline(function () {\n/*\n### 关于\nCNode 社区为国内最大最具影响力的 Node.js 开源技术社区，致力于 Node.js 的技术研究。\n\nCNode 社区由一批热爱 Node.js 技术的工程师发起，目前已经吸引了互联网各个公司的专业技术人员加入，我们非常欢迎更多对 Node.js 感兴趣的朋友。\n\nCNode 的 SLA 保证是，一个9，即 90.000000%。\n\n社区目前由 [@alsotang](http://cnodejs.org/user/alsotang) 在维护，有问题请联系：[https://github.com/alsotang](https://github.com/alsotang)\n\n请关注我们的官方微博：http://weibo.com/cnodejs\n\n### 移动客户端\n\n客户端由 [@soliury](https://cnodejs.org/user/soliury) 开发维护。\n\n源码地址： https://github.com/soliury/noder-react-native 。\n\n立即体验 CNode 客户端，直接扫描页面右侧二维码。\n\n另，安卓用户同时可选择：https://github.com/TakWolf/CNode-Material-Design ，这是 Java 原生开发的安卓客户端。\n\n*/\n        })) %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/static/api.html",
    "content": "<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>API</li>\n      </ul>\n    </div>\n    <div class='inner topic'>\n      <div class=\"topic_content\">\n        <%- markdown(multiline(function () {\n/*\n\n以下 api 路径均以 **https://cnodejs.org/api/v1** 为前缀\n\n### 主题\n\n#### get /topics 主题首页\n\n接收 get 参数\n\n* page `Number` 页数\n* tab `String` 主题分类。目前有 `ask` `share` `job` `good`\n* limit `Number` 每一页的主题数量\n* mdrender `String` 当为 `false` 时，不渲染。默认为 `true`，渲染出现的所有 markdown 格式文本。\n\n示例：[/api/v1/topics](/api/v1/topics)\n\n#### get /topic/:id 主题详情\n\n接收 get 参数\n\n* mdrender `String` 当为 `false` 时，不渲染。默认为 `true`，渲染出现的所有 markdown 格式文本。\n* accesstoken `String` 当需要知道一个主题是否被特定用户收藏以及对应评论是否被特定用户点赞时，才需要带此参数。会影响返回值中的 `is_collect` 以及 `replies` 列表中的 `is_uped` 值。\n\n示例：[/api/v1/topic/5433d5e4e737cbe96dcef312](/api/v1/topic/5433d5e4e737cbe96dcef312)\n\n#### post /topics 新建主题\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n* title `String` 标题\n* tab `String` 目前有 `ask` `share` `job` `dev`。开发新客户端的同学，请务必将你们的测试帖发在 `dev` 专区，以免污染日常的版面，否则会进行封号一周处理。\n* content `String` 主体内容\n\n返回值示例\n\n```js\n{success: true, topic_id: '5433d5e4e737cbe96dcef312'}\n```\n\n#### post /topics/update 编辑主题\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n* topic_id `String` 主题id\n* title `String` 标题\n* tab `String` 目前有 `ask` `share` `job`\n* content `String` 主体内容\n\n返回值示例\n\n```js\n{success: true, topic_id: '5433d5e4e737cbe96dcef312'}\n```\n\n\n### 主题收藏\n\n#### post /topic_collect/collect 收藏主题\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n* topic_id `String` 主题的id\n\n返回值示例\n\n```js\n{\"success\": true}\n```\n\n#### post /topic_collect/de_collect 取消主题\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n* topic_id `String` 主题的id\n\n返回值示例\n\n```js\n{success: true}\n```\n#### get /topic_collect/:loginname 用户所收藏的主题\n\n示例：[/api/v1/topic_collect/alsotang](/api/v1/topic_collect/alsotang)\n\n\n### 评论\n\n#### post /topic/:topic_id/replies 新建评论\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n* content `String` 评论的主体\n* reply_id `String` 如果这个评论是对另一个评论的回复，请务必带上此字段。这样前端就可以构建出评论线索图。\n\n返回值示例\n\n```js\n{success: true, reply_id: '5433d5e4e737cbe96dcef312'}\n```\n\n#### post /reply/:reply_id/ups 为评论点赞\n\n接受 post 参数\n\n* accesstoken `String`\n\n接口会自动判断用户是否已点赞，如果否，则点赞；如果是，则取消点赞。点赞的动作反应在返回数据的 `action` 字段中，`up` or `down`。\n\n返回值示例\n\n```js\n{\"success\": true, \"action\": \"down\"}\n```\n\n### 用户\n\n#### get /user/:loginname 用户详情\n\n示例：[/api/v1/user/alsotang](/api/v1/user/alsotang)\n\n#### post /accesstoken 验证 accessToken 的正确性\n\n接收 post 参数\n\n* accesstoken `String` 用户的 accessToken\n\n如果成功匹配上用户，返回成功信息。否则 403。\n\n返回值示例\n\n```js\n{success: true, loginname: req.user.loginname, id: req.user.id, avatar_url: req.user.avatar_url}\n```\n\n### 消息通知\n\n#### get /message/count 获取未读消息数\n\n接收 get 参数\n\n* accesstoken `String`\n\n返回值示例\n\n```js\n{ data: 3 }\n```\n\n\n#### get /messages 获取已读和未读消息\n\n接收 get 参数\n\n* accesstoken `String`\n* mdrender `String` 当为 `false` 时，不渲染。默认为 `true`，渲染出现的所有 markdown 格式文本。\n\n返回值示例\n\n```js\n{\n  data: {\n    has_read_messages: [],\n    hasnot_read_messages: [\n      {\n        id: \"543fb7abae523bbc80412b26\",\n        type: \"at\",\n        has_read: false,\n        author: {\n          loginname: \"alsotang\",\n          avatar_url: \"https://avatars.githubusercontent.com/u/1147375?v=2\"\n        },\n        topic: {\n          id: \"542d6ecb9ecb3db94b2b3d0f\",\n          title: \"adfadfadfasdf\",\n          last_reply_at: \"2014-10-18T07:47:22.563Z\"\n        },\n        reply: {\n          id: \"543fb7abae523bbc80412b24\",\n          content: \"[@alsotang](/user/alsotang) 哈哈\",\n          ups: [ ],\n          create_at: \"2014-10-16T12:18:51.566Z\"\n          }\n        },\n    ...\n    ]\n  }\n}\n```\n\n#### post /message/mark_all 标记全部已读\n\n接收 post 参数\n\n* accesstoken `String`\n\n返回值示例\n\n```js\n{ success: true,\n  marked_msgs: [ { id: '544ce385aeaeb5931556c6f9' } ] }\n```\n\n\n#### post /message/mark_one/:msg_id 标记单个消息为已读\n\n请求示例：[/message/mark_one/58ec7d39da8344a81eee0c14](/message/mark_one/58ec7d39da8344a81eee0c14)\n\n接收 post 参数\n\n* accesstoken `String`\n\n返回值示例\n\n```js\n{\n  success: true,\n  marked_msg_id: \"58ec7d39da8344a81eee0c14\"\n}\n```\n\n\n\n### 知识点\n\n1. 如何获取 accessToken？\n    用户登录后，在设置页面可以看到自己的 accessToken。\n    建议各移动端应用使用手机扫码的形式登录，验证使用 `/accesstoken` 接口，登录后长期保存 accessToken。\n\n*/\n        })) %>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "views/static/faq.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>FAQ</li>\n      </ul>\n    </div>\n    <div class='inner topic'>\n      <h3>FAQ</h3>\n      <hr/>\n      <div class=\"topic_content\">\n        <p>A：CNode 社区和 Node Club 是什么关系？</p>\n\n        <p>Q：Node Club 是一个用 Node.js 和 MongoDB 开发的开源社区软件，CNode 是基于 Node Club 的 Node.js 中文技术社区。</p>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/static/getstart.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>Node.js 新手入门</li>\n      </ul>\n    </div>\n    <div class='inner topic'>\n      <div class=\"topic_content\">\n        <%- markdown(multiline(function () {\n/*\n\n## Node.js 入门\n\n《**汇智网 Node.js 课程**》\n\nhttp://www.hubwiz.com/course/?type=nodes\n\n《**快速搭建 Node.js 开发环境以及加速 npm**》\n\nhttp://fengmk2.com/blog/2014/03/node-env-and-faster-npm.html\n\n《**Node.js 包教不包会**》\n\nhttps://github.com/alsotang/node-lessons\n\n《**ECMAScript 6入门**》\n\nhttp://es6.ruanyifeng.com/\n\n《**七天学会NodeJS**》\n\nhttps://github.com/nqdeng/7-days-nodejs\n\n《**Node入门-_一本全面的Node.js教程_**》\n\nhttp://www.nodebeginner.org/index-zh-cn.html\n\n## Node.js 资源\n\n《**node weekly**》\n\nhttp://nodeweekly.com/issues\n\n《**node123-_node.js中文资料导航_**》\n\nhttps://github.com/youyudehexie/node123\n\n《**A curated list of delightful Node.js packages and resources**》\n\nhttps://github.com/sindresorhus/awesome-nodejs\n\n《**Node.js Books**》\n\nhttps://github.com/pana/node-books\n\n## Node.js 名人\n\n《**名人堂**》\n\nhttps://github.com/cnodejs/nodeclub/wiki/%E5%90%8D%E4%BA%BA%E5%A0%82\n\n## Node.js 服务器\n\n新手搭建 Node.js 服务器，推荐使用无需备案的 [DigitalOcean(https://www.digitalocean.com/)](https://www.digitalocean.com/?refcode=eba02656eeb3)\n\n*/\n        })) %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/topic/_top_good.html",
    "content": "<% if (topic.top) { %>\n<span class='put_top'>置顶</span>\n<% } else if (topic.good) { %>\n<span class='put_good'>精华</span>\n<% } else if (typeof(tab) !== 'undefined' &&  tab === 'all' && topic.tabName) { %>\n<span class=\"topiclist-tab\"><%= topic.tabName %></span>\n<% } %>\n"
  },
  {
    "path": "views/topic/abstract.html",
    "content": "<div class='cell'>\n\n  <a class=\"user_avatar pull-left\" href=\"/user/<%= topic.author.loginname %>\">\n    <img src=\"<%= proxy(topic.author.avatar_url) %>\"\n         title=\"<%= topic.author.loginname %>\"\n            />\n  </a>\n\n  <span class=\"reply_count pull-left\">\n    <span class=\"count_of_replies\" title=\"回复数\">\n      <%= topic.reply_count %>\n    </span>\n    <span class=\"count_seperator\">/</span>\n    <span class=\"count_of_visits\" title='点击数'>\n      <%= topic.visit_count %>\n    </span>\n  </span>\n\n  <% if (topic.reply && topic.reply.author) {%>\n  <a class='last_time pull-right' href=\"/topic/<%= topic._id %><%- '#' + topic.reply._id %>\">\n    <img class=\"user_small_avatar\" src=\"<%= proxy(topic.reply.author.avatar_url) %>\">\n    <span class=\"last_active_time\"><%= topic.reply.create_at_ago() %></span>\n  </a>\n  <% } %>\n  <% if (!topic.reply) {%>\n    <span class='last_time pull-right'>\n      <span class=\"last_active_time\"><%= topic.create_at_ago() %></span>\n    </span>\n  <% } %>\n\n  <div class=\"topic_title_wrapper\">\n\n    <%- partial('./_top_good', {topic: topic}) %>\n\n    <a class='topic_title' href='/topic/<%= topic._id %>' title='<%= topic.title %>'>\n      <%= topic.title %>\n    </a>\n  </div>\n</div>\n"
  },
  {
    "path": "views/topic/edit.html",
    "content": "<%- partial('../editor_sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ol class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <% if(typeof(action) !== 'undefined' && action == 'edit'){ %>\n        <li class='active'>编辑话题</li>\n        <% }else{ %>\n        <li class='active'>发布话题</li>\n        <% } %>\n      </ol>\n    </div>\n    <div class='inner post'>\n      <% if(typeof(edit_error) !== 'undefined' && edit_error){ %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= edit_error %></strong>\n      </div>\n      <% } %>\n      <% if(typeof(error) !== 'undefined' && error){ %>\n      <div class=\"alert alert-error\">\n        <strong><%= error %></strong>\n      </div>\n      <% }else{ %>\n      <% if (typeof(action) !== 'undefined' && action === 'edit') { %>\n      <form id='create_topic_form' action='/topic/<%= topic_id %>/edit' method='post'>\n        <% } else { %>\n        <form id='create_topic_form' action='/topic/create' method='post'>\n          <% } %>\n          <fieldset>\n            <span class=\"tab-selector\">选择版块：</span>\n            <select name=\"tab\" id=\"tab-value\">\n              <option value=\"\">请选择</option>\n              <%\n              var tabValue = '';\n              if (typeof(tab) !== 'undefined') {\n                tabValue = tab;\n              }\n              tabs.forEach(function (pair) {\n                var value = pair[0];\n                var text = pair[1];\n                %>\n                <option value=\"<%=value%>\" <%= tabValue === value ? 'selected': '' %>><%=text%></option>\n\n              <%});%>\n            </select>\n            <span id=\"topic_create_warn\"></span>\n            <textarea autofocus class='span9' id='title' name='title' rows='1'\n                      placeholder=\"标题字数 10 字以上\"\n                    ><%= typeof(title) !== 'undefined' && title || '' %></textarea>\n\n            <div class='markdown_editor in_editor'>\n              <div class='markdown_in_editor'>\n                <textarea class='editor' name='t_content' rows='20'\n                          placeholder='文章支持 Markdown 语法, 请注意标记代码'\n                        ><%= typeof(content) !== 'undefined' && content || '' %></textarea>\n\n                <div class='editor_buttons'>\n                  <input type=\"submit\" class='span-primary submit_btn' data-loading-text=\"提交中\"\n                         value=\"提交\">\n                </div>\n              </div>\n\n            </div>\n\n            <input type='hidden' id='topic_tags' name='topic_tags' value=''>\n            <input type='hidden' name='_csrf' value='<%= csrf %>'>\n          </fieldset>\n        </form>\n    </div>\n    <% } %>\n  </div>\n</div>\n\n<!-- markdown editor -->\n<%- partial('../includes/editor') %>\n<script>\n  (function () {\n    var editor = new Editor();\n    editor.render($('.editor')[0]);\n\n    // 版块选择的检查，必须选择\n    $('#create_topic_form').on('submit', function (e) {\n      var tabValue = $('#tab-value').val();\n      if (!tabValue) {\n        alert('必须选择一个版块！');\n        $('.submit_btn').button('reset');\n        $('.tab-selector').css('color', 'red');\n        return false;\n      }\n    });\n    // END 版块选择的检查，必须选择\n\n    // 选择招聘版块时，给出提示\n    $('#tab-value').on('change', function () {\n      var $this = $(this);\n      var value = $this.val();\n      var warnMsg = '';\n      if (value === 'job') {\n        warnMsg = '<strong>为避免被管理员删帖，发帖时请好好阅读<a href=\"http://cnodejs.org/topic/541ed2d05e28155f24676a12\" target=\"_blank\">《招聘帖规范》</a></strong>';\n      } else if (value === 'ask') {\n        warnMsg = '<strong>提问时，请遵循 <a href=\"https://gist.github.com/alsotang/f654af8b1fff220e63fcb44846423e6d\" target=\"_blank\">《提问的智慧》</a>中提及的要点，以便您更接收到高质量回复。</strong>'\n      }\n      $('#topic_create_warn').html(warnMsg);\n    });\n    // END 选择招聘版块时，给出提示\n  })();\n</script>\n"
  },
  {
    "path": "views/topic/index.html",
    "content": "<div id='sidebar'>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>作者</span>\n    </div>\n    <div class='inner'>\n      <%- partial('../user/card', { object: topic.author, as: 'user' }) %>\n    </div>\n  </div>\n\n  <% if (!current_user || !current_user.isAdvanced) { %>\n    <%- partial('../_ads') %>\n  <% } %>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>作者其它话题</span>\n    </div>\n    <div class='inner'>\n      <% if (typeof(author_other_topics) === 'undefined' || author_other_topics.length > 0) { %>\n      <ul class='unstyled'>\n        <%- partial('../topic/small', { collection: author_other_topics, as: 'topic' }) %>\n      </ul>\n      <% } else { %>\n      <p>无</p>\n      <% } %>\n    </div>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>无人回复的话题</span>\n    </div>\n    <div class='inner'>\n      <% if (typeof(no_reply_topics) !== 'undefined' && no_reply_topics.length > 0) { %>\n      <ul class='unstyled'>\n        <%- partial('../topic/small', { collection: no_reply_topics, as: 'topic' }) %>\n      </ul>\n      <% } else { %>\n      <p>无</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header topic_header'>\n      <span class=\"topic_full_title\">\n\n        <%- partial('./_top_good', {topic: topic}) %>\n\n        <%= topic.title %>\n      </span>\n      <div class=\"changes\">\n        <span>\n          发布于 <%= topic.create_at_ago() %>\n        </span>\n        <span>\n          作者 <a href=\"/user/<%= topic.author.loginname %>\"><%= topic.author.loginname %></a>\n        </span>\n        <span>\n          <%= topic.visit_count %> 次浏览\n        </span>\n        <% if (topic.create_at_ago() != topic.update_at_ago()) { %>\n          <span>\n            最后一次编辑是 <%= topic.update_at_ago() %>\n          </span>\n        <% } %>\n        <% if (topic.tab) { %>\n          <span> 来自 <%= topic.tabName %></span>\n        <%}%>\n\n        <% if (current_user) { %>\n          <input class=\"span-common <%= is_collect ? '' : 'span-success' %> pull-right collect_btn\" type=\"submit\" value=\"<%= is_collect ? '取消收藏' : '收藏' %>\" action=\"<%= is_collect ? 'de_collect' : 'collect' %>\">\n        <%}%>\n\n      </div>\n      <% if (current_user) { %>\n      <div id=\"manage_topic\">\n        <% if (current_user.is_admin) { %>\n          <a href='/topic/<%= topic._id %>/top' data-method=\"post\">\n            <% if (topic.top) { %>\n              <i class=\"fa fa-lg fa-star-o\" title='取消置顶'></i>\n            <% } else { %>\n              <i class=\"fa fa-lg fa-star\" title='置顶'/></i>\n            <% } %>\n          </a>\n\n\n          <a href='/topic/<%= topic._id %>/good' data-method=\"post\">\n            <% if (topic.good) { %>\n              <i class=\"fa fa-lg fa-heart-o\" title=\"取消精华\"></i>\n            <% } else { %>\n              <i class=\"fa fa-lg fa-heart\" title=\"加精华\"></i>\n            <% } %>\n          </a>\n\n          <a href='/topic/<%= topic._id %>/lock' data-method=\"post\">\n            <% if (topic.lock) { %>\n              <i class=\"fa fa-lg fa-unlock\" title='取消锁定'></i>\n            <% } else { %>\n              <i class=\"fa fa-lg fa-lock\" title='锁定（不可再回复）'/></i>\n            <% } %>\n          </a>\n\n\n          <a href='/topic/<%= topic._id %>/edit'>\n            <i class=\"fa fa-lg fa-pencil-square-o\" title='编辑'></i></a>\n          <a href='javascript:;'\n             data-id=\"<%= topic._id %>\"\n             class='delete_topic_btn'>\n             <i class=\"fa fa-lg fa-trash\" title='删除'></i></a>\n          <% } else { %>\n          <% if (current_user._id.equals(topic.author_id)) { %>\n          <a href='/topic/<%= topic._id %>/edit'>\n            <i class=\"fa fa-lg fa-pencil-square-o\" title='编辑'></i></a>\n          <a href='javascript:;'\n             data-id=\"<%= topic._id %>\"\n             class='delete_topic_btn'>\n             <i class=\"fa fa-lg fa-trash\" title='删除'></i></a>\n          <% } %>\n        <% } %>\n\n\n      </div>\n      <% } %>\n    </div>\n    <div class='inner topic'>\n\n      <div class='topic_content'>\n        <%- markdown(topic.linkedContent) %>\n      </div>\n    </div>\n  </div>\n  <% if (topic.replies && topic.replies.length > 0) { %>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'><%= topic.replies.length %> 回复</span>\n    </div>\n    <%- partial('../reply/reply', topic.replies) %>\n  </div>\n  <% } %>\n  <% if (current_user && typeof(topic) !== 'undefined') { %>\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>添加回复</span>\n    </div>\n    <div class='inner reply'>\n      <form id='reply_form' action='/<%= topic._id %>/reply' method='post'>\n\n        <div class='markdown_editor in_editor'>\n          <div class='markdown_in_editor'>\n            <textarea class='editor' name='r_content' rows='8'></textarea>\n\n            <div class='editor_buttons'>\n              <input class='span-primary submit_btn' type=\"submit\" data-loading-text=\"回复中..\" value=\"回复<%= topic.lock ? '(此主题已锁定)' : ''%>\" <%= topic.lock ? 'disabled=\"disabled\"' : ''%>>\n            </div>\n          </div>\n\n        </div>\n\n        <input type='hidden' name='_csrf' id=\"_csrf\" value='<%= csrf %>'/>\n      </form>\n    </div>\n  </div>\n  <% } %>\n</div>\n\n<div class=\"replies_history\">\n  <div class=\"inner_content\"></div>\n  <div class=\"anchor\"></div>\n</div>\n\n<!-- 预览模态对话框 -->\n<div class=\"modal fade\" id=\"preview-modal\">\n  <div class=\"modal-body\" style=\"max-height: initial;\">\n    <img src=\"\" alt=\"点击内容或者外部自动关闭图片预览\" id=\"preview-image\">\n  </div>\n</div>\n\n\n<% if (current_user && typeof(topic) !== 'undefined') { %>\n<!-- markdown editor -->\n<%- partial('../includes/editor') %>\n<script>\n  $(document).ready(function () {\n    // 获取所有回复者name\n    var allNames = $('.reply_author').map(function (idx, ele) {\n      return $(ele).text().trim();\n    }).toArray();\n    allNames.push($('.user_card .user_name').text())\n    allNames = _.uniq(allNames);\n    // END 获取所有回复者name\n\n    // 编辑器相关\n    $('textarea.editor').each(function(){\n      var editor = new Editor({\n        status: []\n      });\n      var $el = $(this);\n\n      editor.render(this);\n      //绑定editor\n      $(this).data('editor', editor);\n\n      var $input = $(editor.codemirror.display.input);\n      $input.keydown(function(event){\n        if (event.keyCode === 13 && (event.ctrlKey || event.metaKey)) {\n          event.preventDefault();\n          $el.closest('form').submit();\n        }\n      });\n\n      // at.js 配置\n      var codeMirrorGoLineUp = CodeMirror.commands.goLineUp;\n      var codeMirrorGoLineDown = CodeMirror.commands.goLineDown;\n      var codeMirrorNewlineAndIndent = CodeMirror.commands.newlineAndIndent;\n      $input.atwho({\n        at: '@',\n        data: allNames\n      })\n      .on('shown.atwho', function () {\n        CodeMirror.commands.goLineUp = _.noop;\n        CodeMirror.commands.goLineDown = _.noop;\n        CodeMirror.commands.newlineAndIndent = _.noop;\n      })\n      .on('hidden.atwho', function () {\n        CodeMirror.commands.goLineUp = codeMirrorGoLineUp;\n        CodeMirror.commands.goLineDown = codeMirrorGoLineDown;\n        CodeMirror.commands.newlineAndIndent = codeMirrorNewlineAndIndent;\n      });\n      // END at.js 配置\n\n    });\n    // END 编辑器相关\n\n    // 评论回复\n    $('#content').on('click', '.reply2_btn', function (event) {\n      var $btn = $(event.currentTarget);\n      var parent = $btn.closest('.reply_area');\n      var editorWrap = parent.find('.reply2_form');\n      parent.find('.reply2_area').prepend(editorWrap);\n      var textarea = editorWrap.find('textarea.editor');\n      var user = $btn.closest('.author_content').find('.reply_author').text().trim();\n      var editor = textarea.data('editor');\n      editorWrap.show('fast', function () {\n        var cm = editor.codemirror;\n        cm.focus();\n        if(cm.getValue().indexOf('@' + user) < 0){\n          editor.push('@' + user + ' ');\n        }\n      });\n    });\n\n    $('#content').on('click', '.reply2_at_btn', function (event) {\n      var $btn = $(event.currentTarget);\n      var editorWrap = $btn.closest('.reply2_area').find('.reply2_form');\n      $btn.closest('.reply2_item').after(editorWrap);\n      var textarea = editorWrap.find('textarea.editor');\n      var user = $btn.closest('.reply2_item').find('.reply_author').text().trim();\n      var editor = textarea.data('editor');\n      editorWrap.show('fast', function () {\n        var cm = editor.codemirror;\n        cm.focus();\n        if(cm.getValue().indexOf('@' + user) < 0){\n          editor.push('@' + user + ' ');\n        }\n      });\n    });\n    // END 评论回复\n\n    // 加入收藏\n    $('.collect_btn').click(function () {\n      var $me = $(this);\n      var action = $me.attr('action');\n      var data = {\n        topic_id: '<%= topic._id %>',\n        _csrf: '<%= csrf %>'\n      };\n      var $countSpan = $('.collect-topic-count');\n      $.post('/topic/' + action, data, function (data) {\n        if (data.status === 'success') {\n          if (action == 'collect') {\n            $me.val('取消收藏');\n            $me.attr('action', 'de_collect');\n          } else {\n            $me.val('收藏');\n            $me.attr('action', 'collect');\n          }\n          $me.toggleClass('span-success');\n        }\n      }, 'json');\n    });\n    // END 加入收藏\n\n    // 删除回复\n    $('#content').on('click', '.delete_reply_btn, .delete_reply2_btn', function (event) {\n      var $me = $(event.currentTarget);\n      if (confirm('确定要删除此回复吗？')) {\n        var reply_id = null;\n        if ($me.hasClass('delete_reply_btn')) {\n          reply_id = $me.closest('.reply_item').attr('reply_id');\n        }\n        if ($me.hasClass('delete_reply2_btn')) {\n          reply_id = $me.closest('.reply2_item').attr('reply_id');\n        }\n        var data = {\n          reply_id: reply_id,\n          _csrf: \"<%- csrf %>\"\n        };\n        $.post('/reply/' + reply_id + '/delete', data, function (data) {\n          if (data.status === 'success') {\n            if ($me.hasClass('delete_reply_btn')) {\n              $me.closest('.reply_item').remove();\n            }\n            if ($me.hasClass('delete_reply2_btn')) {\n              $me.closest('.reply2_item').remove();\n            }\n          }\n        }, 'json');\n      }\n      return false;\n    });\n    // END 删除回复\n\n    // 删除话题\n    $('.delete_topic_btn').click(function () {\n      var topicId = $(this).data('id');\n      if (topicId && confirm('确定要删除此话题吗？')) {\n        $.post('/topic/' + topicId + '/delete', { _csrf: $('#_csrf').val() }, function (result) {\n          if (!result.success) {\n            alert(result.message);\n          } else {\n            location.href = '/';\n          }\n        });\n      }\n      return false;\n    });\n    // END 删除话题\n\n    // 用户 hover 在回复框时才显示点赞按钮\n    $('.reply_area').hover(\n      function () {\n        $(this).find('.up_btn').removeClass('invisible');\n      },\n      function () {\n        var $this = $(this);\n        if ($this.find('.up-count').text().trim() === '') {\n          $this.find('.up_btn').addClass('invisible');\n        }\n      });\n    // END 用户 hover 在回复框时才显示点赞按钮\n\n\n  });\n\n</script>\n<% } %>\n\n<script type=\"text/javascript\">\n  (function(){\n    var timer = null; //对话框延时定时器\n    // 初始化 $('.replies_history')\n    var $repliesHistory = $('.replies_history');\n    var $repliesHistoryContent = $repliesHistory.find('.inner_content');\n    $repliesHistory.hide();\n    // END\n    // 鼠标移入对话框清除隐藏定时器；移出时隐藏对话框\n    $repliesHistory.on('mouseenter', function(){\n      clearTimeout(timer);\n    }).on('mouseleave', function(){\n      $repliesHistory.fadeOut('fast');\n    });\n    // 显示被 at 用户的本页评论\n    if ($('.reply2_item').length === 0) {\n      // 只在流式评论布局中使用\n\n      $('#content').on('mouseenter', '.reply_content a', function (e) {\n        clearTimeout(timer);\n        var $this = $(this);\n        if ($this.text()[0] === '@') {\n          var thisText = $this.text().trim();\n          var loginname = thisText.slice(1);\n          var offset = $this.offset();\n          var width = $this.width();\n          var mainOffset = $('#main').offset();\n          $repliesHistory.css('left', offset.left-mainOffset.left+width+10); // magic number\n          $repliesHistory.css('top', offset.top-mainOffset.top-10); // magic number\n          $repliesHistory.css({\n            'z-index': 1,\n          });\n          $repliesHistoryContent.empty();\n          var chats = [];\n          var replyToId = $this.closest('.reply_item').attr('reply_to_id');\n          while (replyToId) {\n            var $replyItem = $('.reply_item[reply_id=' + replyToId + ']');\n            var replyContent = $replyItem.find('.reply_content').text().trim();\n            if (replyContent.length > 0) {\n              chats.push([\n                $($replyItem.find('.user_avatar').html()).attr({\n                  height: '30px',\n                  width: '30px',\n                }), // avatar\n                (replyContent.length>300?replyContent.substr(0,300)+'...':replyContent), // reply content\n                '<a href=\"#'+replyToId+'\" class=\"scroll_to_original\" title=\"查看原文\">↑</a>'\n              ]);\n            }\n            replyToId = $replyItem.attr('reply_to_id');\n          }\n          if(chats.length > 0) {\n            chats.reverse();\n\n            $repliesHistoryContent.append('<div class=\"title\">查看对话</div>');\n            chats.forEach(function (pair, idx) {\n              var $chat = $repliesHistoryContent.append('<div class=\"item\"></div>');\n              $chat.append(pair[0]); // 头像\n              $chat.append($('<span>').text(pair[1])); // 内容\n              $chat.append(pair[2]); // 查看原文 anchor\n            });\n            $repliesHistory.fadeIn('fast');\n          }else{\n            $repliesHistory.hide();\n          }\n        }\n      }).on('mouseleave', '.reply_content a', function (e) {\n        timer = setTimeout(function(){\n          $repliesHistory.fadeOut('fast');\n        }, 500);\n      });\n    }\n    // END 显示被 at 用户的本页评论\n  })();\n\n  // 点赞\n  $('.up_btn').click(function (e) {\n    var $this = $(this);\n    var replyId = $this.closest('.reply_area').attr('reply_id');\n    $.ajax({\n      url: '/reply/' + replyId + '/up',\n      method: 'POST',\n    }).done(function (data) {\n      if (data.success) {\n        $this.removeClass('invisible');\n        var currentCount = Number($this.next('.up-count').text().trim()) || 0;\n        if (data.action === 'up') {\n          $this.next('.up-count').text(currentCount + 1);\n          $this.addClass('uped');\n        } else {\n          if (data.action === 'down') {\n            $this.next('.up-count').text(currentCount - 1);\n            $this.removeClass('uped');\n          }\n        }\n      } else {\n        alert(data.message);\n      }\n    }).fail(function (xhr) {\n      if (xhr.status === 403) {\n        alert('请先登录，登陆后即可点赞。');\n      }\n    });\n  });\n  // END 点赞\n  // 图片预览\n  (function(){\n    var $previewModal = $('#preview-modal');\n    var $previewImage = $('#preview-image');\n    var $body = $('body'); // cache\n\n    $(document).on('click', '.markdown-text img', function(e) {\n      var $img = $(this);\n      // 图片被a标签包裹时，不显示弹层\n      if ($img.parent('a').length > 0) {\n        return;\n      }\n      showModal($img.attr('src'));\n    });\n\n    $previewModal.on('click', hideModal);\n\n    $previewModal.on('hidden.bs.modal', function() {\n      // 在预览框消失之后恢复 body 的滚动能力\n      $body.css('overflow-y', 'scroll');\n    })\n\n    $previewModal.on('shown.bs.modal', function() {\n      // 修复上次滚动留下的痕迹,可能会导致短暂的闪烁，不过可以接受\n      // TODO: to be promote\n      $previewModal.scrollTop(0);\n    })\n\n    function showModal(src) {\n      $previewImage.attr('src', src);\n      $previewModal.modal('show');\n      // 禁止 body 滚动\n      $body.css('overflow-y', 'hidden');\n    }\n\n    function hideModal() {\n      $previewModal.modal('hide');\n    }\n\n  })()\n  // END 图片预览\n</script>\n"
  },
  {
    "path": "views/topic/list.html",
    "content": "<div id=\"topic_list\">\n  <%- partial('../topic/abstract', {collection:topics, as:'topic'}) %>\n</div>\n<div class='pagination' current_page='<%= current_page %>'>\n  <ul>\n    <% var base_url = base + (base.indexOf('?') < 0 ? '?' : '&')\n      + 'tab=' + (typeof tab !== 'undefined' ? tab : '') + '&page='; %>\n    <% if (current_page == 1) { %>\n    <li class='disabled'><a>«</a></li>\n    <% } else { %>\n    <li><a href=\"<%= base_url %>1\">«</a></li>\n    <% } %>\n\n    <%\n    var page_start = current_page - 2 > 0 ? current_page - 2 : 1;\n    var page_end = page_start + 4 >= pages ? pages : page_start + 4;\n    %>\n\n    <% if (page_start > 1) { %>\n    <li><a>...</a></li>\n    <% } %>\n\n    <% for(var i = page_start; i <= page_end; i++) { %>\n    <% if (i === current_page) { %>\n    <li class='disabled'><a><%= i %></a></li>\n    <% } else { %>\n    <li><a href='<%= base_url + i %>'><%= i %></a></li>\n    <% } %>\n    <% } %>\n\n    <% if (page_end < pages ) { %>\n    <li><a>...</a></li>\n    <% } %>\n\n    <% if (current_page == pages) { %>\n    <li class='disabled'><a>»</a></li>\n    <% } else { %>\n    <li><a href='<%= base_url + pages %>'>»</a></li>\n    <% } %>\n  </ul>\n</div>\n<script>\n  $(document).ready(function () {\n    var $nav = $('.pagination');\n    var current_page = $nav.attr('current_page');\n    if (current_page) {\n      $nav.find('li').each(function () {\n        var $li = $(this);\n        var $a = $li.find('a');\n        if ($a.html() == current_page) {\n          $li.addClass('active');\n          $a.removeAttr('href');\n        }\n      });\n    }\n  });\n</script>\n"
  },
  {
    "path": "views/topic/small.html",
    "content": "<li>\n  <div><a class='dark topic_title' href=\"/topic/<%= topic._id %>\" title=\"<%= topic.title %>\"><%= topic.title %></a>\n  </div>\n</li>\n"
  },
  {
    "path": "views/user/card.html",
    "content": "<div class='user_card'>\n  <div>\n    <a class='user_avatar' href=\"/user/<%= user.loginname %>\">\n      <img src=\"<%= proxy(user.avatar_url) %>\" title=\"<%= user.loginname %>\"/>\n    </a>\n    <span class='user_name'><a class='dark' href=\"/user/<%= user.loginname %>\"><%= user.loginname %></a></span>\n\n    <div class='board clearfix'>\n      <div class='floor'>\n        <span class='big'>积分: <%= user.score %> </span>\n      </div>\n    </div>\n    <div class=\"space clearfix\"></div>\n    <span class=\"signature\">\n        “\n        <% if (user.signature) {%>\n            <%-escapeSignature(user.signature)%>\n        <%} else {%>\n            这家伙很懒，什么个性签名都没有留下。\n        <%}%>\n        ”\n    </span>\n  </div>\n</div>\n\n<% if (current_user) { %>\n<script>\n  $(document).ready(function () {\n    $('.follow_btn').click(function () {\n      var $me = $(this);\n      var action = $me.attr('action');\n      var params = {\n        follow_id: '<%= user._id %>',\n        _csrf: '<%- csrf %>'\n      };\n      $.post('/user/' + action, params, function (data) {\n        if (data.status === 'success') {\n          var $btns = $('.follow_btn');\n          if (action === 'follow') {\n            $btns.html('取消关注');\n            $btns.attr('action', 'un_follow');\n          } else {\n            $btns.html('加入关注');\n            $btns.attr('action', 'follow');\n          }\n          $btns.toggleClass('btn-success');\n        }\n      }, 'json');\n    });\n  });\n</script>\n<% } %>\n"
  },
  {
    "path": "views/user/collect_topics.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'><%= user.loginname %> 收藏的话题</li>\n      </ul>\n    </div>\n    <div class='inner no-padding'>\n      <% if (topics.length > 0) { %>\n      <%- partial('../topic/list', { topics: topics, pages: pages, current_pages: current_page, base: '/user/' +\n      user.loginname + '/collections' }) %>\n      <% } else { %>\n      <p>找不到话题 (T_T)</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/user/followers.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>关注 <%= user.loginname %> 的人</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if (users.length > 0) { %>\n      <%- partial('../user/user', { collection: users, as: 'user' }) %>\n      <% } else { %>\n      <p>还没有任何人关注他</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/user/followings.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'><%= user.loginname %> 关注的人</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if (users.length > 0) { %>\n      <%- partial('../user/user', { collection: users, as: 'user' }) %>\n      <% } else { %>\n      <p>没有关注任何人</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/user/index.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n      </ul>\n    </div>\n    <div class='inner userinfo'>\n      <div class='user_big_avatar'>\n        <img src=\"<%= proxy(user.avatar_url) %>\" class=\"user_avatar\" title=\"<%= user.loginname %>\"/>\n      </div>\n      <a class='dark'><%= user.loginname %></a>\n\n      <div class='user_profile'>\n        <ul class='unstyled'>\n          <span class='big'><%= user.score %></span> 积分\n          <% if (user.collect_topic_count) {%>\n          <li>\n            <a class='dark' href=\"/user/<%= user.loginname %>/collections\" target='_blank'>\n              <span class='big collect-topic-count'><%= user.collect_topic_count %></span>个话题收藏\n            </a>\n          </li>\n          <%}%>\n          <% if (user.url) { %>\n          <li>\n            <i class=\"fa fa-lg fa-fw fa-home\"></i>\n            <a class='dark' href=\"<%= user.url %>\" target='_blank'><%= user.url %></a>\n          </li>\n          <% } %>\n          <% if (user.location) { %>\n          <li>\n            <i class=\"fa fa-lg fa-fw fa-map-marker\"></i>\n            <span class='dark'><%= user.location %></span>\n          </li>\n          <% } %>\n          <% if (user.githubUsername) { %>\n          <li>\n            <i class=\"fa fa-lg fa-fw fa-github\"></i>\n            <a class='dark' href=\"https://github.com/<%= user.githubUsername %>\" target='_blank'>\n              @<%= user.githubUsername %>\n            </a>\n          </li>\n          <% } %>\n          <% if (user.weibo) { %>\n          <li>\n            <i class=\"fa fa-lg fa-fw fa-twitter\"></i>\n            <a class='dark' href=\"<%= user.weibo %>\" target='_blank'><%= user.weibo %></a>\n          </li>\n          <% } %>\n        </ul>\n      </div>\n      <p class='col_fade'>注册时间 <%= user.create_at_ago() %></p>\n      <% if (current_user && current_user.is_admin) { %>\n      <% if (!user.is_star) { %>\n      <span class='span-common' id='set_star_btn' action='set_star'>设为达人</span>\n      <% } else { %>\n      <span class='span-common' id='set_star_btn' action='cancel_star'>取消达人</span>\n      <% } %>\n\n      <% if (!user.is_block) { %>\n      <span class='span-common' id='set_block_btn' action='set_block'>屏蔽用户</span>\n      <% } else { %>\n      <span class='span-common' id='set_block_btn' action='cancel_block'>取消屏蔽用户</span>\n      <% } %>\n\n      <span class=\"span-common\" id=\"delete_all\">删除所有发言</span>\n\n      <br/><br/>\n      Email (Seen by Administrator): <a href=\"mailto:<%= user.email %>\"><%= user.email %></a>\n      <% if (!user.active) { %>\n      <a href=\"/active_account?key=<%- typeof(token) !== 'undefined' && token %>&name=<%= user.loginname %>\"\n         target=\"_blank\">\n         <span class=\"span-common\">\n           激活账号\n         </span>\n       </a>\n      <% } %>\n      <% } %>\n    </div>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>最近创建的话题</span>\n    </div>\n    <% if (typeof(recent_topics) !== 'undefined' && recent_topics.length > 0) { %>\n    <%- partial('../topic/abstract', { collection: recent_topics, as: 'topic' }) %>\n    <div class='cell more'>\n      <a class='dark' href=\"/user/<%= user.loginname %>/topics\">查看更多»</a>\n    </div>\n    <% } else { %>\n    <div class='inner'>\n      <p>无话题</p>\n    </div>\n    <% } %>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>最近参与的话题</span>\n    </div>\n    <% if (typeof(recent_replies) !== 'undefined' && recent_replies.length > 0) { %>\n    <%- partial('../topic/abstract', { collection: recent_replies, as: 'topic' }) %>\n    <div class='cell more'>\n      <a class='dark' href=\"/user/<%= user.loginname %>/replies\">查看更多»</a>\n    </div>\n    <% } else { %>\n    <div class='inner'>\n      <p>无话题</p>\n    </div>\n    <% } %>\n  </div>\n</div>\n\n<% if (current_user) { %>\n<script>\n  $(document).ready(function () {\n    $('#set_star_btn').click(function () {\n      var $me = $(this);\n      var action = $me.attr('action');\n      var params = {\n        user_id: '<%= user._id %>',\n        _csrf: '<%- csrf %>'\n      };\n      $.post('/user/' + action, params, function (data) {\n        if (data.status === 'success') {\n          if (action === 'set_star') {\n            $me.html('取消达人');\n            $me.attr('action', 'cancel_star');\n          } else {\n            $me.html('设为达人');\n            $me.attr('action', 'set_star');\n          }\n        }\n      }, 'json');\n    });\n\n    $('#set_block_btn').click(function () {\n      var $me = $(this);\n      var action = $me.attr('action');\n      var params = {\n        _csrf: '<%- csrf %>',\n        action: action\n      };\n      if (action === 'set_block' && !confirm('确定要屏蔽该用户吗？')) {\n        return;\n      }\n      $.post('/user/<%- user.loginname %>/block', params, function (data) {\n        if (data.status === 'success') {\n          if (action === 'set_block') {\n            $me.html('取消屏蔽用户');\n            $me.attr('action', 'cancel_block');\n          } else if (action === 'cancel_block') {\n            $me.html('屏蔽用户');\n            $me.attr('action', 'set_block');\n          }\n        }\n      }, 'json');\n    })\n\n    $('#delete_all').click(function () {\n      var $me = $(this);\n      var params = {\n        _csrf: '<%- csrf %>',\n      };\n      if (!confirm('确定要删除吗？（不会永久删除，只做标记位）')) {\n        return;\n      }\n      $.post('/user/<%- user.loginname %>/delete_all', params, function (data) {\n        if (data.status === 'success') {\n          alert('操作成功');\n        }\n      }, 'json');\n    })\n  });\n</script>\n<% } %>\n"
  },
  {
    "path": "views/user/replies.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'><a href=\"/user/<%=user.loginname%>\"><%=user.loginname%>的主页</a></li>\n      </ul>\n    </div>\n  </div>\n  <div class='panel'>\n    <div class=\"header\"><%= user.loginname %> 参与的话题</div>\n    <div class=\"inner padding\">\n    <% if(typeof(topics) !== 'undefined' && topics.length > 0){ %>\n    <%- partial('../topic/list',\n    {topics:topics,pages:pages,current_pages:current_page,base:'/user/'+user.loginname+'/replies'}) %>\n    <% }else{ %>\n    <div class='inner'>\n      <p>无话题</p>\n    </div>\n    <% } %>\n  </div>\n  </div>\n</div>\n\n"
  },
  {
    "path": "views/user/setting.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>设置</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if(typeof(error) !== 'undefined' && error){ %>\n      <div class=\"alert alert-error\">\n        <a class=\"close\" data-dismiss=\"alert\" href=\"#\">&times;</a>\n        <strong><%= error %></strong>\n      </div>\n      <% } %>\n      <% if (typeof(success) !== 'undefined' && success) { %>\n      <div class=\"alert alert-success\">\n        <strong><%= success %></strong>\n      </div>\n      <% } %>\n      <form id='setting_form' class='form-horizontal' action='/setting' method='post'>\n        <div class='control-group'>\n          <label class='control-label' for='name'>用户名</label>\n\n          <div class='controls'>\n            <input class='input-xlarge readonly' id='name' name='name' size='30' type='text' readonly='true'\n                   value=\"<%= loginname %>\"/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='email'>电子邮件</label>\n\n          <div class='controls'>\n            <input class='input-xlarge readonly' id='email' name='email' size='30' type='text'\n                   readonly='true' value=\"<%= email %>\"/>\n\n            <p>同时决定了 Gravatar 头像</p>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='url'>个人网站</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='url' name='url' size='30' type='text' value=\"<%= typeof(url) !== 'undefined' ? url : '' %>\"/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='location'>所在地点</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='location' name='location' size='30' type='text'\n                   value=\"<%= typeof(location) !== 'undefined' ? location : '' %>\"/>\n          </div>\n        </div>\n\n        <div class='control-group'>\n          <label class='control-label' for='weibo'>微博</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='weibo' name='weibo' size='30' type='text' value=\"<%= typeof(weibo) !== 'undefined' ? weibo : '' %>\"\n                   placeholder=\"e.g. http://weibo.com/cnodejs\"/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='github'>GitHub</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' id='github' name='github' size='30' type='text'\n                   value=\"<%= typeof(githubUsername) !== 'undefined' ? '@' + githubUsername : '' %>\" placeholder=\"@username\" readonly=\"readonly\" />\n            <p>请通过 GitHub 登陆 CNode 来修改此处</p>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='signature'>个性签名</label>\n\n          <div class='controls'>\n            <textarea class='input-xlarge' id='signature' name='signature' size='30'><%= typeof(signature) !== 'undefined' ? signature : \"\" %></textarea>\n          </div>\n        </div>\n        <input type='hidden' id='action' name='action' value='change_setting'/>\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n\n        <div class='form-actions'>\n          <input type='submit' class='span-primary submit_btn' data-loading-text=\"保存中..\" value='保存设置'/>\n        </div>\n      </form>\n    </div>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>更改密码</span>\n    </div>\n    <div class='inner'>\n      <form id='change_pass_form' class='form-horizontal' action='/setting' method='post'>\n        <div class='control-group'>\n          <label class='control-label' for='old_pass'>当前密码</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' type='password' id='old_pass' name='old_pass' size='30'/>\n          </div>\n        </div>\n        <div class='control-group'>\n          <label class='control-label' for='new_pass'>新密码</label>\n\n          <div class='controls'>\n            <input class='input-xlarge' type='password' id='new_pass' name='new_pass' size='30'/>\n          </div>\n        </div>\n        <input type='hidden' id='action' name='action' value='change_password'/>\n        <input type='hidden' name='_csrf' value='<%= csrf %>'/>\n\n        <div class='form-actions'>\n          <input type='submit' class='span-primary submit_btn' data-loading-text=\"更改中..\" value='更改密码'/>\n        </div>\n      </form>\n    </div>\n  </div>\n\n  <div class='panel'>\n    <div class='header'>\n      <span class='col_fade'>Access Token</span>\n    </div>\n    <div class='inner'>\n      <div>\n        <input type='button' class='span-primary refreshToken' value='刷新Token'/>\n      </div>\n      <div>\n        <span>字符串：</span>\n        <span id=\"accessToken\"><%- accessToken %></span>\n      </div>\n      <div>\n        <span>二维码：</span>\n        <span id=\"access-token-qrcode\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<script type=\"text/javascript\">\n  $(function() {\n    // qrcode generate\n    var accessToken = \"<%- accessToken %>\";\n    var qrcode = new QRCode(document.getElementById(\"access-token-qrcode\"), {\n      text: accessToken,\n      width: 200,\n      height: 200,\n    });\n    // END qrcode generate\n\n    // refreshToken\n    $(\".refreshToken\").on(\"click\", function() {\n      $.post(\"/user/refresh_token\", function(result) {\n        if (result.status === 'success') {\n          $(\"#accessToken\").text(result.accessToken);\n          qrcode.makeCode(result.accessToken)\n        } else {\n          alert(result.message);\n        }\n      })\n    })\n   });\n</script>\n"
  },
  {
    "path": "views/user/star.html",
    "content": "<li>\n  <div>\n    <i class=\"fa fa-star\"></i>\n    <a class='dark star_name' href=\"/user/<%= user.loginname %>\"><%= user.loginname %></a>\n    <span class='col_fade'><%= user.follower_count %> 粉丝</span>\n    <span class='col_fade'><%= user.following_count %> 关注</span>\n  </div>\n</li>\n"
  },
  {
    "path": "views/user/stars.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>社区达人</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if(typeof(stars) !== 'undefined' && stars.length > 0){ %>\n      <%- partial('../user/user',{collection:stars,as:'user'}) %>\n      <% }else{ %>\n      <p>还没有社区达人</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/user/top.html",
    "content": "<li>\n  <span class='top_score'><%= user.score %></span>\n  <span class=\"user_name\"><a href=\"/user/<%= user.loginname %>\"><%= user.loginname %></a></span>\n</li>\n"
  },
  {
    "path": "views/user/top100.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'>Top100 积分榜</li>\n      </ul>\n    </div>\n    <div class='inner'>\n      <% if(typeof(users) !== 'undefined' && users.length > 0){ %>\n      <table class='table table-condensed table-striped'>\n        <thead>\n          <th>#</th>\n          <th>用户名</th>\n          <th>积分</th>\n          <th>主题数</th>\n          <th>评论数</th>\n        </thead>\n        <tbody>\n        <%- partial('../user/top100_user',{collection:users,as:'user'}) %>\n        </tbody>\n      </table>\n      <% }else{ %>\n      <p>还没有用户</p>\n      <% } %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "views/user/top100_user.html",
    "content": "<tr>\n  <td><b><%= indexInCollection+1 %></b></td>\n  <td>\n    <a class='user_avatar' href=\"/user/<%= user.loginname %>\">\n      <img src=\"<%= proxy(user.avatar_url) %>\" title=\"<%= user.loginname %>\"/>\n    </a>\n    <span class='sp10'></span>\n    <a href='/user/<%= user.loginname %>'><%= user.loginname %></a></td>\n  <td><%= user.score %></td>\n  <td><%= user.topic_count %></td>\n  <td><%= user.reply_count %></td>\n</tr>\n"
  },
  {
    "path": "views/user/topics.html",
    "content": "<%- partial('../sidebar') %>\n\n<div id='content'>\n  <div class='panel'>\n    <div class='header'>\n      <ul class='breadcrumb'>\n        <li><a href='/'>主页</a><span class='divider'>/</span></li>\n        <li class='active'><a href=\"/user/<%=user.loginname%>\"><%=user.loginname%>的主页</a></li>\n      </ul>\n    </div>\n  </div>\n  <div class='panel'>\n    <div class=\"header\"><%=user.loginname%> 创建的话题</div>\n    <div class=\"inner padding\">\n    <% if(typeof(topics) !== 'undefined' && topics.length > 0 ){ %>\n    <%- partial('../topic/list',\n    {topics:topics,pages:pages,current_pages:current_page,base:'/user/'+user.loginname+'/topics'}) %>\n    <% }else{ %>\n    <div class='inner'>\n      <p>无话题</p>\n    </div>\n    <% } %>\n  </div>\n</div>\n</div>\n\n"
  },
  {
    "path": "views/user/user.html",
    "content": "<div class='user'>\n  <div>\n    <a href=\"/user/<%= user.loginname %>\">\n      <img class='user_avatar' src=\"<%= proxy(user.avatar_url) %>\" title=\"<%= user.loginname %>\"/>\n    </a>\n    <span class='user_name'><a class='dark' href=\"/user/<%= user.loginname %>\"><%= user.loginname %></a></span>\n\n    <div class='space'></div>\n    <span class='col_fade'><%= user.follower_count %> 粉丝 </span>\n    <span class='space'></span>\n    <span class='col_fade'><%= user.following_count %> 关注 </span>\n  </div>\n  <div>\n\t\t<span>\n\t\t\t<a href='mailto:<%= user.email %>'>\n        <i class=\"fa fa-envelope-o\" title='电子邮箱'></i>\n      </a>\n\t\t</span>\n    <% if (user.url) { %>\n\t\t<span>\n\t\t\t<a href=\"<%= user.url %>\" target='_blank'>\n        <i class=\"fa fa-home\" title='个人网站'></i>\n      </a>\n\t\t</span>\n    <% } %>\n    <% if (user.weibo) { %>\n\t\t<span>\n\t\t\t<a href=\"<%= user.weibo %>\" target='_blank'>\n        <i class=\"fa fa-weibo\" title='微博'></i>\n      </a>\n\t\t</span>\n    <% } %>\n  </div>\n</div>\n"
  },
  {
    "path": "web_router.js",
    "content": "/*!\n * nodeclub - route.js\n * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar express = require('express');\nvar sign = require('./controllers/sign');\nvar site = require('./controllers/site');\nvar user = require('./controllers/user');\nvar message = require('./controllers/message');\nvar topic = require('./controllers/topic');\nvar reply = require('./controllers/reply');\nvar rss = require('./controllers/rss');\nvar staticController = require('./controllers/static');\nvar auth = require('./middlewares/auth');\nvar limit = require('./middlewares/limit');\nvar github = require('./controllers/github');\nvar search = require('./controllers/search');\nvar passport = require('passport');\nvar configMiddleware = require('./middlewares/conf');\nvar config = require('./config');\n\nvar router = express.Router();\n\n// home page\nrouter.get('/', site.index);\n// sitemap\nrouter.get('/sitemap.xml', site.sitemap);\n// mobile app download\nrouter.get('/app/download', site.appDownload);\n\n// sign controller\nif (config.allow_sign_up) {\n  router.get('/signup', sign.showSignup);  // 跳转到注册页面\n  router.post('/signup', sign.signup);  // 提交注册信息\n} else {\n  // 进行github验证\n  router.get('/signup', function (req, res, next) {\n    return res.redirect('/auth/github')\n  });\n}\nrouter.post('/signout', sign.signout);  // 登出\nrouter.get('/signin', sign.showLogin);  // 进入登录页面\nrouter.post('/signin', sign.login);  // 登录校验\nrouter.get('/active_account', sign.activeAccount);  //帐号激活\n\nrouter.get('/search_pass', sign.showSearchPass);  // 找回密码页面\nrouter.post('/search_pass', sign.updateSearchPass);  // 更新密码\nrouter.get('/reset_pass', sign.resetPass);  // 进入重置密码页面\nrouter.post('/reset_pass', sign.updatePass);  // 更新密码\n\n// user controller\nrouter.get('/user/:name', user.index); // 用户个人主页\nrouter.get('/setting', auth.userRequired, user.showSetting); // 用户个人设置页\nrouter.post('/setting', auth.userRequired, user.setting); // 提交个人信息设置\nrouter.get('/stars', user.listStars); // 显示所有达人列表页\nrouter.get('/users/top100', user.top100);  // 显示积分前一百用户页\nrouter.get('/user/:name/collections', user.listCollectedTopics);  // 用户收藏的所有话题页\nrouter.get('/user/:name/topics', user.listTopics);  // 用户发布的所有话题页\nrouter.get('/user/:name/replies', user.listReplies);  // 用户参与的所有回复页\nrouter.post('/user/set_star', auth.adminRequired, user.toggleStar); // 把某用户设为达人\nrouter.post('/user/cancel_star', auth.adminRequired, user.toggleStar);  // 取消某用户的达人身份\nrouter.post('/user/:name/block', auth.adminRequired, user.block);  // 禁言某用户\nrouter.post('/user/:name/delete_all', auth.adminRequired, user.deleteAll);  // 删除某用户所有发言\nrouter.post('/user/refresh_token', auth.userRequired, user.refreshToken);  // 刷新用户token\n\n// message controler\nrouter.get('/my/messages', auth.userRequired, message.index); // 用户个人的所有消息页\n\n// topic\n\n// 新建文章界面\nrouter.get('/topic/create', auth.userRequired, topic.create);\n\nrouter.get('/topic/:tid', topic.index);  // 显示某个话题\nrouter.post('/topic/:tid/top', auth.adminRequired, topic.top);  // 将某话题置顶\nrouter.post('/topic/:tid/good', auth.adminRequired, topic.good); // 将某话题加精\nrouter.get('/topic/:tid/edit', auth.userRequired, topic.showEdit);  // 编辑某话题\nrouter.post('/topic/:tid/lock', auth.adminRequired, topic.lock); // 锁定主题，不能再回复\n\nrouter.post('/topic/:tid/delete', auth.userRequired, topic.delete);\n\n// 保存新建的文章\nrouter.post('/topic/create', auth.userRequired, limit.peruserperday('create_topic', config.create_post_per_day, {showJson: false}), topic.put);\n\nrouter.post('/topic/:tid/edit', auth.userRequired, topic.update);\nrouter.post('/topic/collect', auth.userRequired, topic.collect); // 关注某话题\nrouter.post('/topic/de_collect', auth.userRequired, topic.de_collect); // 取消关注某话题\n\n// reply controller\nrouter.post('/:topic_id/reply', auth.userRequired, limit.peruserperday('create_reply', config.create_reply_per_day, {showJson: false}), reply.add); // 提交一级回复\nrouter.get('/reply/:reply_id/edit', auth.userRequired, reply.showEdit); // 修改自己的评论页\nrouter.post('/reply/:reply_id/edit', auth.userRequired, reply.update); // 修改某评论\nrouter.post('/reply/:reply_id/delete', auth.userRequired, reply.delete); // 删除某评论\nrouter.post('/reply/:reply_id/up', auth.userRequired, reply.up); // 为评论点赞\nrouter.post('/upload', auth.userRequired, topic.upload); //上传图片\n\n// static\nrouter.get('/about', staticController.about);\nrouter.get('/faq', staticController.faq);\nrouter.get('/getstart', staticController.getstart);\nrouter.get('/robots.txt', staticController.robots);\nrouter.get('/api', staticController.api);\n\n//rss\nrouter.get('/rss', rss.index);\n\n// github oauth\nrouter.get('/auth/github', configMiddleware.github, passport.authenticate('github'));\nrouter.get('/auth/github/callback',\n  passport.authenticate('github', { failureRedirect: '/signin' }),\n  github.callback);\nrouter.get('/auth/github/new', github.new);\nrouter.post('/auth/github/create', limit.peripperday('create_user_per_ip', config.create_user_per_ip, {showJson: false}), github.create);\n\nrouter.get('/search', search.index);\n\nif (!config.debug) { // 这个兼容破坏了不少测试\n\trouter.get('/:name', function (req, res) {\n\t  res.redirect('/user/' + req.params.name)\n\t})\n}\n\n\nmodule.exports = router;\n"
  }
]