[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"@vue/app\"\n  ],\n  \"plugins\": [\n    [\n      \"component\",\n      {\n        \"libraryName\": \"mint-ui\",\n        \"style\": true\n      }\n    ]\n  ]\n}"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"root\": true,\n  \"extends\": [\n    \"plugin:vue/essential\",\n    \"eslint:recommended\"\n  ],\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  \"globals\": {\n  },\n  \"rules\": {\n    \"no-console\": \"off\",\n    \"no-case-declarations\": \"off\"\n  }\n}"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n/demo\n\n/tests/e2e/videos/\n/tests/e2e/screenshots/\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": ".npmignore",
    "content": "*\n*/\n!dist/*.js\n!package*.json"
  },
  {
    "path": ".npmrc",
    "content": "git-tag-version=false"
  },
  {
    "path": ".postcssrc",
    "content": "{\n  \"plugins\": {\n    \"autoprefixer\": {}\n  }\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [0.7.2](https://github.com/whinc/web-console/compare/v0.7.1...v0.7.2) (2019-04-23)\n\n### Bug Fixes\n\n- **console:** 修复打印多个参数时,它们之间没有空白符分割的问题 ([2292733](https://github.com/whinc/web-console/commit/2292733))\n\n## [0.7.1](https://github.com/whinc/web-console/compare/v0.7.0...v0.7.1) (2019-04-23)\n\n### Bug Fixes\n\n- 修复 activeTab 失效问题 ([c785e38](https://github.com/whinc/web-console/commit/c785e38))\n\n# [0.7.0](https://github.com/whinc/web-console/compare/v0.6.2...v0.7.0) (2019-04-23)\n\n### Bug Fixes\n\n- **plugin:** 修复 onWebConsoleShow 触发两次的问题;修复插件面板高度被改变的 bug ([bd88ddd](https://github.com/whinc/web-console/commit/bd88ddd))\n- **plugin:** 修复插件生命周期未触发问题和插件重复问题 ([7c89fd6](https://github.com/whinc/web-console/commit/7c89fd6))\n- 修复 lint 告警 ([82aedb5](https://github.com/whinc/web-console/commit/82aedb5))\n\n### Features\n\n- **plugin:** 初步支持插件的注册,展示,部分生命周期方法 ([513b530](https://github.com/whinc/web-console/commit/513b530))\n- **plugin:** 增加插件获取当前设置的方法;增加插件首次加载设置的周期方法 ([0f5d129](https://github.com/whinc/web-console/commit/0f5d129))\n- **plugin:** 增强插件支持 ([342a499](https://github.com/whinc/web-console/commit/342a499))\n- **plugin:** 新增几种内置组件暴露给插件使用 ([a17c840](https://github.com/whinc/web-console/commit/a17c840))\n- 更新在线 demo ([3275f42](https://github.com/whinc/web-console/commit/3275f42))\n\n## [0.6.2](https://github.com/whinc/web-console/compare/v0.6.1...v0.6.2) (2019-03-26)\n\n### Bug Fixes\n\n- **network:** 修复部分情况下请求参数未展示的 bug ([edeff31](https://github.com/whinc/web-console/commit/edeff31))\n\n## [0.6.1](https://github.com/whinc/web-console/compare/v0.6.0...v0.6.1) (2019-03-24)\n\n### Bug Fixes\n\n- **network:** 修复样式 ([f815474](https://github.com/whinc/web-console/commit/f815474))\n\n# [0.6.0](https://github.com/whinc/web-console/compare/v0.5.0...v0.6.0) (2019-03-24)\n\n### Bug Fixes\n\n- **network:** 修复 fetch 请求参数为 Request 时的异常 ([e4739b6](https://github.com/whinc/web-console/commit/e4739b6))\n- **network:** 修复请求状态展示错误 ([74f53b6](https://github.com/whinc/web-console/commit/74f53b6))\n\n### Features\n\n- **network:** 初步支持 fetch 请求 ([5e64f03](https://github.com/whinc/web-console/commit/5e64f03))\n- **settings:** 支持设置\"显示/隐藏\"网络请求类型 ([b70cad5](https://github.com/whinc/web-console/commit/b70cad5))\n\n# [0.5.0](https://github.com/whinc/web-console/compare/v0.4.7...v0.5.0) (2019-03-16)\n\n### Bug Fixes\n\n- 修复 lint 警告 ([e21046e](https://github.com/whinc/web-console/commit/e21046e))\n\n### Features\n\n- **console:** 增加搜索日志 ([8c90bac](https://github.com/whinc/web-console/commit/8c90bac))\n\n## [0.4.7](https://github.com/whinc/web-console/compare/v0.4.6...v0.4.7) (2019-03-07)\n\n### Bug Fixes\n\n- 修复 vue-infinite-scroll 插件与宿主环境冲突,导致宿主引入该插件后无效的问题 ([8a89a15](https://github.com/whinc/web-console/commit/8a89a15))\n\n## [0.4.6](https://github.com/whinc/web-console/compare/v0.4.4...v0.4.6) (2019-03-04)\n\n### Bug Fixes\n\n- 修复 PC 端滚动条覆盖面板右边缘的问题 ([bd03c0c](https://github.com/whinc/web-console/commit/bd03c0c))\n- **application:** 输入框设置高度撑破父元素的问题 ([dbbfa9c](https://github.com/whinc/web-console/commit/dbbfa9c))\n- **console:** 将输出内容对齐到每行的顶部 ([85a0c5f](https://github.com/whinc/web-console/commit/85a0c5f))\n- **element:** 修复<br>标签不换行导致的 element 面板中盒模型样式坍塌问题 ([1a6cd8b](https://github.com/whinc/web-console/commit/1a6cd8b))\n\n### Reverts\n\n- px 单位转 vw 单位 ([93c52b8](https://github.com/whinc/web-console/commit/93c52b8))\n\n## [0.4.4](https://github.com/whinc/web-console/compare/v0.4.2...v0.4.4) (2019-03-01)\n\n### Bug Fixes\n\n- **console:** 修复打印多参数时参数之间无空白符分隔的问题 ([7c7c525](https://github.com/whinc/web-console/commit/7c7c525))\n\n### Features\n\n- **settings:** 增加反馈入口 ([26984b9](https://github.com/whinc/web-console/commit/26984b9))\n\n## [0.4.2](https://github.com/whinc/web-console/compare/v0.4.1...v0.4.2) (2019-02-26)\n\n### Features\n\n- **console:** 处理未捕获的异常(error 和 unhandledrejection 事件) ([04e7da5](https://github.com/whinc/web-console/commit/04e7da5))\n\n## [0.4.1](https://github.com/whinc/web-console/compare/v0.4.0-beta.2...v0.4.1) (2019-02-21)\n\n### Bug Fixes\n\n- **console:** 修复 Symbole 作为对象 key 时,打印对象报错的问题 ([27ff599](https://github.com/whinc/web-console/commit/27ff599))\n- **console:** 修复打印数组时末尾多出一个逗号 ([5b5e04d](https://github.com/whinc/web-console/commit/5b5e04d))\n\n### Features\n\n- **console:** log 格式输出支持 %c 占位符 ([0ba036d](https://github.com/whinc/web-console/commit/0ba036d))\n\n# [0.4.0-beta.2](https://github.com/whinc/web-console/compare/v0.4.0-beta.1...v0.4.0-beta.2) (2019-02-18)\n\n### Bug Fixes\n\n- **element:** 修复 Element.prototype.getAttributeNames 的兼容性问题 ([92b0355](https://github.com/whinc/web-console/commit/92b0355))\n\n# [0.4.0-beta.1](https://github.com/whinc/web-console/compare/v0.3.0-beta.6...v0.4.0-beta.1) (2019-02-16)\n\n### Bug Fixes\n\n- **application:** 修复选中行没有高亮颜色的 bug ([33a4633](https://github.com/whinc/web-console/commit/33a4633))\n\n### Features\n\n- **element:** 展示元素计算属性的继承值 ([072fbf2](https://github.com/whinc/web-console/commit/072fbf2))\n- **element:** 计算属性面板的颜色值增加颜色小方块展示 ([65987d2](https://github.com/whinc/web-console/commit/65987d2))\n\n# [0.3.0-beta.6](https://github.com/whinc/web-console/compare/v0.3.0-beta.5...v0.3.0-beta.6) (2019-02-01)\n\n### Bug Fixes\n\n- 去除 ios 默认的 tap 高亮效果 ([873c02c](https://github.com/whinc/web-console/commit/873c02c))\n- 无法复制文本的问题 ([a224c2e](https://github.com/whinc/web-console/commit/a224c2e))\n- **components:** 修复 TabBar 组件子项超出宽度部分不可见的问题 ([56b7c40](https://github.com/whinc/web-console/commit/56b7c40))\n- **console:** 优化消息换行展示 ([ed0567a](https://github.com/whinc/web-console/commit/ed0567a))\n- **console:** 修复 Element.prototype.scrollTo 兼容性问题 ([70d3410](https://github.com/whinc/web-console/commit/70d3410))\n- **console:** 修复数组包含非数字下标时的展示问题 ([0e9de4f](https://github.com/whinc/web-console/commit/0e9de4f))\n- **element:** 修复审查分组选择器匹配元素时报错问题 ([fb43814](https://github.com/whinc/web-console/commit/fb43814))\n- **element:** 修复样式被宿主样式覆盖的 bug ([3e218ba](https://github.com/whinc/web-console/commit/3e218ba))\n- **element:** 禁止审查 doctype 元素 ([bc7cea6](https://github.com/whinc/web-console/commit/bc7cea6))\n- **element:** 隐藏 dom paths 的滚动条 ([e13e27b](https://github.com/whinc/web-console/commit/e13e27b))\n\n### Features\n\n- **element:** dom path 紧凑展示时优先展示元素 id ([24e631f](https://github.com/whinc/web-console/commit/24e631f))\n- **element:** 元素审查的 computed 面板增加盒模型 ([883ac26](https://github.com/whinc/web-console/commit/883ac26))\n- **element:** 元素审查面板新增计算样式 ([ce87971](https://github.com/whinc/web-console/commit/ce87971))\n- **element:** 审查元素的 styles 面板增加盒模型 ([fd83e9a](https://github.com/whinc/web-console/commit/fd83e9a))\n- **element:** 支持属性值中颜色值预览 ([8acb299](https://github.com/whinc/web-console/commit/8acb299))\n- **element:** 支持显示 doctype 元素 ([47f862d](https://github.com/whinc/web-console/commit/47f862d))\n- **element:** 新增 DOM 路径 ([4d41b3c](https://github.com/whinc/web-console/commit/4d41b3c))\n- **element:** 新增属性缩写形式折叠展开 ([02b952d](https://github.com/whinc/web-console/commit/02b952d))\n- **element:** 美化元素审查面板样式 ([e5309b3](https://github.com/whinc/web-console/commit/e5309b3))\n- **element:** 计算样式增加获取继承样式的方法 ([6934b5a](https://github.com/whinc/web-console/commit/6934b5a))\n- **element:** 选中底部 dom paths 时 dom tree 自动滚动到可视区域 ([22d6b1e](https://github.com/whinc/web-console/commit/22d6b1e))\n\n# [0.3.0-beta.5](https://github.com/whinc/web-console/compare/v0.3.0-beta.4...v0.3.0-beta.5) (2018-12-06)\n\n### Features\n\n- **application:** 调整底部清除和刷新范围；删除存储数据增加二次确认弹窗 ([1953419](https://github.com/whinc/web-console/commit/1953419))\n- **element:** 支持审查元素样式 ([26344f6](https://github.com/whinc/web-console/commit/26344f6))\n- **settings:** 增加 CHANGELOG ([7e92468](https://github.com/whinc/web-console/commit/7e92468))\n\n### Performance Improvements\n\n- **console:** 提升批量打印日志时的显示性能 ([776891e](https://github.com/whinc/web-console/commit/776891e))\n\n# [0.3.0-beta.4](https://github.com/whinc/web-console/compare/v0.3.0-beta.3...v0.3.0-beta.4) (2018-12-03)\n\n### Bug Fixes\n\n- **network:** 修复底部隐藏按钮失效问题 ([a0d2269](https://github.com/whinc/web-console/commit/a0d2269))\n\n### Features\n\n- **application:** 支持折叠工具栏 ([ca20bfd](https://github.com/whinc/web-console/commit/ca20bfd))\n- **application:** 新增刷新全部和清除全部快捷按钮 ([fd4e4b4](https://github.com/whinc/web-console/commit/fd4e4b4))\n\n# [0.3.0-beta.3](https://github.com/whinc/web-console/compare/v0.3.0-beta.2...v0.3.0-beta.3) (2018-12-03)\n\n### Bug Fixes\n\n- **element:** 修复同时选中开始和结束标签的问题 ([6a0cb15](https://github.com/whinc/web-console/commit/6a0cb15))\n- **settings:** 移除 input 的 appearance 样式 ([fa9a0fe](https://github.com/whinc/web-console/commit/fa9a0fe))\n- **settings:** 移除 About 页标题地下多出的横线 ([14e8cde](https://github.com/whinc/web-console/commit/14e8cde))\n\n### Features\n\n- **application:** 增加底部隐藏按钮 ([5eb10fe](https://github.com/whinc/web-console/commit/5eb10fe))\n- **element:** 优化元素选中态样式；支持指定 DOM 树展开深度 ([76bb6d3](https://github.com/whinc/web-console/commit/76bb6d3))\n- **element:** 增加元素审查面板 ([03e82f8](https://github.com/whinc/web-console/commit/03e82f8))\n- **element:** 支持无内容标签的正确显示 ([cd31e3e](https://github.com/whinc/web-console/commit/cd31e3e))\n- **element:** 节点显式过长时自动换行 ([aa89681](https://github.com/whinc/web-console/commit/aa89681))\n\n# [0.3.0-beta.2](https://github.com/whinc/web-console/compare/v0.3.0-beta.1...v0.3.0-beta.2) (2018-11-29)\n\n### Bug Fixes\n\n- **settings:** 移除 input 的 appearance 样式 ([c7a25b1](https://github.com/whinc/web-console/commit/c7a25b1))\n\n### Features\n\n- **element:** 元素增加折叠展开箭头 ([d000198](https://github.com/whinc/web-console/commit/d000198))\n- **element:** 支持显式 html 结构 ([fbac371](https://github.com/whinc/web-console/commit/fbac371))\n\n# [0.3.0-beta.1](https://github.com/whinc/web-console/compare/0.3.0-beta.1...v0.3.0-beta.1) (2018-11-24)\n\n### Bug Fixes\n\n- **application:** 分页加载边界条件判断错误 ([0f18ca1](https://github.com/whinc/web-console/commit/0f18ca1))\n- **application:** 删除后自动选中下一项 ([3007755](https://github.com/whinc/web-console/commit/3007755))\n- **application:** 删除并重新写入 cookie 不更新 ([477c0fa](https://github.com/whinc/web-console/commit/477c0fa))\n- **application:** 移除 IOS 输入框边框；弹窗可见时阻止 IOS 上缩放行为 ([31c50ec](https://github.com/whinc/web-console/commit/31c50ec))\n\n### Performance Improvements\n\n- **application:** 优化修改和删除操作的性能 ([c376187](https://github.com/whinc/web-console/commit/c376187))\n- **application:** 大幅提升展示大量(1W+)storage 和 cookie 的性能 ([66b8c45](https://github.com/whinc/web-console/commit/66b8c45))\n\n# [0.3.0-beta.0](https://github.com/whinc/web-console/compare/v0.2.0-rc.15...v0.3.0-beta.0) (2018-11-20)\n\n### Bug Fixes\n\n- **application:** cookie 操作 ([01dc531](https://github.com/whinc/web-console/commit/01dc531))\n- **application:** 异常 ([bf80d3b](https://github.com/whinc/web-console/commit/bf80d3b))\n- **application:** 样式问题 ([c41b07c](https://github.com/whinc/web-console/commit/c41b07c))\n- **network:** 解决 ios 上网络面板无法滚动问题 ([a3d39a6](https://github.com/whinc/web-console/commit/a3d39a6))\n\n### Features\n\n- **application:** localStorage 和 sessionStorage 支持编辑、新增 ([3cef20c](https://github.com/whinc/web-console/commit/3cef20c))\n- **application:** 优化编辑状态样式 ([10e7727](https://github.com/whinc/web-console/commit/10e7727))\n- **application:** 固定表头滚动表内容 ([9624ea3](https://github.com/whinc/web-console/commit/9624ea3))\n- **application:** 增加 value 的过滤 ([84eddef](https://github.com/whinc/web-console/commit/84eddef))\n- **application:** 增加对 cookie 的编辑和新增操作 ([273af90](https://github.com/whinc/web-console/commit/273af90))\n- **application:** 新增 localStorage 和 sessionStorage ([292f863](https://github.com/whinc/web-console/commit/292f863))\n- **application:** 美化 cookie 面板 ([4deefb9](https://github.com/whinc/web-console/commit/4deefb9))\n- **application:** 表格增加奇偶行色差 ([e8996e8](https://github.com/whinc/web-console/commit/e8996e8))\n- **console:** 增加最大消息数量限制 ([d69e15c](https://github.com/whinc/web-console/commit/d69e15c))\n\n### Performance Improvements\n\n- 用 v-if 替换部分 v-show 提高性能 ([1ec40f2](https://github.com/whinc/web-console/commit/1ec40f2))\n- **application:** 优化保存性能 ([802c87f](https://github.com/whinc/web-console/commit/802c87f))\n- **application:** 提高巨量(1w+)storage 条目的展示性能 ([258568d](https://github.com/whinc/web-console/commit/258568d))\n- **console:** 优化短期内批量打印日志造成的 UI 假死现象 ([53be149](https://github.com/whinc/web-console/commit/53be149))\n\n# [0.2.0-rc.15](https://github.com/whinc/web-console/compare/v0.2.0-rc.14...v0.2.0-rc.15) (2018-11-08)\n\n# [0.2.0-rc.14](https://github.com/whinc/web-console/compare/v0.2.0-rc.13...v0.2.0-rc.14) (2018-11-08)\n\n### Bug Fixes\n\n- **console:** ios 上滚动失效 ([c282ad5](https://github.com/whinc/web-console/commit/c282ad5))\n- **console:** 美化 safari 上输出的错误堆栈信息 ([4634cfa](https://github.com/whinc/web-console/commit/4634cfa))\n- **settings:** ios 上图标展示过大 ([66f7beb](https://github.com/whinc/web-console/commit/66f7beb))\n\n### Features\n\n- **console:** 增加时间戳 ([ec6752c](https://github.com/whinc/web-console/commit/ec6752c))\n- **settings:** 增加设置面板 ([89c4492](https://github.com/whinc/web-console/commit/89c4492))\n- **settings:** 增加设置项持久化 ([1f542bc](https://github.com/whinc/web-console/commit/1f542bc))\n\n# [0.2.0-rc.13](https://github.com/whinc/web-console/compare/v0.2.0-rc.12...v0.2.0-rc.13) (2018-11-06)\n\n### Bug Fixes\n\n- **console:** 循环打印错误日志 ([a570170](https://github.com/whinc/web-console/commit/a570170))\n\n### Features\n\n- **console:** 缩略展示长字符串 ([a7af920](https://github.com/whinc/web-console/commit/a7af920))\n\n# [0.2.0-rc.12](https://github.com/whinc/web-console/compare/v0.2.0-rc.11...v0.2.0-rc.12) (2018-11-05)\n\n### Features\n\n- **application:** 实现 cookie 的展示、删除、过滤、刷新 ([0a26517](https://github.com/whinc/web-console/commit/0a26517))\n\n# [0.2.0-rc.11](https://github.com/whinc/web-console/compare/v0.2.0-rc.10...v0.2.0-rc.11) (2018-10-31)\n\n### Features\n\n- 增加设置(展示版本信息) ([6ab8caa](https://github.com/whinc/web-console/commit/6ab8caa))\n\n# [0.2.0-rc.10](https://github.com/whinc/web-console/compare/v0.2.0-rc.9...v0.2.0-rc.10) (2018-10-30)\n\n### Bug Fixes\n\n- **console:** 保留消息换行符 ([2d75a7c](https://github.com/whinc/web-console/commit/2d75a7c))\n- **console:** 数组展示 ([8b8faa0](https://github.com/whinc/web-console/commit/8b8faa0))\n\n### Features\n\n- **application:** 增加 cookie 展示 ([4df4f8c](https://github.com/whinc/web-console/commit/4df4f8c))\n- **application:** 新增 Application 面板 ([91daa76](https://github.com/whinc/web-console/commit/91daa76))\n- **application:** 新增 cookie/localStorage/sessionStorage 的 UI ([aac507d](https://github.com/whinc/web-console/commit/aac507d))\n- **console:** 打印 Error 对象堆栈信息 ([599206b](https://github.com/whinc/web-console/commit/599206b))\n- **console:** 输出日志时可自动定位到最新位置 ([2509392](https://github.com/whinc/web-console/commit/2509392))\n\n# [0.2.0-rc.9](https://github.com/whinc/web-console/compare/v0.2.0-rc.7...v0.2.0-rc.9) (2018-10-26)\n\n### Performance Improvements\n\n- **console:** 提升面板切换性能 ([a068d6c](https://github.com/whinc/web-console/commit/a068d6c))\n\n# [0.2.0-rc.7](https://github.com/whinc/web-console/compare/v0.2.0-rc.6...v0.2.0-rc.7) (2018-10-09)\n\n# [0.2.0-rc.6](https://github.com/whinc/web-console/compare/v0.2.0-rc.5...v0.2.0-rc.6) (2018-10-09)\n\n### Features\n\n- **network:** 响应数据增加语法高亮 ([dcc3df7](https://github.com/whinc/web-console/commit/dcc3df7))\n- **network:** 增加 JSON 数据预览 ([a099205](https://github.com/whinc/web-console/commit/a099205))\n- **network:** 增加响应数据预览 ([0bb4e95](https://github.com/whinc/web-console/commit/0bb4e95))\n- **network:** 支持预览 gif/jpg/svg/txt ([72fcd52](https://github.com/whinc/web-console/commit/72fcd52))\n\n# [0.2.0-rc.5](https://github.com/whinc/web-console/compare/v0.2.0-rc.4...v0.2.0-rc.5) (2018-09-14)\n\n### Bug Fixes\n\n- 修复触摸滚动穿透问题 ([f017338](https://github.com/whinc/web-console/commit/f017338))\n\n# [0.2.0-rc.4](https://github.com/whinc/web-console/compare/v0.2.0-rc.3...v0.2.0-rc.4) (2018-09-11)\n\n### Bug Fixes\n\n- **network:** response 数据超出边界时无法滚动查看 ([4111487](https://github.com/whinc/web-console/commit/4111487))\n\n# [0.2.0-rc.3](https://github.com/whinc/web-console/compare/v0.2.0-rc.2...v0.2.0-rc.3) (2018-09-10)\n\n### Bug Fixes\n\n- **console:** 未正确识别占位符 %c 导致的显示问题 ([c740d16](https://github.com/whinc/web-console/commit/c740d16))\n- 面板可见时悬浮按钮未隐藏 ([41f8af5](https://github.com/whinc/web-console/commit/41f8af5))\n\n# [0.2.0-rc.2](https://github.com/whinc/web-console/compare/v0.2.0-rc.1...v0.2.0-rc.2) (2018-09-08)\n\n# [0.2.0-rc.1](https://github.com/whinc/web-console/compare/v0.1.5...v0.2.0-rc.1) (2018-09-08)\n\n### Bug Fixes\n\n- 对象展示问题 ([0eb0a14](https://github.com/whinc/web-console/commit/0eb0a14))\n- 测试用例报错 ([f2ad431](https://github.com/whinc/web-console/commit/f2ad431))\n- **console:** 修复字符串显式时没有引号包裹；修复字符串很长时没有自动换行的问题 ([42b27c2](https://github.com/whinc/web-console/commit/42b27c2))\n- 移除同名文件 ([3c86612](https://github.com/whinc/web-console/commit/3c86612))\n- **console:** get 访问器的计算结果没有展开 ([64fc61e](https://github.com/whinc/web-console/commit/64fc61e))\n- **console:** vue 向原始日志数据中添加额外属性的问题 ([ef0424b](https://github.com/whinc/web-console/commit/ef0424b))\n- **console:** 使日志每行内容垂直居中 ([5d95035](https://github.com/whinc/web-console/commit/5d95035))\n- **console:** 多参数打印空格问题 ([9b90a00](https://github.com/whinc/web-console/commit/9b90a00))\n- **console:** 对象的折叠展开态 ([0c59572](https://github.com/whinc/web-console/commit/0c59572))\n- **console:** 属性名和值过长时导致属性名未对齐;Symb 和转符串拼接时报错 ([0e6c192](https://github.com/whinc/web-console/commit/0e6c192))\n- **console:** 打印对象时丢失不可枚举和 symbol 属性 ([f3901ef](https://github.com/whinc/web-console/commit/f3901ef))\n- **console:** 打印的对象属性键换行导致不易阅读的问题 ([53f018a](https://github.com/whinc/web-console/commit/53f018a))\n- **console:** 样式错误显式为斜体 ([83f24ca](https://github.com/whinc/web-console/commit/83f24ca))\n- **console:** 计算属性结果没有高亮 ([a769a41](https://github.com/whinc/web-console/commit/a769a41))\n- **network:** 状态码 ([d39e2c2](https://github.com/whinc/web-console/commit/d39e2c2))\n\n### Features\n\n- **cosole:** 增加多参数高亮展示；增加 console 面板内部错误处理； ([0b13707](https://github.com/whinc/web-console/commit/0b13707))\n- **network:** 优化网络请求 Headers 的展示 ([fe26231](https://github.com/whinc/web-console/commit/fe26231))\n- **network:** 增加对 POST 中 JSON 数据格式的展示 ([2b25641](https://github.com/whinc/web-console/commit/2b25641))\n- **network:** 增加接口测试代码；解决 onreadystatechange 事件注册时机问题 ([2822d35](https://github.com/whinc/web-console/commit/2822d35))\n- **network:** 增加清除；优化行高；补充测试用例 ([ceff5e7](https://github.com/whinc/web-console/commit/ceff5e7))\n- **network:** 增加请求头、查询参数和 POST 数据展示 ([4075a9f](https://github.com/whinc/web-console/commit/4075a9f))\n- **network:** 请求头信息分类展示 ([6fdb205](https://github.com/whinc/web-console/commit/6fdb205))\n- **network:** 错误状态码高亮展示 ([78e78ac](https://github.com/whinc/web-console/commit/78e78ac))\n- 入口浮标支持滑动、图标样式 ([01bee17](https://github.com/whinc/web-console/commit/01bee17))\n\n## [0.1.5](https://github.com/whinc/web-console/compare/v0.1.4...v0.1.5) (2018-06-20)\n\n## [0.1.4](https://github.com/whinc/web-console/compare/v0.1.3...v0.1.4) (2018-06-20)\n\n## [0.1.3](https://github.com/whinc/web-console/compare/v0.1.2...v0.1.3) (2018-06-20)\n\n## [0.1.2](https://github.com/whinc/web-console/compare/v0.1.1...v0.1.2) (2018-06-12)\n\n## [0.1.1](https://github.com/whinc/web-console/compare/1dfc2f0...v0.1.1) (2018-06-12)\n\n### Bug Fixes\n\n- **build:** 修复 npm run serve 问题 ([93162f9](https://github.com/whinc/web-console/commit/93162f9))\n- **build:** 修复构建问题 ([a3eb6a2](https://github.com/whinc/web-console/commit/a3eb6a2))\n- **demo:** 修正日志打印接口调用 ([83882a9](https://github.com/whinc/web-console/commit/83882a9))\n- **network:** 修复手机上无法滚动 ([919c618](https://github.com/whinc/web-console/commit/919c618))\n\n### Features\n\n- **console:** 增加 clear 和 hide 两个操作 ([f0238a4](https://github.com/whinc/web-console/commit/f0238a4))\n- **console:** 增加日志消息分类展示 ([f17a653](https://github.com/whinc/web-console/commit/f17a653))\n- **ConsolePanel:** 美化日志输出样式 ([1dfc2f0](https://github.com/whinc/web-console/commit/1dfc2f0))\n- **network:** 内容超出时变为 scroll ([d4efd7f](https://github.com/whinc/web-console/commit/d4efd7f))\n- **network:** 初步实现对 XMLHttpRequest 的请求的拦截显示 ([bfc8f72](https://github.com/whinc/web-console/commit/bfc8f72))\n- WebConsole 支持初始化参数；增加测试用例 ([9974f00](https://github.com/whinc/web-console/commit/9974f00))\n- **network:** 增加样式 ([dbf58f8](https://github.com/whinc/web-console/commit/dbf58f8))\n- **network:** 展示请求响应头和数据 ([3469c9f](https://github.com/whinc/web-console/commit/3469c9f))\n- **network:** 显示请求的 response 内容 ([d3c759e](https://github.com/whinc/web-console/commit/d3c759e))\n- 导出全局的 WebConsole 变量 ([a0282a4](https://github.com/whinc/web-console/commit/a0282a4))\n- 面板的 Tab 栏替换成自定义组件 ([37f6923](https://github.com/whinc/web-console/commit/37f6923))\n"
  },
  {
    "path": "cypress.json",
    "content": "{\n  \"pluginsFile\": \"tests/e2e/plugins/index.js\"\n}\n"
  },
  {
    "path": "docs/depoly.md",
    "content": "## 部署\n\n发布到 npm 仓库：\n\n```bash\nnpm run semantic-release\n```\n\n更新 github.io 在线示例：\n\n```bash\nnpm run depoly\n```\n\n本地调试\n\n```js\nconst script = document.createElement(\"script\");\nscript.src = \"http://localhost:8081/app.js\";\nscript.onload = function() {\n  new window.WebConsole();\n};\ndocument.body.appendChild(script);\n```\n"
  },
  {
    "path": "docs/plugin.md",
    "content": "# web-console 插件开发\n\n通过插件可以增强 web-console 的能力，扩大使用场景。web-console 插件基于 Vue 组件开发，提供了丰富的 API、生命周期方法、内置组件、自定义偏好设置，同时提供了一个初始的插件开发项目模板方便开发、测试和发布。\n\n## 一个简单的插件示例\n\n下面是一个简单的插件示例。\n\n首先，创建一个文件 MyPlugin.js 用于定义插件。\n\n```js\nexport default function(WebConsole, options) {\n  return new WebConsole.Plugin({\n    id: \"myPlugin\",\n    name: \"我的插件\",\n    component: {\n      render(h) {\n        return h(\"h1\", null, \"这是我的第一个插件\");\n      }\n    }\n  });\n}\n```\n\n然后通过`WebConsole.use()`方法注册插件，当 web-console 运行起来后，插件会自动加载到主面板。\n\n```js\n// 使用插件\nimport WebConsole from \"@whinc/web-console\";\nimport MyPlugin from \"my-plugin\";\n\nWebConsole.use(MyPlugin);\nnew WebConsole();\n```\n\n运行截图如下：\n\n![](snapshot_plugin.png)\n\n## 创建插件\n\nweb-console 插件是一个返回`WebConsole.Plugin`实例的函数，函数的第一个参数是`WebConsole`对象，第二个参数是传递给插件的参数。\n\n```js\nfunction MyPlugin(WebConsole, options) {\n  return new WebConsole.Plugin({\n    /*...*/\n  });\n}\n```\n\n`WebConsole.Plugin`类是所有插件的基类，返回的插件实例必须继承自它，它的构造函数接受一个配置参数，支持的字段及取值如下：\n\n| 字段      | 类型         | 必填  | 备注                                 |\n| --------- | ------------ | ----- | ------------------------------------ |\n| id        | string       | true  | 不能与已安装的插件 id 相同           |\n| name      | string       | false | 插件名称，用于显示，缺省时与 id 相同 |\n| component | VueComponent | true  | 插件主面板，是一个 Vue 组件          |\n| settings  | Object       | false | 增加到设置面板的设置项               |\n\n> 参数配置中的 component 注入了一些 web-console 组件，可以直接使用。（后续补充关于这些内置组件的文档）\n\n## 注册插件\n\n通过`WebConsole.use()`方法注册插件。\n\n```js\nWebConsole.use(MyPlugin, {/*...*/})\n\nnew WebConsole(})\n```\n\n> 注意：插件必须在 WebConsole 实例化之前注册，否则不生效。\n\n## 插件生命周期\n\n目前插件支持如下生命周期方法，这些插件方法在`component`所赋值的组件中可用。\n\n| 生命周期方法                                        | 执行时机                     | 备注                          |\n| --------------------------------------------------- | ---------------------------- | ----------------------------- |\n| `onWebConsoleReady(hostProxy)`                      | 主面板首次渲染完成时         | 可访问 DOM 和配置，仅执行一次 |\n| `onWebConsoleShow(hostProxy)`                       | 主面板从不可见变为**可见**时 |                               |\n| `onWebConsoleHide(hostProxy)`                       | 主面板从可见变为**不可见**时 |\n| `onWebConsoleTabChanged(hostProxy, newTab, oldTab)` | 切换不同子面板时             |\n| `onWebConsoleSettingsLoaded(hostProxy, settings)`   | 偏好设置首次加载时           |\n| `onWebConsoleSettingsChanged(hostProxy, settings)`  | 偏好设置变化时               |\n\n使用示例：\n\n```js\nexport default function(WebConsole, options) {\n  return new WebConsole.Plugin({\n    id: \"myPlugin\",\n    name: \"我的插件\",\n    component: {\n      render(h) {\n        return h(\"h1\", null, \"这是我的第一个插件\");\n      },\n      methods: {\n        onWebConsoleShow() {\n          console.log(\"Show\");\n        },\n        onWebConsoleHide() {\n          console.log(\"Hide\");\n        }\n      }\n    }\n  });\n}\n```\n\n## 插件偏好设置\n\nweb-console 有一个集中的偏好设置界面（点击主面板右上角的齿轮进入），插件可以在其中添加自己的偏好设置项，添加方式是在创建插件时指定`settings`属性，该属性接受一个数组，数组每个元素是一个设置描述对象。\n\n支持的设置描述对象如下（还会支持更多）：\n\n```js\n// 复选框\n{\n  type: 'checkbox',\n  desc: '是否开启',\n  name: 'isOpen',\n  value: false\n}\n\n// 下拉选择框\n{\n  type: \"select\",\n  desc: \"选择颜色\",\n  name: 'color',\n  value: 'red',\n  options: [\n    { text: \"红色\", value: 'red' },\n    { text: \"绿色\", value: 'green' },\n    { text: \"蓝色\", value: 'blue' },\n  ]\n}\n```\n\n使用示例：\n\n```js\nexport default function(WebConsole, options) {\n  return new WebConsole.Plugin({\n    id: \"myPlugin\",\n    name: \"我的插件\",\n    component: {\n      render(h) {\n        return h(\"h1\", null, \"这是我的第一个插件\");\n      },\n      methods: {\n        onWebConsoleSettingsLoaded(hostProxy, settings) {\n          console.log(\"加载设置：\", settings);\n        },\n        onWebConsoleSettingsChanged(hostProxy, settings) {\n          console.log(\"设置变化：\", settings);\n        }\n      }\n    },\n    settings: [\n      {\n        type: \"checkbox\",\n        desc: \"是否选中\",\n        name: \"isChecked\",\n        value: false\n      }\n    ]\n  });\n}\n```\n\n运行截图：\n\n![](snapshot_plugin_settings1.png)\n\n![](snapshot_plugin_settings2.png)\n"
  },
  {
    "path": "docs/snapshot.md",
    "content": "# Element 面板\n\n![element](snapshot_element.png)\n\n![element](snapshot_element2.png)\n\n![element](snapshot_element3.png)\n\n# Console 面板\n\n![console](snapshot_console.png)\n\n# Network 面板\n\n![network](snapshot_network.png)\n\n# Application 面板\n\n![application](snapshot_application.png)\n"
  },
  {
    "path": "gulpfile.js",
    "content": "const pkgInfo = require(\"./package.json\");\nconst child_process = require(\"child_process\");\nconst process = require(\"process\");\nvar gulp = require(\"gulp\");\n\ngulp.task(\"default\", function(cb) {\n  // 将你的默认的任务代码放在这\n});\n\nlet version = \"prerelease\";\n\ngulp.task(\"version\", cb => {\n  const oldVersion = pkgInfo.version;\n  console.log(oldVersion);\n  console.log(process.argv, \";\", process.argv0);\n  child_process.exec(\"npm version \" + version, (err, stdout, stderr) => {\n    if (err) cb(err);\n    console.log(stdout);\n  });\n});\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  moduleFileExtensions: [\n    'js',\n    'jsx',\n    'json',\n    'vue'\n  ],\n  transform: {\n    '^.+\\\\.vue$': 'vue-jest',\n    '^.+\\\\.jsx?$': 'babel-jest'\n  },\n  moduleNameMapper: {\n    '^@/(.*)$': '<rootDir>/src/$1'\n  },\n  snapshotSerializers: [\n    'jest-serializer-vue'\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@whinc/web-console\",\n  \"version\": \"0.7.2\",\n  \"author\": \"whincwu@163.com\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"console\",\n    \"vConsole\",\n    \"web-console\",\n    \"webConsole\",\n    \"log\"\n  ],\n  \"repository\": \"https://github.com/whinc/web-console\",\n  \"main\": \"dist/web-console.umd.min.js\",\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build:lib\": \"vue-cli-service build --target lib --name web-console --dest dist src/main.js\",\n    \"depoly:lib\": \"npm run lint && npm run build:lib && cross-env NPM_TOKEN=$NPM_TOKEN semantic-release && npm run changelog\",\n    \"build:demo\": \"vue-cli-service build --target app --dest demo\",\n    \"depoly:demo\": \"npm run build:demo && npx gh-pages -d demo\",\n    \"lint\": \"vue-cli-service lint src/**/*.js\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s -r 0\",\n    \"report\": \"vue-cli-service build --report\",\n    \"test:unit\": \"vue-cli-service test:unit\",\n    \"test:api\": \"cd ./tests/api && nodemon index.js\",\n    \"e2e\": \"vue-cli-service e2e\"\n  },\n  \"dependencies\": {\n    \"cookie_js\": \"^1.2.2\",\n    \"highlight.js\": \"^9.14.2\",\n    \"lodash.clonedeep\": \"^4.5.0\",\n    \"mint-ui\": \"^2.2.13\",\n    \"specificity\": \"^0.4.1\",\n    \"url-search-params\": \"^1.1.0\",\n    \"vue\": \"^2.5.22\",\n    \"vue-infinite-scroll\": \"^2.0.2\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^7.5.0\",\n    \"@commitlint/config-conventional\": \"^7.5.0\",\n    \"@vue/cli-plugin-babel\": \"^3.4.1\",\n    \"@vue/cli-plugin-e2e-cypress\": \"^3.4.1\",\n    \"@vue/cli-plugin-eslint\": \"^3.4.1\",\n    \"@vue/cli-plugin-unit-jest\": \"^3.5.1\",\n    \"@vue/cli-service\": \"^3.4.1\",\n    \"@vue/test-utils\": \"^1.0.0-beta.28\",\n    \"babel-core\": \"^7.0.0-0\",\n    \"babel-jest\": \"^22.4.4\",\n    \"babel-plugin-component\": \"^1.1.1\",\n    \"conventional-changelog-angular\": \"^5.0.3\",\n    \"conventional-changelog-cli\": \"^2.0.12\",\n    \"cross-env\": \"^5.2.0\",\n    \"cz-conventional-changelog\": \"^2.1.0\",\n    \"gh-pages\": \"^1.2.0\",\n    \"gulp\": \"^4.0.0\",\n    \"husky\": \"^1.3.1\",\n    \"i\": \"^0.3.6\",\n    \"node-sass\": \"^4.11.0\",\n    \"nodemon\": \"^1.18.9\",\n    \"prettier\": \"1.14.2\",\n    \"pretty-quick\": \"^1.10.0\",\n    \"sass-loader\": \"^6.0.6\",\n    \"semantic-release\": \"^15.13.3\",\n    \"semantic-release-cli\": \"^4.1.1\",\n    \"vue-template-compiler\": \"^2.5.22\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ],\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"pretty-quick --staged\",\n      \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\"\n    }\n  },\n  \"prettier\": {\n    \"printWidth\": 120\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@commitlint/config-conventional\"\n    ]\n  },\n  \"release\": {\n    \"ci\": false,\n    \"plugins\": [\n      \"@semantic-release/commit-analyzer\",\n      \"@semantic-release/release-notes-generator\",\n      \"@semantic-release/npm\"\n    ]\n  }\n}\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<!-- <!DOCTYPE html> -->\n<html>\n  <head>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"shortcut icon\" href=\"<%= webpackConfig.output.publicPath %>favicon.ico\">\n    <title>web-console</title>\n    <style>\n      /* CSSImportRule */\n      @import url('style_import1.css');\n\n      /* CSSStyleRule */\n      #element {\n        color: red;\n        /* 样式缩写形式 */\n        font: 1em \"Fira Sans\", sans-serif;\n        font-size: 15px;\n        font-weight: bold;\n        /* 样式缩写形式 */\n        border: 1px dotted blue;\n        border-left: 1px solid red;\n        padding: 5px;\n        padding-left: 10px;\n        /* 不同形式的颜色写法 */\n        color: black;\n        color: rgb(0, 0, 0);\n        color: #000;\n        background-color: #000;\n        border-bottom-color: #000;\n        border-top-color: #000;\n        border-right-color: #000;\n        border-left-color: #000;\n        border-color:#F00;\n        caret-color: #000;\n        outline-color: #000;\n        text-decoration-color: #000;\n        background: #000;\n        color:inherit;\n        color: darkmagenta\n;\n      }\n      /* 测试分组选择器 */\n      .xyz, .zyx, #element, .other {\n        border: 1px solid red;\n      }\n      /* 测试宿主页和浏览器设置默认样式 */\n      div, span, p {\n        /* 会覆盖 conole 面板 %c 占位符的设置的继承样式 */\n        /* color: black; */\n      }\n\n      /* CSSKeyframesRule */\n      @keyframes out {\n        0% { }\n        100% { opacity: 0; }\n      }\n\n      /* parent element style */\n      #parent {\n        /* 可继承 */\n        color: gold;\n        /* 不可继承 */\n        border-right-width: 1px;\n      }\n\n      #parent2 {\n        color: brown;\n        /* 不可继承 */\n        border-right-width: 2px;\n      }\n\n      /* 测试盒模型 */\n      .box-model {\n        display: none;\n        margin: 99999999999999999999px;\n        border: 88888888888888888888px solid red;\n        padding: 77777777777777777777px;\n        width: 666px;\n        height: 666px;\n      }\n      .box-model2 {\n        display: none;\n        position: relative;\n        left: 10px;\n        top: 20px;\n        margin-left: 1px;\n        padding-left: 2px;\n        border-left: 3px;\n        width: -10px;\n        height: 0px;\n      }\n      .box-model3 {\n        display: none;\n        position: absolute;\n        left: 10px;\n        top: 20px;\n        width: 0px;\n        height: 10px;\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"style_link.css\">\n    <link rel=\"stylesheet\" href=\"./mint-ui.min.css\">\n    <style>\n      #app {\n        height: 100%;\n        overflow-y: scroll;\n      }\n\n      #app .mint-button {\n        width: 100%;\n        margin-bottom: 15px;\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"./HelloPlugin.css\">\n  </head>\n  <body>\n    <!-- 测试 element 面板 -->\n    <div id=\"parent\" class=\"a b\" style=\"color: red\">\n      <div id=\"parent2\" class=\"c d\" style=\"color: red\">\n        <div id=\"element\"\n          class=\"xyz zyx\"\n          style=\"display: none\"\n          data-a=\"a\"\n          >\n          <div> \n            <!-- 测试不同类型节点 -->\n              hello \n              world\n            <span>b</span>\n            <!-- 无内容标签测试 -->\n            <input>\n            <input/>\n            <input></input>\n          </div>\n          1\n          <div>\n            2\n            <div>\n              3\n              <div>\n                4\n                <div>\n                  5\n                  <div>\n                    6\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <!-- 测试盒模型 -->\n      <div class=\"box-model\"></div>\n      <div class=\"box-model2\"></div>\n      <div class=\"box-model3\"></div>\n    </div>\n\n    <!-- import Vue before Mint UI -->\n    <script src=\"./vue.js\"></script>\n    <div id=\"app\">\n      <h1>WebConsole</h1>\n      <div v-if=\"isDev\">\n        <h2>Console Test</h2>\n        <mt-button type=\"primary\" @click=\"$console.testLogLevel\">打印不同级别日志</mt-button>\n        <mt-button type=\"primary\" @click=\"$console.testLogParams()\">打印多参数</mt-button>\n        <mt-button type=\"primary\" @click=\"$console.testFormat()\">打印格式化</mt-button>\n        <mt-button type=\"primary\" @click=\"$console.testObject()\">打印对象</mt-button>\n        <mt-button type=\"primary\" @click=\"$console.testException()\">触发异常</mt-button>\n        <h2>Network Test</h2>\n        <mt-button type=\"primary\" @click=\"$network.testXMLHttpRequest()\">测试 XHR 请求</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testFetch()\">测试 fetch 请求</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testHTTPStatus()\">测试 HTTP 状态码</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testResponseData()\">测试响应数据</mt-button>\n        <h3>query string & body</h3>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('get')\">get</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('post:raw')\">post:raw</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('post:raw:text')\">post:raw:text</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('post:raw:json')\">post:raw:json</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('post:form-data')\">post:form-data</mt-button>\n        <mt-button type=\"primary\" @click=\"$network.testRequestParams('post:x-www-form-urlencoded')\">post:x-www-form-urlencoded</mt-button>\n\n        <h2>Application Test</h2>\n        <mt-button type=\"primary\" @click=\"$application.testWriteCookie(1000)\">测试 Cookie</mt-button>\n        <mt-button type=\"primary\" @click=\"$application.testWriteLocalStorage(1000)\">测试 LocalStorage</mt-button>\n        <mt-button type=\"primary\" @click=\"$application.testWriteSessionStorage(1000)\">测试 SessionStorage</mt-button>\n        <h2>Other</h2>\n        <mt-button type=\"primary\" @click=\"performanceTest(50)\">日志性能测试</mt-button>\n        <mt-button type=\"primary\" @click=\"pluginTest\">测试插件</mt-button>\n      </div>\n      <div v-else>\n        <mt-button type=\"primary\" @click=\"onClickLog('level_log')\">普通日志(log)</mt-button>\n        <mt-button type=\"primary\" @click=\"onClickLog('level_info')\">信息日志(info)</mt-button>\n        <mt-button type=\"primary\" @click=\"onClickLog('level_debug')\">调试日志(debug)</mt-button>\n        <mt-button type=\"primary\" @click=\"onClickLog('level_warn')\">警告日志(warn)</mt-button>\n        <mt-button type=\"danger\" @click=\"onClickLog('level_error')\">报错日志(error)</mt-button>\n\n        <h2></h2>\n        <mt-button type=\"primary\" @click=\"onClickLog('object')\">打印对象</mt-button>\n        <mt-button type=\"danger\" @click=\"onClickLog('error')\">打印错误</mt-button>\n\n        <h2></h2>\n        <mt-button type=\"primary\" @click=\"onClickRequest('xhr')\">发起 XMLHttpRequest 请求</mt-button>\n        <mt-button type=\"primary\" @click=\"onClickRequest('fetch')\">发起 fetch 请求</mt-button>\n      </div>\n      <p>\n        <mt-switch v-model='isDev'>切换</mt-switch>\n      </p>\n    </div>\n    <!-- import JavaScript -->\n    <script src=\"./mint-ui.min.js\"></script>\n    <script src=\"./test_console.js\"></script>\n    <script src=\"./test_network.js\"></script>\n    <script src=\"./test_application.js\"></script>\n    <script src=\"./test_plugin.js\"></script>\n    <script type=\"text/javascript\">\n      var isDev = !!localStorage.getItem('web-console:isDev')\n      new Vue({\n        el: \"#app\",\n        data: function() {\n          return {\n            isDev: isDev\n          };\n        },\n        computed: {\n          $network () {\n            return window.$network\n          },\n          $console () {\n            return window.$console\n          },\n          $application () {\n            return window.$application\n          }\n        },\n        mounted: function() {\n          this.launchWebConsole();\n        },\n        methods: {\n          pluginTest () {\n            localStorage.setItem('plugin', 'enable')\n            location.reload()\n          },\n          performanceTest (n) {\n            if (n <= 0) return\n            this.$console.testFormat()\n            // this.$network.testHTTPStatus()\n            var that = this\n            that.performanceTest(n - 1)\n            // setTimeout(function () {\n            //   that.performanceTest(n - 1)\n            // }, 10)\n          },\n          launchWebConsole: function() {\n            var self = this;\n            if (window.WebConsole) {\n              // 测试 web-console 加载完毕前的日志输出\n              // console.error(new Error('hello'))\n\n              // 测试日志数量\n              // new Array(11).fill(0).forEach((v, i) => console.log(i))\n              if (localStorage.getItem('plugin') === 'enable') {\n                window.$plugins.forEach(plugin => WebConsole.use(plugin))\n              }\n\n              window.webConsole = new WebConsole({\n                activeTab: isDev ? 'network' : 'console',\n                panelVisible: isDev ? true : false,\n                entryStyle: 'button'\n              });\n              if (isDev) {\n                // this.$network.testRequestParams()\n              }\n              // self.$network.testFetchApi()\n              // this.$console.testFormat()\n              // this.$network.testHTTPStatus()\n              // this.$network.testRequestParams()\n              // this.$network.testResponseData()\n              // this.$console.testIntervalLog()\n            } else {\n              setTimeout(function() {\n                self.launchWebConsole();\n              }, 200);\n            }\n          },\n          onClickLog: function(type) {\n            switch (type) {\n              case \"level_log\":\n                console.log(\"log\");\n                break;\n              case \"level_info\":\n                console.info(\"info\");\n                break;\n              case \"level_debug\":\n                console.debug(\"debug\");\n                break;\n              case \"level_warn\":\n                console.warn(\"warn\");\n                break;\n              case \"level_error\":\n                console.error(\"error\");\n                break;\n              case \"object\":\n                console.log({a: 100, b: [1, 2], c: true});\n                break;\n              case \"error\":\n                console.error(new Error('你触发了一个错误'));\n                break;\n            }\n            this.$toast({message: '已打印，请打开 Console 面板查看'})\n          },\n          onClickRequest(type) {\n            var nodeDocsApi = \"https://nodejs.org/dist/latest-v8.x/docs/api/index.json\";\n            if (type === 'xhr') {\n              var xhr = new window.XMLHttpRequest();\n              xhr.open(\"GET\", nodeDocsApi);\n              xhr.send();\n              this.$toast({message: '已发送，请打开 Network 面板查看'})\n            } else if (type === 'fetch') {\n              if (typeof window.fetch === 'function') {\n                window.fetch(nodeDocsApi)\n                this.$toast({message: '已发送，请打开 Network 面板查看'})\n              } else {\n                this.$toast({message: '您的浏览器不支持 fetch 请求'})\n              }\n            } else {\n\n            }\n          }\n        }\n      });\n\n      function ajax(options) {\n        options = options || {};\n        var url = options.url;\n        var method = options.method || \"GET\";\n\n        var xhr = new window.XMLHttpRequest();\n        xhr.onreadystatechange = function() {\n          console.log(\"readyState:\", this.readyState);\n        };\n        xhr.open(method, url);\n        xhr.send();\n      }\n\n      // 测试log格式化输出\n      function testLogFormat(value, format) {\n        console.log(\"console.log(%\" + format + \", %s)\", JSON.stringify(value));\n        console.log(format, value);\n      }\n    </script>\n    <!-- <script src=\"https://unpkg.com/@whinc/web-console\"></script> -->\n    <!-- <script src=\"http://res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/vconsole/3.0.0/vconsole.min.js\"></script> -->\n    <script>\n      if (window.VConsole) {\n        window._vConsole = new window.VConsole();\n      }\n    </script>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "public/index_cdn.html",
    "content": "<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>测试CDN下载</title>\n</head>\n<body>\n  <script src=\"https://unpkg.com/vue\"></script>\n  <script src=\"https://unpkg.com/@whinc/web-console\"></script>\n  <script>\n    new WebConsole()\n  </script>\n</body>\n</html>"
  },
  {
    "path": "public/style_import1.css",
    "content": "@import url(\"style_import2.css\");\n\n/* 测试元素审查 */\n#element {\n  border-color: green;\n}\n.xyz {\n  border: 2px dashed blue;\n  padding: 2px;\n}\n"
  },
  {
    "path": "public/style_import2.css",
    "content": "/* 测试元素审查 */\n#element {\n  border-color: yellow;\n}\n.xyz {\n  border: 3px dotted yellow;\n  padding: 3px;\n}\n"
  },
  {
    "path": "public/style_link.css",
    "content": "/* 测试元素审查 */\n#element {\n  background-color: white;\n}\n.xyz {\n  border: 1px solid red;\n  padding: 1px;\n}\n"
  },
  {
    "path": "public/test_application.js",
    "content": "window.$application = (function() {\n  function testWriteCookie(n = 1) {\n    for (var i = 0; i < n; ++i) {\n      const obj = { timestamp: Date.now() };\n      document.cookie = i + \"=\" + JSON.stringify(obj);\n    }\n  }\n\n  function testWriteLocalStorage(n = 1) {\n    for (var i = 0; i < n; ++i) {\n      const obj = { timestamp: Date.now() };\n      window.localStorage.setItem(i, JSON.stringify(obj));\n    }\n  }\n\n  function testWriteSessionStorage(n = 1) {\n    for (var i = 0; i < n; ++i) {\n      const obj = { timestamp: Date.now() };\n      window.sessionStorage.setItem(i, JSON.stringify(obj));\n    }\n  }\n\n  return {\n    testWriteCookie,\n    testWriteSessionStorage,\n    testWriteLocalStorage\n  };\n})();\n"
  },
  {
    "path": "public/test_console.js",
    "content": "window.$console = (function() {\n  function testLogLevel() {\n    console.log(\"log\");\n    console.info(\"info\");\n    console.debug(\"debug\");\n    console.warn(\"warn\");\n    console.error(\"error\");\n  }\n\n  function testLogParams() {\n    console.log(100, true, undefined, null, { a: 100 });\n  }\n\n  // 测试log格式化输出\n  function testLogFormat(value, format) {\n    console.log(\"console.log(%\" + format + \", %s)\", JSON.stringify(value));\n    console.log(format, value);\n  }\n\n  function testIntervalLog(interval = 1000) {\n    console.log(Date.now());\n    setTimeout(testIntervalLog.bind(null, arguments), interval);\n  }\n\n  function testException() {\n    // 测试直接输出 Error 对象\n    console.error(new ReferenceError(\"reference error\"));\n\n    // 测试未捕获的全局异常\n    setTimeout(function() {\n      throw new Error(\"This is a uncaught exception\");\n    });\n\n    // 测试未处理的 rejected Promise\n    Promise.reject(\"uncaught rejected promise\");\n  }\n\n  // 打印对象\n  function testObject() {\n    let obj2 = {\n      // 测试数值类型\n      a: 1,\n      a5: { a: 1, b: 2, c: { a: 1, b: 2 } },\n      // 测试数组\n      a3: [1, 2],\n      a4: [1, 2, [1, 2, [1, 2, 3], 3], 3],\n      // 测试属性值很长时的展示\n      a1: \"10101010101010101010101010101010101010101010101001010101\",\n      a2: 10101010101010101010110101010101010110101011010101011010101,\n      // 测试字符串类型\n      b: \"b\",\n      // 测试转义字符展示\n      b1: '\"b1\"',\n      c: true,\n      d: undefined,\n      e: null,\n      f: Symbol(),\n      g: {\n        a: 1,\n        f1: () => {\n          return 1;\n        },\n        f2: function() {\n          return 2;\n        },\n        b: \"b\",\n        c: {\n          a: 1,\n          b: \"b\"\n        },\n        d: undefined,\n        e: null\n      },\n      [Symbol()]: 10,\n      [Symbol(\"age\")]: 28,\n      f1: function() {},\n      f2: () => {},\n      f3: function say() {},\n      Z: \"Z\",\n      M: \"m\",\n      z: \"z\",\n      // 测试排序规则\n      __a: \"public __a\",\n      _a: \"public _a\",\n      $a: \"public $a\"\n    };\n    Object.defineProperties(obj2, {\n      // value\n      h: {\n        enumerable: true,\n        configurable: true,\n        value: 10,\n        writable: false\n      },\n      // value\n      i: {\n        enumerable: false,\n        configurable: false,\n        value: 10,\n        writable: true\n      },\n      // getter and setter\n      j: {\n        get: () => {\n          return Math.random() + \"\";\n        },\n        set: v => {}\n      },\n      J: {\n        get: () => {\n          return \"\" + Math.random();\n        },\n        set: v => {}\n      },\n      // only getter\n      k: {\n        get: function() {\n          return {\n            a: 1,\n            b: \"b\"\n          };\n        }\n      },\n      K: {\n        get: function() {\n          return [\n            {\n              a: 1,\n              b: \"b\"\n            }\n          ];\n        }\n      },\n      // only setter\n      l: {\n        set: function(v) {}\n      },\n      [Symbol(\"a\")]: {\n        value: \"1\",\n        enumerable: false\n      },\n      // 测试属性排序规则\n      __b: {\n        value: \"private __b\",\n        enumerable: false\n      },\n      _b: {\n        value: \"private _b\",\n        enumerable: false\n      },\n      $b: {\n        value: \"private $b\",\n        enumerable: false\n      }\n    });\n    Object.defineProperties(obj2.g, {\n      h: {\n        enumerable: true,\n        configurable: true,\n        value: 10\n      },\n      i: {\n        enumerable: false,\n        configurable: false,\n        value: 10\n      },\n      j: {\n        get: () => {\n          return Math.random();\n        },\n        set: v => {}\n      },\n      k: {\n        get: function() {\n          return Math.random();\n        },\n        set: function() {}\n      }\n    });\n    Object.defineProperties(obj2.g.c, {\n      c: {\n        enumerable: false,\n        configurable: false,\n        value: 10\n      },\n      d: {\n        get: function() {\n          return Math.random();\n        },\n        set: function() {}\n      }\n    });\n    console.log(\"对象全属性:\", obj2);\n    console.log(\"三种函数:\", {\n      f1: function() {},\n      f2: () => {},\n      f3: function say() {}\n    });\n    var arr = [];\n    arr[\"10\"] = \"10\";\n    arr[\"29\"] = \"29\";\n    arr[\" \"] = \" \"; // 0x20\n    arr[\"  \"] = \"  \";\n    arr[\" a\"] = \" a\";\n    arr[\"  a\"] = \"  a\"; // 空白符处理\n    arr[\"!\"] = \"!\"; // 0x21\n    arr[\"0\"] = \"0\"; // 0x30\n    arr[\"1\"] = \"1\"; // 0x31\n    arr[\"2\"] = \"2\"; // 0x31\n    arr[\":\"] = \":\"; // 0x3A\n    arr[\"A\"] = \"A\"; // 0x41\n    arr[\"B\"] = \"B\"; // 0x42\n    arr[\"[\"] = \"[\"; // 0x5B\n    arr[\"a\"] = \"a\"; // 0x61\n    arr[\"b\"] = \"b\"; // 0x62\n    arr[\"{\"] = \"{\"; // 0x7B\n    console.log(\"数组 key 排序:\", arr);\n\n    const thumbObj = {\n      extra: `{\"num_total\":300,\"num_left\":157,\"discount\":\"{\\\"create_time\\\":1531970051000,\\\"discount_data\\\":\\\"[{\\\\\\\"amount_min\\\\\\\":\\\\\\\"100\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"10\\\\\\\"},{\\\\\\\"amount_min\\\\\\\":\\\\\\\"200\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"25\\\\\\\"},{\\\\\\\"amount_min\\\\\\\":\\\\\\\"300\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"50\\\\\\\"}]\\\",\\\"discount_id\\\":3,\\\"discount_name\\\":\\\"满减-yanger\\\",\\\"discount_prod\\\":\\\"[\\\\\\\"WM-180101001\\\\\\\",\\\\\\\"WM-180101002\\\\\\\",\\\\\\\"WM-180101004\\\\\\\",\\\\\\\"WM-180101006\\\\\\\",\\\\\\\"WM-180101008\\\\\\\",\\\\\\\"WM-180525102\\\\\\\",\\\\\\\"WM-180101017\\\\\\\"]\\\",\\\"discount_provider\\\":\\\"0\\\",\\\"discount_status\\\":\\\"1\\\",\\\"discount_type\\\":\\\"1\\\",\\\"update_time\\\":1540458898000}\"}`,\n      isChange: false\n    };\n    const thumbObj2 = [\n      `{\"num_total\":300,\"num_left\":157,\"discount\":\"{\\\"create_time\\\":1531970051000,\\\"discount_data\\\":\\\"[{\\\\\\\"amount_min\\\\\\\":\\\\\\\"100\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"10\\\\\\\"},{\\\\\\\"amount_min\\\\\\\":\\\\\\\"200\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"25\\\\\\\"},{\\\\\\\"amount_min\\\\\\\":\\\\\\\"300\\\\\\\",\\\\\\\"amount_cut\\\\\\\":\\\\\\\"50\\\\\\\"}]\\\",\\\"discount_id\\\":3,\\\"discount_name\\\":\\\"满减-yanger\\\",\\\"discount_prod\\\":\\\"[\\\\\\\"WM-180101001\\\\\\\",\\\\\\\"WM-180101002\\\\\\\",\\\\\\\"WM-180101004\\\\\\\",\\\\\\\"WM-180101006\\\\\\\",\\\\\\\"WM-180101008\\\\\\\",\\\\\\\"WM-180525102\\\\\\\",\\\\\\\"WM-180101017\\\\\\\"]\\\",\\\"discount_provider\\\":\\\"0\\\",\\\"discount_status\\\":\\\"1\\\",\\\"discount_type\\\":\\\"1\\\",\\\"update_time\\\":1540458898000}\"}`\n    ];\n    thumbObj2.push(thumbObj);\n    console.log(\"测试对象缩略信息：\", thumbObj, thumbObj2);\n  }\n\n  // 格式化输出\n  function testFormat() {\n    var numInt = 1;\n    var numFloat = 1.23;\n    var str = \"hello\";\n    var obj = {\n      a: 1,\n      b: 2,\n      c: {\n        d: 3\n      }\n    };\n    var arr = [1, 2, [3, obj]];\n    // 测试占位符的支持类型\n    testLogFormat(numInt, \"%i\");\n    testLogFormat(numFloat, \"%i\");\n    testLogFormat(numInt, \"%d\");\n    testLogFormat(numFloat, \"%d\");\n    testLogFormat(numInt, \"%f\");\n    testLogFormat(numFloat, \"%f\");\n    testLogFormat(obj, \"%s\");\n    testLogFormat(obj, \"%o\");\n    testLogFormat(arr, \"%o\");\n    testLogFormat(obj, \"%O\");\n    testLogFormat(arr, \"%O\");\n    // 测试占位符缺少对应的参数\n    console.log('console.log(\"-%s%i%d%f%o%O%c-\")');\n    console.log(\"-%s%i%d%f%o%O%c-\");\n    /* 测试占位符之间的空白符 */\n    // 期望输出 \"a1b2c 3\"\n    console.log(\"a%sb%sc\", 1, 2, 3);\n    // 测试参数多余占位符\n    console.log('console.log(\"-%s-\", 99, {a: \"1\"})');\n    console.log(\"-%s-\", 99, { a: 1 });\n    // 测试占位符和参数的高亮\n    console.log(\"console.log('%s-%s-%s-%s-%s-%s-%s', 100, true, null, undefined, '100', {}, [])\");\n    console.log(\"%s-%s-%s-%s-%s-%s-%s\", 100, true, null, undefined, \"100\", {}, []);\n    // 测试占位符 %c 的展示\n    console.log(\n      `%c111 222 %c 333 %c %c %O %o %i %f %s %%`,\n      \"color: white; background-color: rgba(0, 116, 217, 0.69); padding: 2px 5px; border-radius: 2px\",\n      \"color: #0074D9\",\n      \"\",\n      \"color: white; background-color: rgba(255, 65, 54, 0.69); padding: 2px 5px; margin-right: 5px; border-radius: 2px\",\n      { a: 444 },\n      { b: 555 },\n      666,\n      777,\n      \"888\"\n    );\n  }\n\n  return {\n    testLogLevel: testLogLevel,\n    testLogParams: testLogParams,\n    testFormat: testFormat,\n    testException: testException,\n    testIntervalLog: testIntervalLog,\n    testObject: testObject\n  };\n})();\n"
  },
  {
    "path": "public/test_network.js",
    "content": "window.$network = (function() {\n  var baseURL = \"http://localhost:8090\";\n  var nodeApi = \"https://nodejs.org/dist/latest-v8.x/docs/api/index.json\";\n\n  function testXMLHttpRequest() {}\n\n  function testFetch() {\n    if (typeof window.fetch !== \"function\") {\n      console.warn(\"current browser version don't support fetch api\");\n      return;\n    }\n\n    var url = baseURL + \"/get_status/\" + 200;\n\n    // 不带选项\n    var options = {\n      method: \"GET\",\n      headers: {\n        \"Content-Type\": \"text/plain\"\n      }\n    };\n    fetch(url);\n    fetch(url, options);\n\n    fetch(new Request(url));\n    fetch(new Request(url, options));\n\n    fetch(new Request(url, options), {\n      headers: {\n        \"Content-Type\": \"text/html\"\n      }\n    });\n  }\n\n  // 测试 HTTP 状态码\n  function testHTTPStatus() {\n    request({ url: nodeApi });\n    [100, 200, 300, 400, 500].forEach(function(status) {\n      request({ url: baseURL + \"/get_status/\" + status });\n    });\n  }\n\n  /**\n   * 测试请求参数\n   */\n  function testRequestParams(type = \"get\") {\n    var email = \"xx@yy.com\";\n    var password = \"zz\";\n\n    switch (type) {\n      case \"get\":\n        // GET\n        request({ url: baseURL + \"/get?email=\" + email + \"&password=\" + password + \"&a=&b\" });\n        break;\n      case \"post:raw\":\n        request({\n          url: baseURL + \"/post\",\n          method: \"POST\",\n          data: \"hello\"\n        });\n        break;\n      case \"post:raw:text\":\n        request({\n          url: baseURL + \"/post\",\n          method: \"POST\",\n          requestHeaders: {\n            \"Content-Type\": \"text/plain\"\n          },\n          data: \"hello\"\n        });\n        break;\n      case \"post:raw:json\":\n        request({\n          url: baseURL + \"/post\",\n          method: \"POST\",\n          requestHeaders: {\n            \"Content-Type\": \"application/json;charset=UTF-8\"\n          },\n          data: '{\"email\": aa}'\n        });\n        break;\n      case \"post:x-www-form-urlencoded\":\n        request({\n          url: baseURL + \"/post\",\n          method: \"POST\",\n          requestHeaders: {\n            \"Content-Type\": \"application/x-www-form-urlencoded\"\n          },\n          data: \"email=\" + encodeURIComponent(email) + \"&password=\" + encodeURIComponent(password)\n        });\n        break;\n      case \"post:form-data\":\n        var formData = new FormData();\n        formData.append(\"email\", email);\n        formData.append(\"password\", password);\n\n        request({\n          url: baseURL + \"/post\",\n          method: \"POST\",\n          data: formData\n        });\n        break;\n    }\n  }\n\n  // 测试响应数据类型\n  // 参考 http://devdocs.io/http/basics_of_http/mime_types\n  function testResponseData() {\n    const mimeTypeList = [\n      \"application/json\",\n      \"application/javascript\",\n      \"text/css\",\n      \"text/plain\",\n      \"text/html\",\n      \"image/jpeg\",\n      \"image/png\",\n      \"image/gif\",\n      \"image/svg+xml\"\n    ];\n\n    mimeTypeList.forEach(mimeType => {\n      request({ url: baseURL + \"/get_data/?mime_type=\" + encodeURIComponent(mimeType) });\n    });\n  }\n\n  return {\n    testHTTPStatus: testHTTPStatus,\n    testResponseData: testResponseData,\n    testRequestParams: testRequestParams,\n    testFetch: testFetch,\n    testXMLHttpRequest: testXMLHttpRequest\n  };\n})();\n\nfunction request(options) {\n  options = options || {};\n  var url = options.url;\n  var method = options.method || \"GET\";\n  var data = options.data || null;\n  var requestHeaders = options.requestHeaders || {};\n\n  /* XMLHttpRequest */\n  var xhr = new window.XMLHttpRequest();\n  xhr.onreadystatechange = function() {\n    // console.log(\"readyState:\", this.readyState);\n    if (this.readyState === XMLHttpRequest.DONE) {\n      // console.log(this.getResponseHeader(\"content-type\"));\n      // console.log(this.response);\n    }\n  };\n  xhr.open(method, url);\n  Object.keys(requestHeaders).forEach(key => {\n    xhr.setRequestHeader(key, requestHeaders[key]);\n  });\n  xhr.send(data);\n\n  /* fetch */\n  if (typeof window.fetch === \"function\") {\n    fetch(url, {\n      method: method,\n      body: data,\n      headers: requestHeaders\n    });\n  } else {\n    console.warn('current brwoser don\\'t support \"fetch\"');\n  }\n}\n"
  },
  {
    "path": "public/test_plugin.js",
    "content": "function plugin1(WebConsole) {\n  return new WebConsole.Plugin({\n    id: \"plugin1\",\n    name: \"插件1\",\n    component: function() {\n      var plugin = this;\n      var tag = plugin.toString();\n      return {\n        data: function() {\n          return {\n            text: \"plugin1 panel\",\n            activeTab: \"A\"\n          };\n        },\n        methods: {\n          onWebConsoleReady: function(hostProxy) {\n            this.hostProxy = hostProxy;\n            console.log(tag, \"onWebConsoleReady\");\n          },\n          onWebConsoleShow: function(hostProxy) {\n            console.log(tag, \"onWebConsoleShow\");\n          },\n          onWebConsoleHide: function(hostProxy) {\n            console.log(tag, \"onWebConsoleHide\");\n          },\n          onWebConsoleTabChanged: function(hostProxy, newVal, oldVal) {\n            console.log(tag, \"onWebConsoleTabChanged\", newVal);\n          },\n          onWebConsoleSettingsLoaded: function(hostProxy, settings) {\n            console.log(tag, \"onWebConsoleSettingsLoaded\", settings);\n          },\n          onWebConsoleSettingsChanged: function(hostProxy, settings) {\n            console.log(tag, \"onWebConsoleSettingsChanged\", settings);\n          },\n          hidePanel: function() {\n            this.hostProxy.hidePanel();\n          },\n          printSettings: function() {\n            console.log(\"settings:\", this.hostProxy.getSettings());\n          }\n        },\n        render: function(h) {\n          var vm = this;\n          return h(\n            \"div\",\n            {\n              style: {\n                display: \"flex\",\n                \"flex-direction\": \"column\"\n              }\n            },\n            [\n              h(\n                \"div\",\n                {\n                  style: {\n                    flex: \"1\"\n                  }\n                },\n                [\n                  h(\n                    \"button\",\n                    {\n                      on: {\n                        click: this.hidePanel\n                      }\n                    },\n                    \"hide panel\"\n                  ),\n                  h(\n                    \"button\",\n                    {\n                      style: {\n                        \"margin-left\": \"10px\"\n                      },\n                      on: {\n                        click: this.printSettings\n                      }\n                    },\n                    \"print settings\"\n                  ),\n                  h(\n                    \"div\",\n                    {\n                      style: {\n                        \"margin-top\": \"20px\",\n                        display: \"flex\",\n                        \"flex-direction\": \"row\"\n                      }\n                    },\n                    [\"setting\", \"close\", \"refresh\", \"ban\", \"edit\", \"save\", \"add\", \"cancel\", \"expand\", \"collapse\"].map(\n                      name =>\n                        h(\"VIcon\", {\n                          style: {\n                            \"margin-left\": \"10px\",\n                            width: \"20px\",\n                            height: \"20px\"\n                          },\n                          props: {\n                            name: name\n                          }\n                        })\n                    )\n                  )\n                ]\n              ),\n              h(\"VJSONViewer\", {\n                props: {\n                  value: { a: 1, b: \"2\", c: { a: 1, b: \"2\" } }\n                }\n              }),\n              h(\"VHighlightView\", {\n                props: {\n                  code: \"var a = 1;\\nvar b 2;\\nconsole.log('a + b =', a + b)\",\n                  language: \"javascript\"\n                }\n              }),\n              h(\n                \"VTabBar\",\n                {\n                  props: {\n                    value: vm.activeTab,\n                    equalWidth: true\n                  },\n                  on: {\n                    input: function(id) {\n                      vm.activeTab = id;\n                    }\n                  }\n                },\n                [\n                  h(\"VTabBarItem\", { props: { id: \"A\" } }, \"Tab1\"),\n                  h(\"VTabBarItem\", { props: { id: \"B\" } }, \"Tab2\"),\n                  h(\"VTabBarItem\", { props: { id: \"C\" } }, \"Tab3\"),\n                  h(\"VTabBarItem\", { props: { id: \"D\" } }, \"Tab4\")\n                ]\n              ),\n              h(\"v-foot-bar\", {\n                props: {\n                  buttons: [\n                    {\n                      text: \"Hide\",\n                      click: function() {\n                        vm.hostProxy.hidePanel();\n                      }\n                    }\n                  ]\n                }\n              }) // v-foot-bar\n            ]\n          ); // div\n        }\n      };\n    },\n    settings: [\n      {\n        type: \"checkbox\",\n        name: \"plugin0\",\n        value: false,\n        desc: \"test plugin0\"\n      }\n    ]\n  });\n}\n\nfunction plugin2(WebConsole) {\n  return new WebConsole.Plugin({\n    id: \"plugin2\",\n    name: \"插件2\",\n    component: {\n      data: function() {\n        return {\n          text: \"plugin2 panel\"\n        };\n      },\n      render: function(h) {\n        return h(\"div\", {}, this.text);\n      }\n    },\n    settings: function() {\n      return [\n        {\n          type: \"checkbox\",\n          name: this.id,\n          value: false,\n          desc: \"test \" + this.id\n        }\n      ];\n    }\n  });\n}\n\nwindow.$plugins = [plugin1, plugin2];\n"
  },
  {
    "path": "public/vue.js",
    "content": "/*!\n * Vue.js v2.5.17\n * (c) 2014-2018 Evan You\n * Released under the MIT License.\n */\n(function(global, factory) {\n  typeof exports === \"object\" && typeof module !== \"undefined\"\n    ? (module.exports = factory())\n    : typeof define === \"function\" && define.amd\n      ? define(factory)\n      : (global.Vue = factory());\n})(this, function() {\n  \"use strict\";\n\n  /*  */\n\n  var emptyObject = Object.freeze({});\n\n  // these helpers produces better vm code in JS engines due to their\n  // explicitness and function inlining\n  function isUndef(v) {\n    return v === undefined || v === null;\n  }\n\n  function isDef(v) {\n    return v !== undefined && v !== null;\n  }\n\n  function isTrue(v) {\n    return v === true;\n  }\n\n  function isFalse(v) {\n    return v === false;\n  }\n\n  /**\n   * Check if value is primitive\n   */\n  function isPrimitive(value) {\n    return (\n      typeof value === \"string\" ||\n      typeof value === \"number\" ||\n      // $flow-disable-line\n      typeof value === \"symbol\" ||\n      typeof value === \"boolean\"\n    );\n  }\n\n  /**\n   * Quick object check - this is primarily used to tell\n   * Objects from primitive values when we know the value\n   * is a JSON-compliant type.\n   */\n  function isObject(obj) {\n    return obj !== null && typeof obj === \"object\";\n  }\n\n  /**\n   * Get the raw type string of a value e.g. [object Object]\n   */\n  var _toString = Object.prototype.toString;\n\n  function toRawType(value) {\n    return _toString.call(value).slice(8, -1);\n  }\n\n  /**\n   * Strict object type check. Only returns true\n   * for plain JavaScript objects.\n   */\n  function isPlainObject(obj) {\n    return _toString.call(obj) === \"[object Object]\";\n  }\n\n  function isRegExp(v) {\n    return _toString.call(v) === \"[object RegExp]\";\n  }\n\n  /**\n   * Check if val is a valid array index.\n   */\n  function isValidArrayIndex(val) {\n    var n = parseFloat(String(val));\n    return n >= 0 && Math.floor(n) === n && isFinite(val);\n  }\n\n  /**\n   * Convert a value to a string that is actually rendered.\n   */\n  function toString(val) {\n    return val == null ? \"\" : typeof val === \"object\" ? JSON.stringify(val, null, 2) : String(val);\n  }\n\n  /**\n   * Convert a input value to a number for persistence.\n   * If the conversion fails, return original string.\n   */\n  function toNumber(val) {\n    var n = parseFloat(val);\n    return isNaN(n) ? val : n;\n  }\n\n  /**\n   * Make a map and return a function for checking if a key\n   * is in that map.\n   */\n  function makeMap(str, expectsLowerCase) {\n    var map = Object.create(null);\n    var list = str.split(\",\");\n    for (var i = 0; i < list.length; i++) {\n      map[list[i]] = true;\n    }\n    return expectsLowerCase\n      ? function(val) {\n          return map[val.toLowerCase()];\n        }\n      : function(val) {\n          return map[val];\n        };\n  }\n\n  /**\n   * Check if a tag is a built-in tag.\n   */\n  var isBuiltInTag = makeMap(\"slot,component\", true);\n\n  /**\n   * Check if a attribute is a reserved attribute.\n   */\n  var isReservedAttribute = makeMap(\"key,ref,slot,slot-scope,is\");\n\n  /**\n   * Remove an item from an array\n   */\n  function remove(arr, item) {\n    if (arr.length) {\n      var index = arr.indexOf(item);\n      if (index > -1) {\n        return arr.splice(index, 1);\n      }\n    }\n  }\n\n  /**\n   * Check whether the object has the property.\n   */\n  var hasOwnProperty = Object.prototype.hasOwnProperty;\n  function hasOwn(obj, key) {\n    return hasOwnProperty.call(obj, key);\n  }\n\n  /**\n   * Create a cached version of a pure function.\n   */\n  function cached(fn) {\n    var cache = Object.create(null);\n    return function cachedFn(str) {\n      var hit = cache[str];\n      return hit || (cache[str] = fn(str));\n    };\n  }\n\n  /**\n   * Camelize a hyphen-delimited string.\n   */\n  var camelizeRE = /-(\\w)/g;\n  var camelize = cached(function(str) {\n    return str.replace(camelizeRE, function(_, c) {\n      return c ? c.toUpperCase() : \"\";\n    });\n  });\n\n  /**\n   * Capitalize a string.\n   */\n  var capitalize = cached(function(str) {\n    return str.charAt(0).toUpperCase() + str.slice(1);\n  });\n\n  /**\n   * Hyphenate a camelCase string.\n   */\n  var hyphenateRE = /\\B([A-Z])/g;\n  var hyphenate = cached(function(str) {\n    return str.replace(hyphenateRE, \"-$1\").toLowerCase();\n  });\n\n  /**\n   * Simple bind polyfill for environments that do not support it... e.g.\n   * PhantomJS 1.x. Technically we don't need this anymore since native bind is\n   * now more performant in most browsers, but removing it would be breaking for\n   * code that was able to run in PhantomJS 1.x, so this must be kept for\n   * backwards compatibility.\n   */\n\n  /* istanbul ignore next */\n  function polyfillBind(fn, ctx) {\n    function boundFn(a) {\n      var l = arguments.length;\n      return l ? (l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a)) : fn.call(ctx);\n    }\n\n    boundFn._length = fn.length;\n    return boundFn;\n  }\n\n  function nativeBind(fn, ctx) {\n    return fn.bind(ctx);\n  }\n\n  var bind = Function.prototype.bind ? nativeBind : polyfillBind;\n\n  /**\n   * Convert an Array-like object to a real Array.\n   */\n  function toArray(list, start) {\n    start = start || 0;\n    var i = list.length - start;\n    var ret = new Array(i);\n    while (i--) {\n      ret[i] = list[i + start];\n    }\n    return ret;\n  }\n\n  /**\n   * Mix properties into target object.\n   */\n  function extend(to, _from) {\n    for (var key in _from) {\n      to[key] = _from[key];\n    }\n    return to;\n  }\n\n  /**\n   * Merge an Array of Objects into a single Object.\n   */\n  function toObject(arr) {\n    var res = {};\n    for (var i = 0; i < arr.length; i++) {\n      if (arr[i]) {\n        extend(res, arr[i]);\n      }\n    }\n    return res;\n  }\n\n  /**\n   * Perform no operation.\n   * Stubbing args to make Flow happy without leaving useless transpiled code\n   * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)\n   */\n  function noop(a, b, c) {}\n\n  /**\n   * Always return false.\n   */\n  var no = function(a, b, c) {\n    return false;\n  };\n\n  /**\n   * Return same value\n   */\n  var identity = function(_) {\n    return _;\n  };\n\n  /**\n   * Generate a static keys string from compiler modules.\n   */\n  function genStaticKeys(modules) {\n    return modules\n      .reduce(function(keys, m) {\n        return keys.concat(m.staticKeys || []);\n      }, [])\n      .join(\",\");\n  }\n\n  /**\n   * Check if two values are loosely equal - that is,\n   * if they are plain objects, do they have the same shape?\n   */\n  function looseEqual(a, b) {\n    if (a === b) {\n      return true;\n    }\n    var isObjectA = isObject(a);\n    var isObjectB = isObject(b);\n    if (isObjectA && isObjectB) {\n      try {\n        var isArrayA = Array.isArray(a);\n        var isArrayB = Array.isArray(b);\n        if (isArrayA && isArrayB) {\n          return (\n            a.length === b.length &&\n            a.every(function(e, i) {\n              return looseEqual(e, b[i]);\n            })\n          );\n        } else if (!isArrayA && !isArrayB) {\n          var keysA = Object.keys(a);\n          var keysB = Object.keys(b);\n          return (\n            keysA.length === keysB.length &&\n            keysA.every(function(key) {\n              return looseEqual(a[key], b[key]);\n            })\n          );\n        } else {\n          /* istanbul ignore next */\n          return false;\n        }\n      } catch (e) {\n        /* istanbul ignore next */\n        return false;\n      }\n    } else if (!isObjectA && !isObjectB) {\n      return String(a) === String(b);\n    } else {\n      return false;\n    }\n  }\n\n  function looseIndexOf(arr, val) {\n    for (var i = 0; i < arr.length; i++) {\n      if (looseEqual(arr[i], val)) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  /**\n   * Ensure a function is called only once.\n   */\n  function once(fn) {\n    var called = false;\n    return function() {\n      if (!called) {\n        called = true;\n        fn.apply(this, arguments);\n      }\n    };\n  }\n\n  var SSR_ATTR = \"data-server-rendered\";\n\n  var ASSET_TYPES = [\"component\", \"directive\", \"filter\"];\n\n  var LIFECYCLE_HOOKS = [\n    \"beforeCreate\",\n    \"created\",\n    \"beforeMount\",\n    \"mounted\",\n    \"beforeUpdate\",\n    \"updated\",\n    \"beforeDestroy\",\n    \"destroyed\",\n    \"activated\",\n    \"deactivated\",\n    \"errorCaptured\"\n  ];\n\n  /*  */\n\n  var config = {\n    /**\n     * Option merge strategies (used in core/util/options)\n     */\n    // $flow-disable-line\n    optionMergeStrategies: Object.create(null),\n\n    /**\n     * Whether to suppress warnings.\n     */\n    silent: false,\n\n    /**\n     * Show production mode tip message on boot?\n     */\n    productionTip: \"development\" !== \"production\",\n\n    /**\n     * Whether to enable devtools\n     */\n    devtools: \"development\" !== \"production\",\n\n    /**\n     * Whether to record perf\n     */\n    performance: false,\n\n    /**\n     * Error handler for watcher errors\n     */\n    errorHandler: null,\n\n    /**\n     * Warn handler for watcher warns\n     */\n    warnHandler: null,\n\n    /**\n     * Ignore certain custom elements\n     */\n    ignoredElements: [],\n\n    /**\n     * Custom user key aliases for v-on\n     */\n    // $flow-disable-line\n    keyCodes: Object.create(null),\n\n    /**\n     * Check if a tag is reserved so that it cannot be registered as a\n     * component. This is platform-dependent and may be overwritten.\n     */\n    isReservedTag: no,\n\n    /**\n     * Check if an attribute is reserved so that it cannot be used as a component\n     * prop. This is platform-dependent and may be overwritten.\n     */\n    isReservedAttr: no,\n\n    /**\n     * Check if a tag is an unknown element.\n     * Platform-dependent.\n     */\n    isUnknownElement: no,\n\n    /**\n     * Get the namespace of an element\n     */\n    getTagNamespace: noop,\n\n    /**\n     * Parse the real tag name for the specific platform.\n     */\n    parsePlatformTagName: identity,\n\n    /**\n     * Check if an attribute must be bound using property, e.g. value\n     * Platform-dependent.\n     */\n    mustUseProp: no,\n\n    /**\n     * Exposed for legacy reasons\n     */\n    _lifecycleHooks: LIFECYCLE_HOOKS\n  };\n\n  /*  */\n\n  /**\n   * Check if a string starts with $ or _\n   */\n  function isReserved(str) {\n    var c = (str + \"\").charCodeAt(0);\n    return c === 0x24 || c === 0x5f;\n  }\n\n  /**\n   * Define a property.\n   */\n  function def(obj, key, val, enumerable) {\n    Object.defineProperty(obj, key, {\n      value: val,\n      enumerable: !!enumerable,\n      writable: true,\n      configurable: true\n    });\n  }\n\n  /**\n   * Parse simple path.\n   */\n  var bailRE = /[^\\w.$]/;\n  function parsePath(path) {\n    if (bailRE.test(path)) {\n      return;\n    }\n    var segments = path.split(\".\");\n    return function(obj) {\n      for (var i = 0; i < segments.length; i++) {\n        if (!obj) {\n          return;\n        }\n        obj = obj[segments[i]];\n      }\n      return obj;\n    };\n  }\n\n  /*  */\n\n  // can we use __proto__?\n  var hasProto = \"__proto__\" in {};\n\n  // Browser environment sniffing\n  var inBrowser = typeof window !== \"undefined\";\n  var inWeex = typeof WXEnvironment !== \"undefined\" && !!WXEnvironment.platform;\n  var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();\n  var UA = inBrowser && window.navigator.userAgent.toLowerCase();\n  var isIE = UA && /msie|trident/.test(UA);\n  var isIE9 = UA && UA.indexOf(\"msie 9.0\") > 0;\n  var isEdge = UA && UA.indexOf(\"edge/\") > 0;\n  var isAndroid = (UA && UA.indexOf(\"android\") > 0) || weexPlatform === \"android\";\n  var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || weexPlatform === \"ios\";\n  var isChrome = UA && /chrome\\/\\d+/.test(UA) && !isEdge;\n\n  // Firefox has a \"watch\" function on Object.prototype...\n  var nativeWatch = {}.watch;\n\n  var supportsPassive = false;\n  if (inBrowser) {\n    try {\n      var opts = {};\n      Object.defineProperty(opts, \"passive\", {\n        get: function get() {\n          /* istanbul ignore next */\n          supportsPassive = true;\n        }\n      }); // https://github.com/facebook/flow/issues/285\n      window.addEventListener(\"test-passive\", null, opts);\n    } catch (e) {}\n  }\n\n  // this needs to be lazy-evaled because vue may be required before\n  // vue-server-renderer can set VUE_ENV\n  var _isServer;\n  var isServerRendering = function() {\n    if (_isServer === undefined) {\n      /* istanbul ignore if */\n      if (!inBrowser && !inWeex && typeof global !== \"undefined\") {\n        // detect presence of vue-server-renderer and avoid\n        // Webpack shimming the process\n        _isServer = global[\"process\"].env.VUE_ENV === \"server\";\n      } else {\n        _isServer = false;\n      }\n    }\n    return _isServer;\n  };\n\n  // detect devtools\n  var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;\n\n  /* istanbul ignore next */\n  function isNative(Ctor) {\n    return typeof Ctor === \"function\" && /native code/.test(Ctor.toString());\n  }\n\n  var hasSymbol =\n    typeof Symbol !== \"undefined\" && isNative(Symbol) && typeof Reflect !== \"undefined\" && isNative(Reflect.ownKeys);\n\n  var _Set; // $flow-disable-line\n  /* istanbul ignore if */ if (typeof Set !== \"undefined\" && isNative(Set)) {\n    // use native Set when available.\n    _Set = Set;\n  } else {\n    // a non-standard Set polyfill that only works with primitive keys.\n    _Set = (function() {\n      function Set() {\n        this.set = Object.create(null);\n      }\n      Set.prototype.has = function has(key) {\n        return this.set[key] === true;\n      };\n      Set.prototype.add = function add(key) {\n        this.set[key] = true;\n      };\n      Set.prototype.clear = function clear() {\n        this.set = Object.create(null);\n      };\n\n      return Set;\n    })();\n  }\n\n  /*  */\n\n  var warn = noop;\n  var tip = noop;\n  var generateComponentTrace = noop; // work around flow check\n  var formatComponentName = noop;\n\n  {\n    var hasConsole = typeof console !== \"undefined\";\n    var classifyRE = /(?:^|[-_])(\\w)/g;\n    var classify = function(str) {\n      return str\n        .replace(classifyRE, function(c) {\n          return c.toUpperCase();\n        })\n        .replace(/[-_]/g, \"\");\n    };\n\n    warn = function(msg, vm) {\n      var trace = vm ? generateComponentTrace(vm) : \"\";\n\n      if (config.warnHandler) {\n        config.warnHandler.call(null, msg, vm, trace);\n      } else if (hasConsole && !config.silent) {\n        console.error(\"[Vue warn]: \" + msg + trace);\n      }\n    };\n\n    tip = function(msg, vm) {\n      if (hasConsole && !config.silent) {\n        console.warn(\"[Vue tip]: \" + msg + (vm ? generateComponentTrace(vm) : \"\"));\n      }\n    };\n\n    formatComponentName = function(vm, includeFile) {\n      if (vm.$root === vm) {\n        return \"<Root>\";\n      }\n      var options =\n        typeof vm === \"function\" && vm.cid != null\n          ? vm.options\n          : vm._isVue\n            ? vm.$options || vm.constructor.options\n            : vm || {};\n      var name = options.name || options._componentTag;\n      var file = options.__file;\n      if (!name && file) {\n        var match = file.match(/([^/\\\\]+)\\.vue$/);\n        name = match && match[1];\n      }\n\n      return (name ? \"<\" + classify(name) + \">\" : \"<Anonymous>\") + (file && includeFile !== false ? \" at \" + file : \"\");\n    };\n\n    var repeat = function(str, n) {\n      var res = \"\";\n      while (n) {\n        if (n % 2 === 1) {\n          res += str;\n        }\n        if (n > 1) {\n          str += str;\n        }\n        n >>= 1;\n      }\n      return res;\n    };\n\n    generateComponentTrace = function(vm) {\n      if (vm._isVue && vm.$parent) {\n        var tree = [];\n        var currentRecursiveSequence = 0;\n        while (vm) {\n          if (tree.length > 0) {\n            var last = tree[tree.length - 1];\n            if (last.constructor === vm.constructor) {\n              currentRecursiveSequence++;\n              vm = vm.$parent;\n              continue;\n            } else if (currentRecursiveSequence > 0) {\n              tree[tree.length - 1] = [last, currentRecursiveSequence];\n              currentRecursiveSequence = 0;\n            }\n          }\n          tree.push(vm);\n          vm = vm.$parent;\n        }\n        return (\n          \"\\n\\nfound in\\n\\n\" +\n          tree\n            .map(function(vm, i) {\n              return (\n                \"\" +\n                (i === 0 ? \"---> \" : repeat(\" \", 5 + i * 2)) +\n                (Array.isArray(vm)\n                  ? formatComponentName(vm[0]) + \"... (\" + vm[1] + \" recursive calls)\"\n                  : formatComponentName(vm))\n              );\n            })\n            .join(\"\\n\")\n        );\n      } else {\n        return \"\\n\\n(found in \" + formatComponentName(vm) + \")\";\n      }\n    };\n  }\n\n  /*  */\n\n  var uid = 0;\n\n  /**\n   * A dep is an observable that can have multiple\n   * directives subscribing to it.\n   */\n  var Dep = function Dep() {\n    this.id = uid++;\n    this.subs = [];\n  };\n\n  Dep.prototype.addSub = function addSub(sub) {\n    this.subs.push(sub);\n  };\n\n  Dep.prototype.removeSub = function removeSub(sub) {\n    remove(this.subs, sub);\n  };\n\n  Dep.prototype.depend = function depend() {\n    if (Dep.target) {\n      Dep.target.addDep(this);\n    }\n  };\n\n  Dep.prototype.notify = function notify() {\n    // stabilize the subscriber list first\n    var subs = this.subs.slice();\n    for (var i = 0, l = subs.length; i < l; i++) {\n      subs[i].update();\n    }\n  };\n\n  // the current target watcher being evaluated.\n  // this is globally unique because there could be only one\n  // watcher being evaluated at any time.\n  Dep.target = null;\n  var targetStack = [];\n\n  function pushTarget(_target) {\n    if (Dep.target) {\n      targetStack.push(Dep.target);\n    }\n    Dep.target = _target;\n  }\n\n  function popTarget() {\n    Dep.target = targetStack.pop();\n  }\n\n  /*  */\n\n  var VNode = function VNode(tag, data, children, text, elm, context, componentOptions, asyncFactory) {\n    this.tag = tag;\n    this.data = data;\n    this.children = children;\n    this.text = text;\n    this.elm = elm;\n    this.ns = undefined;\n    this.context = context;\n    this.fnContext = undefined;\n    this.fnOptions = undefined;\n    this.fnScopeId = undefined;\n    this.key = data && data.key;\n    this.componentOptions = componentOptions;\n    this.componentInstance = undefined;\n    this.parent = undefined;\n    this.raw = false;\n    this.isStatic = false;\n    this.isRootInsert = true;\n    this.isComment = false;\n    this.isCloned = false;\n    this.isOnce = false;\n    this.asyncFactory = asyncFactory;\n    this.asyncMeta = undefined;\n    this.isAsyncPlaceholder = false;\n  };\n\n  var prototypeAccessors = { child: { configurable: true } };\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  prototypeAccessors.child.get = function() {\n    return this.componentInstance;\n  };\n\n  Object.defineProperties(VNode.prototype, prototypeAccessors);\n\n  var createEmptyVNode = function(text) {\n    if (text === void 0) text = \"\";\n\n    var node = new VNode();\n    node.text = text;\n    node.isComment = true;\n    return node;\n  };\n\n  function createTextVNode(val) {\n    return new VNode(undefined, undefined, undefined, String(val));\n  }\n\n  // optimized shallow clone\n  // used for static nodes and slot nodes because they may be reused across\n  // multiple renders, cloning them avoids errors when DOM manipulations rely\n  // on their elm reference.\n  function cloneVNode(vnode) {\n    var cloned = new VNode(\n      vnode.tag,\n      vnode.data,\n      vnode.children,\n      vnode.text,\n      vnode.elm,\n      vnode.context,\n      vnode.componentOptions,\n      vnode.asyncFactory\n    );\n    cloned.ns = vnode.ns;\n    cloned.isStatic = vnode.isStatic;\n    cloned.key = vnode.key;\n    cloned.isComment = vnode.isComment;\n    cloned.fnContext = vnode.fnContext;\n    cloned.fnOptions = vnode.fnOptions;\n    cloned.fnScopeId = vnode.fnScopeId;\n    cloned.isCloned = true;\n    return cloned;\n  }\n\n  /*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n\n  var arrayProto = Array.prototype;\n  var arrayMethods = Object.create(arrayProto);\n\n  var methodsToPatch = [\"push\", \"pop\", \"shift\", \"unshift\", \"splice\", \"sort\", \"reverse\"];\n\n  /**\n   * Intercept mutating methods and emit events\n   */\n  methodsToPatch.forEach(function(method) {\n    // cache original method\n    var original = arrayProto[method];\n    def(arrayMethods, method, function mutator() {\n      var args = [],\n        len = arguments.length;\n      while (len--) args[len] = arguments[len];\n\n      var result = original.apply(this, args);\n      var ob = this.__ob__;\n      var inserted;\n      switch (method) {\n        case \"push\":\n        case \"unshift\":\n          inserted = args;\n          break;\n        case \"splice\":\n          inserted = args.slice(2);\n          break;\n      }\n      if (inserted) {\n        ob.observeArray(inserted);\n      }\n      // notify change\n      ob.dep.notify();\n      return result;\n    });\n  });\n\n  /*  */\n\n  var arrayKeys = Object.getOwnPropertyNames(arrayMethods);\n\n  /**\n   * In some cases we may want to disable observation inside a component's\n   * update computation.\n   */\n  var shouldObserve = true;\n\n  function toggleObserving(value) {\n    shouldObserve = value;\n  }\n\n  /**\n   * Observer class that is attached to each observed\n   * object. Once attached, the observer converts the target\n   * object's property keys into getter/setters that\n   * collect dependencies and dispatch updates.\n   */\n  var Observer = function Observer(value) {\n    this.value = value;\n    this.dep = new Dep();\n    this.vmCount = 0;\n    def(value, \"__ob__\", this);\n    if (Array.isArray(value)) {\n      var augment = hasProto ? protoAugment : copyAugment;\n      augment(value, arrayMethods, arrayKeys);\n      this.observeArray(value);\n    } else {\n      this.walk(value);\n    }\n  };\n\n  /**\n   * Walk through each property and convert them into\n   * getter/setters. This method should only be called when\n   * value type is Object.\n   */\n  Observer.prototype.walk = function walk(obj) {\n    var keys = Object.keys(obj);\n    for (var i = 0; i < keys.length; i++) {\n      defineReactive(obj, keys[i]);\n    }\n  };\n\n  /**\n   * Observe a list of Array items.\n   */\n  Observer.prototype.observeArray = function observeArray(items) {\n    for (var i = 0, l = items.length; i < l; i++) {\n      observe(items[i]);\n    }\n  };\n\n  // helpers\n\n  /**\n   * Augment an target Object or Array by intercepting\n   * the prototype chain using __proto__\n   */\n  function protoAugment(target, src, keys) {\n    /* eslint-disable no-proto */\n    target.__proto__ = src;\n    /* eslint-enable no-proto */\n  }\n\n  /**\n   * Augment an target Object or Array by defining\n   * hidden properties.\n   */\n  /* istanbul ignore next */\n  function copyAugment(target, src, keys) {\n    for (var i = 0, l = keys.length; i < l; i++) {\n      var key = keys[i];\n      def(target, key, src[key]);\n    }\n  }\n\n  /**\n   * Attempt to create an observer instance for a value,\n   * returns the new observer if successfully observed,\n   * or the existing observer if the value already has one.\n   */\n  function observe(value, asRootData) {\n    if (!isObject(value) || value instanceof VNode) {\n      return;\n    }\n    var ob;\n    if (hasOwn(value, \"__ob__\") && value.__ob__ instanceof Observer) {\n      ob = value.__ob__;\n    } else if (\n      shouldObserve &&\n      !isServerRendering() &&\n      (Array.isArray(value) || isPlainObject(value)) &&\n      Object.isExtensible(value) &&\n      !value._isVue\n    ) {\n      ob = new Observer(value);\n    }\n    if (asRootData && ob) {\n      ob.vmCount++;\n    }\n    return ob;\n  }\n\n  /**\n   * Define a reactive property on an Object.\n   */\n  function defineReactive(obj, key, val, customSetter, shallow) {\n    var dep = new Dep();\n\n    var property = Object.getOwnPropertyDescriptor(obj, key);\n    if (property && property.configurable === false) {\n      return;\n    }\n\n    // cater for pre-defined getter/setters\n    var getter = property && property.get;\n    if (!getter && arguments.length === 2) {\n      val = obj[key];\n    }\n    var setter = property && property.set;\n\n    var childOb = !shallow && observe(val);\n    Object.defineProperty(obj, key, {\n      enumerable: true,\n      configurable: true,\n      get: function reactiveGetter() {\n        var value = getter ? getter.call(obj) : val;\n        if (Dep.target) {\n          dep.depend();\n          if (childOb) {\n            childOb.dep.depend();\n            if (Array.isArray(value)) {\n              dependArray(value);\n            }\n          }\n        }\n        return value;\n      },\n      set: function reactiveSetter(newVal) {\n        var value = getter ? getter.call(obj) : val;\n        /* eslint-disable no-self-compare */\n        if (newVal === value || (newVal !== newVal && value !== value)) {\n          return;\n        }\n        /* eslint-enable no-self-compare */\n        if (\"development\" !== \"production\" && customSetter) {\n          customSetter();\n        }\n        if (setter) {\n          setter.call(obj, newVal);\n        } else {\n          val = newVal;\n        }\n        childOb = !shallow && observe(newVal);\n        dep.notify();\n      }\n    });\n  }\n\n  /**\n   * Set a property on an object. Adds the new property and\n   * triggers change notification if the property doesn't\n   * already exist.\n   */\n  function set(target, key, val) {\n    if (\"development\" !== \"production\" && (isUndef(target) || isPrimitive(target))) {\n      warn(\"Cannot set reactive property on undefined, null, or primitive value: \" + target);\n    }\n    if (Array.isArray(target) && isValidArrayIndex(key)) {\n      target.length = Math.max(target.length, key);\n      target.splice(key, 1, val);\n      return val;\n    }\n    if (key in target && !(key in Object.prototype)) {\n      target[key] = val;\n      return val;\n    }\n    var ob = target.__ob__;\n    if (target._isVue || (ob && ob.vmCount)) {\n      \"development\" !== \"production\" &&\n        warn(\n          \"Avoid adding reactive properties to a Vue instance or its root $data \" +\n            \"at runtime - declare it upfront in the data option.\"\n        );\n      return val;\n    }\n    if (!ob) {\n      target[key] = val;\n      return val;\n    }\n    defineReactive(ob.value, key, val);\n    ob.dep.notify();\n    return val;\n  }\n\n  /**\n   * Delete a property and trigger change if necessary.\n   */\n  function del(target, key) {\n    if (\"development\" !== \"production\" && (isUndef(target) || isPrimitive(target))) {\n      warn(\"Cannot delete reactive property on undefined, null, or primitive value: \" + target);\n    }\n    if (Array.isArray(target) && isValidArrayIndex(key)) {\n      target.splice(key, 1);\n      return;\n    }\n    var ob = target.__ob__;\n    if (target._isVue || (ob && ob.vmCount)) {\n      \"development\" !== \"production\" &&\n        warn(\"Avoid deleting properties on a Vue instance or its root $data \" + \"- just set it to null.\");\n      return;\n    }\n    if (!hasOwn(target, key)) {\n      return;\n    }\n    delete target[key];\n    if (!ob) {\n      return;\n    }\n    ob.dep.notify();\n  }\n\n  /**\n   * Collect dependencies on array elements when the array is touched, since\n   * we cannot intercept array element access like property getters.\n   */\n  function dependArray(value) {\n    for (var e = void 0, i = 0, l = value.length; i < l; i++) {\n      e = value[i];\n      e && e.__ob__ && e.__ob__.dep.depend();\n      if (Array.isArray(e)) {\n        dependArray(e);\n      }\n    }\n  }\n\n  /*  */\n\n  /**\n   * Option overwriting strategies are functions that handle\n   * how to merge a parent option value and a child option\n   * value into the final value.\n   */\n  var strats = config.optionMergeStrategies;\n\n  /**\n   * Options with restrictions\n   */\n  {\n    strats.el = strats.propsData = function(parent, child, vm, key) {\n      if (!vm) {\n        warn('option \"' + key + '\" can only be used during instance ' + \"creation with the `new` keyword.\");\n      }\n      return defaultStrat(parent, child);\n    };\n  }\n\n  /**\n   * Helper that recursively merges two data objects together.\n   */\n  function mergeData(to, from) {\n    if (!from) {\n      return to;\n    }\n    var key, toVal, fromVal;\n    var keys = Object.keys(from);\n    for (var i = 0; i < keys.length; i++) {\n      key = keys[i];\n      toVal = to[key];\n      fromVal = from[key];\n      if (!hasOwn(to, key)) {\n        set(to, key, fromVal);\n      } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {\n        mergeData(toVal, fromVal);\n      }\n    }\n    return to;\n  }\n\n  /**\n   * Data\n   */\n  function mergeDataOrFn(parentVal, childVal, vm) {\n    if (!vm) {\n      // in a Vue.extend merge, both should be functions\n      if (!childVal) {\n        return parentVal;\n      }\n      if (!parentVal) {\n        return childVal;\n      }\n      // when parentVal & childVal are both present,\n      // we need to return a function that returns the\n      // merged result of both functions... no need to\n      // check if parentVal is a function here because\n      // it has to be a function to pass previous merges.\n      return function mergedDataFn() {\n        return mergeData(\n          typeof childVal === \"function\" ? childVal.call(this, this) : childVal,\n          typeof parentVal === \"function\" ? parentVal.call(this, this) : parentVal\n        );\n      };\n    } else {\n      return function mergedInstanceDataFn() {\n        // instance merge\n        var instanceData = typeof childVal === \"function\" ? childVal.call(vm, vm) : childVal;\n        var defaultData = typeof parentVal === \"function\" ? parentVal.call(vm, vm) : parentVal;\n        if (instanceData) {\n          return mergeData(instanceData, defaultData);\n        } else {\n          return defaultData;\n        }\n      };\n    }\n  }\n\n  strats.data = function(parentVal, childVal, vm) {\n    if (!vm) {\n      if (childVal && typeof childVal !== \"function\") {\n        \"development\" !== \"production\" &&\n          warn(\n            'The \"data\" option should be a function ' +\n              \"that returns a per-instance value in component \" +\n              \"definitions.\",\n            vm\n          );\n\n        return parentVal;\n      }\n      return mergeDataOrFn(parentVal, childVal);\n    }\n\n    return mergeDataOrFn(parentVal, childVal, vm);\n  };\n\n  /**\n   * Hooks and props are merged as arrays.\n   */\n  function mergeHook(parentVal, childVal) {\n    return childVal\n      ? parentVal\n        ? parentVal.concat(childVal)\n        : Array.isArray(childVal)\n          ? childVal\n          : [childVal]\n      : parentVal;\n  }\n\n  LIFECYCLE_HOOKS.forEach(function(hook) {\n    strats[hook] = mergeHook;\n  });\n\n  /**\n   * Assets\n   *\n   * When a vm is present (instance creation), we need to do\n   * a three-way merge between constructor options, instance\n   * options and parent options.\n   */\n  function mergeAssets(parentVal, childVal, vm, key) {\n    var res = Object.create(parentVal || null);\n    if (childVal) {\n      \"development\" !== \"production\" && assertObjectType(key, childVal, vm);\n      return extend(res, childVal);\n    } else {\n      return res;\n    }\n  }\n\n  ASSET_TYPES.forEach(function(type) {\n    strats[type + \"s\"] = mergeAssets;\n  });\n\n  /**\n   * Watchers.\n   *\n   * Watchers hashes should not overwrite one\n   * another, so we merge them as arrays.\n   */\n  strats.watch = function(parentVal, childVal, vm, key) {\n    // work around Firefox's Object.prototype.watch...\n    if (parentVal === nativeWatch) {\n      parentVal = undefined;\n    }\n    if (childVal === nativeWatch) {\n      childVal = undefined;\n    }\n    /* istanbul ignore if */\n    if (!childVal) {\n      return Object.create(parentVal || null);\n    }\n    {\n      assertObjectType(key, childVal, vm);\n    }\n    if (!parentVal) {\n      return childVal;\n    }\n    var ret = {};\n    extend(ret, parentVal);\n    for (var key$1 in childVal) {\n      var parent = ret[key$1];\n      var child = childVal[key$1];\n      if (parent && !Array.isArray(parent)) {\n        parent = [parent];\n      }\n      ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child];\n    }\n    return ret;\n  };\n\n  /**\n   * Other object hashes.\n   */\n  strats.props = strats.methods = strats.inject = strats.computed = function(parentVal, childVal, vm, key) {\n    if (childVal && \"development\" !== \"production\") {\n      assertObjectType(key, childVal, vm);\n    }\n    if (!parentVal) {\n      return childVal;\n    }\n    var ret = Object.create(null);\n    extend(ret, parentVal);\n    if (childVal) {\n      extend(ret, childVal);\n    }\n    return ret;\n  };\n  strats.provide = mergeDataOrFn;\n\n  /**\n   * Default strategy.\n   */\n  var defaultStrat = function(parentVal, childVal) {\n    return childVal === undefined ? parentVal : childVal;\n  };\n\n  /**\n   * Validate component names\n   */\n  function checkComponents(options) {\n    for (var key in options.components) {\n      validateComponentName(key);\n    }\n  }\n\n  function validateComponentName(name) {\n    if (!/^[a-zA-Z][\\w-]*$/.test(name)) {\n      warn(\n        'Invalid component name: \"' +\n          name +\n          '\". Component names ' +\n          \"can only contain alphanumeric characters and the hyphen, \" +\n          \"and must start with a letter.\"\n      );\n    }\n    if (isBuiltInTag(name) || config.isReservedTag(name)) {\n      warn(\"Do not use built-in or reserved HTML elements as component \" + \"id: \" + name);\n    }\n  }\n\n  /**\n   * Ensure all props option syntax are normalized into the\n   * Object-based format.\n   */\n  function normalizeProps(options, vm) {\n    var props = options.props;\n    if (!props) {\n      return;\n    }\n    var res = {};\n    var i, val, name;\n    if (Array.isArray(props)) {\n      i = props.length;\n      while (i--) {\n        val = props[i];\n        if (typeof val === \"string\") {\n          name = camelize(val);\n          res[name] = { type: null };\n        } else {\n          warn(\"props must be strings when using array syntax.\");\n        }\n      }\n    } else if (isPlainObject(props)) {\n      for (var key in props) {\n        val = props[key];\n        name = camelize(key);\n        res[name] = isPlainObject(val) ? val : { type: val };\n      }\n    } else {\n      warn(\n        'Invalid value for option \"props\": expected an Array or an Object, ' + \"but got \" + toRawType(props) + \".\",\n        vm\n      );\n    }\n    options.props = res;\n  }\n\n  /**\n   * Normalize all injections into Object-based format\n   */\n  function normalizeInject(options, vm) {\n    var inject = options.inject;\n    if (!inject) {\n      return;\n    }\n    var normalized = (options.inject = {});\n    if (Array.isArray(inject)) {\n      for (var i = 0; i < inject.length; i++) {\n        normalized[inject[i]] = { from: inject[i] };\n      }\n    } else if (isPlainObject(inject)) {\n      for (var key in inject) {\n        var val = inject[key];\n        normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val };\n      }\n    } else {\n      warn(\n        'Invalid value for option \"inject\": expected an Array or an Object, ' + \"but got \" + toRawType(inject) + \".\",\n        vm\n      );\n    }\n  }\n\n  /**\n   * Normalize raw function directives into object format.\n   */\n  function normalizeDirectives(options) {\n    var dirs = options.directives;\n    if (dirs) {\n      for (var key in dirs) {\n        var def = dirs[key];\n        if (typeof def === \"function\") {\n          dirs[key] = { bind: def, update: def };\n        }\n      }\n    }\n  }\n\n  function assertObjectType(name, value, vm) {\n    if (!isPlainObject(value)) {\n      warn('Invalid value for option \"' + name + '\": expected an Object, ' + \"but got \" + toRawType(value) + \".\", vm);\n    }\n  }\n\n  /**\n   * Merge two option objects into a new one.\n   * Core utility used in both instantiation and inheritance.\n   */\n  function mergeOptions(parent, child, vm) {\n    {\n      checkComponents(child);\n    }\n\n    if (typeof child === \"function\") {\n      child = child.options;\n    }\n\n    normalizeProps(child, vm);\n    normalizeInject(child, vm);\n    normalizeDirectives(child);\n    var extendsFrom = child.extends;\n    if (extendsFrom) {\n      parent = mergeOptions(parent, extendsFrom, vm);\n    }\n    if (child.mixins) {\n      for (var i = 0, l = child.mixins.length; i < l; i++) {\n        parent = mergeOptions(parent, child.mixins[i], vm);\n      }\n    }\n    var options = {};\n    var key;\n    for (key in parent) {\n      mergeField(key);\n    }\n    for (key in child) {\n      if (!hasOwn(parent, key)) {\n        mergeField(key);\n      }\n    }\n    function mergeField(key) {\n      var strat = strats[key] || defaultStrat;\n      options[key] = strat(parent[key], child[key], vm, key);\n    }\n    return options;\n  }\n\n  /**\n   * Resolve an asset.\n   * This function is used because child instances need access\n   * to assets defined in its ancestor chain.\n   */\n  function resolveAsset(options, type, id, warnMissing) {\n    /* istanbul ignore if */\n    if (typeof id !== \"string\") {\n      return;\n    }\n    var assets = options[type];\n    // check local registration variations first\n    if (hasOwn(assets, id)) {\n      return assets[id];\n    }\n    var camelizedId = camelize(id);\n    if (hasOwn(assets, camelizedId)) {\n      return assets[camelizedId];\n    }\n    var PascalCaseId = capitalize(camelizedId);\n    if (hasOwn(assets, PascalCaseId)) {\n      return assets[PascalCaseId];\n    }\n    // fallback to prototype chain\n    var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];\n    if (\"development\" !== \"production\" && warnMissing && !res) {\n      warn(\"Failed to resolve \" + type.slice(0, -1) + \": \" + id, options);\n    }\n    return res;\n  }\n\n  /*  */\n\n  function validateProp(key, propOptions, propsData, vm) {\n    var prop = propOptions[key];\n    var absent = !hasOwn(propsData, key);\n    var value = propsData[key];\n    // boolean casting\n    var booleanIndex = getTypeIndex(Boolean, prop.type);\n    if (booleanIndex > -1) {\n      if (absent && !hasOwn(prop, \"default\")) {\n        value = false;\n      } else if (value === \"\" || value === hyphenate(key)) {\n        // only cast empty string / same name to boolean if\n        // boolean has higher priority\n        var stringIndex = getTypeIndex(String, prop.type);\n        if (stringIndex < 0 || booleanIndex < stringIndex) {\n          value = true;\n        }\n      }\n    }\n    // check default value\n    if (value === undefined) {\n      value = getPropDefaultValue(vm, prop, key);\n      // since the default value is a fresh copy,\n      // make sure to observe it.\n      var prevShouldObserve = shouldObserve;\n      toggleObserving(true);\n      observe(value);\n      toggleObserving(prevShouldObserve);\n    }\n    {\n      assertProp(prop, key, value, vm, absent);\n    }\n    return value;\n  }\n\n  /**\n   * Get the default value of a prop.\n   */\n  function getPropDefaultValue(vm, prop, key) {\n    // no default, return undefined\n    if (!hasOwn(prop, \"default\")) {\n      return undefined;\n    }\n    var def = prop.default;\n    // warn against non-factory defaults for Object & Array\n    if (\"development\" !== \"production\" && isObject(def)) {\n      warn(\n        'Invalid default value for prop \"' +\n          key +\n          '\": ' +\n          \"Props with type Object/Array must use a factory function \" +\n          \"to return the default value.\",\n        vm\n      );\n    }\n    // the raw prop value was also undefined from previous render,\n    // return previous default value to avoid unnecessary watcher trigger\n    if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined) {\n      return vm._props[key];\n    }\n    // call factory function for non-Function types\n    // a value is Function if its prototype is function even across different execution context\n    return typeof def === \"function\" && getType(prop.type) !== \"Function\" ? def.call(vm) : def;\n  }\n\n  /**\n   * Assert whether a prop is valid.\n   */\n  function assertProp(prop, name, value, vm, absent) {\n    if (prop.required && absent) {\n      warn('Missing required prop: \"' + name + '\"', vm);\n      return;\n    }\n    if (value == null && !prop.required) {\n      return;\n    }\n    var type = prop.type;\n    var valid = !type || type === true;\n    var expectedTypes = [];\n    if (type) {\n      if (!Array.isArray(type)) {\n        type = [type];\n      }\n      for (var i = 0; i < type.length && !valid; i++) {\n        var assertedType = assertType(value, type[i]);\n        expectedTypes.push(assertedType.expectedType || \"\");\n        valid = assertedType.valid;\n      }\n    }\n    if (!valid) {\n      warn(\n        'Invalid prop: type check failed for prop \"' +\n          name +\n          '\".' +\n          \" Expected \" +\n          expectedTypes.map(capitalize).join(\", \") +\n          \", got \" +\n          toRawType(value) +\n          \".\",\n        vm\n      );\n      return;\n    }\n    var validator = prop.validator;\n    if (validator) {\n      if (!validator(value)) {\n        warn('Invalid prop: custom validator check failed for prop \"' + name + '\".', vm);\n      }\n    }\n  }\n\n  var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/;\n\n  function assertType(value, type) {\n    var valid;\n    var expectedType = getType(type);\n    if (simpleCheckRE.test(expectedType)) {\n      var t = typeof value;\n      valid = t === expectedType.toLowerCase();\n      // for primitive wrapper objects\n      if (!valid && t === \"object\") {\n        valid = value instanceof type;\n      }\n    } else if (expectedType === \"Object\") {\n      valid = isPlainObject(value);\n    } else if (expectedType === \"Array\") {\n      valid = Array.isArray(value);\n    } else {\n      valid = value instanceof type;\n    }\n    return {\n      valid: valid,\n      expectedType: expectedType\n    };\n  }\n\n  /**\n   * Use function string name to check built-in types,\n   * because a simple equality check will fail when running\n   * across different vms / iframes.\n   */\n  function getType(fn) {\n    var match = fn && fn.toString().match(/^\\s*function (\\w+)/);\n    return match ? match[1] : \"\";\n  }\n\n  function isSameType(a, b) {\n    return getType(a) === getType(b);\n  }\n\n  function getTypeIndex(type, expectedTypes) {\n    if (!Array.isArray(expectedTypes)) {\n      return isSameType(expectedTypes, type) ? 0 : -1;\n    }\n    for (var i = 0, len = expectedTypes.length; i < len; i++) {\n      if (isSameType(expectedTypes[i], type)) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  /*  */\n\n  function handleError(err, vm, info) {\n    if (vm) {\n      var cur = vm;\n      while ((cur = cur.$parent)) {\n        var hooks = cur.$options.errorCaptured;\n        if (hooks) {\n          for (var i = 0; i < hooks.length; i++) {\n            try {\n              var capture = hooks[i].call(cur, err, vm, info) === false;\n              if (capture) {\n                return;\n              }\n            } catch (e) {\n              globalHandleError(e, cur, \"errorCaptured hook\");\n            }\n          }\n        }\n      }\n    }\n    globalHandleError(err, vm, info);\n  }\n\n  function globalHandleError(err, vm, info) {\n    if (config.errorHandler) {\n      try {\n        return config.errorHandler.call(null, err, vm, info);\n      } catch (e) {\n        logError(e, null, \"config.errorHandler\");\n      }\n    }\n    logError(err, vm, info);\n  }\n\n  function logError(err, vm, info) {\n    {\n      warn(\"Error in \" + info + ': \"' + err.toString() + '\"', vm);\n    }\n    /* istanbul ignore else */\n    if ((inBrowser || inWeex) && typeof console !== \"undefined\") {\n      console.error(err);\n    } else {\n      throw err;\n    }\n  }\n\n  /*  */\n  /* globals MessageChannel */\n\n  var callbacks = [];\n  var pending = false;\n\n  function flushCallbacks() {\n    pending = false;\n    var copies = callbacks.slice(0);\n    callbacks.length = 0;\n    for (var i = 0; i < copies.length; i++) {\n      copies[i]();\n    }\n  }\n\n  // Here we have async deferring wrappers using both microtasks and (macro) tasks.\n  // In < 2.4 we used microtasks everywhere, but there are some scenarios where\n  // microtasks have too high a priority and fire in between supposedly\n  // sequential events (e.g. #4521, #6690) or even between bubbling of the same\n  // event (#6566). However, using (macro) tasks everywhere also has subtle problems\n  // when state is changed right before repaint (e.g. #6813, out-in transitions).\n  // Here we use microtask by default, but expose a way to force (macro) task when\n  // needed (e.g. in event handlers attached by v-on).\n  var microTimerFunc;\n  var macroTimerFunc;\n  var useMacroTask = false;\n\n  // Determine (macro) task defer implementation.\n  // Technically setImmediate should be the ideal choice, but it's only available\n  // in IE. The only polyfill that consistently queues the callback after all DOM\n  // events triggered in the same loop is by using MessageChannel.\n  /* istanbul ignore if */\n  if (typeof setImmediate !== \"undefined\" && isNative(setImmediate)) {\n    macroTimerFunc = function() {\n      setImmediate(flushCallbacks);\n    };\n  } else if (\n    typeof MessageChannel !== \"undefined\" &&\n    (isNative(MessageChannel) ||\n      // PhantomJS\n      MessageChannel.toString() === \"[object MessageChannelConstructor]\")\n  ) {\n    var channel = new MessageChannel();\n    var port = channel.port2;\n    channel.port1.onmessage = flushCallbacks;\n    macroTimerFunc = function() {\n      port.postMessage(1);\n    };\n  } else {\n    /* istanbul ignore next */\n    macroTimerFunc = function() {\n      setTimeout(flushCallbacks, 0);\n    };\n  }\n\n  // Determine microtask defer implementation.\n  /* istanbul ignore next, $flow-disable-line */\n  if (typeof Promise !== \"undefined\" && isNative(Promise)) {\n    var p = Promise.resolve();\n    microTimerFunc = function() {\n      p.then(flushCallbacks);\n      // in problematic UIWebViews, Promise.then doesn't completely break, but\n      // it can get stuck in a weird state where callbacks are pushed into the\n      // microtask queue but the queue isn't being flushed, until the browser\n      // needs to do some other work, e.g. handle a timer. Therefore we can\n      // \"force\" the microtask queue to be flushed by adding an empty timer.\n      if (isIOS) {\n        setTimeout(noop);\n      }\n    };\n  } else {\n    // fallback to macro\n    microTimerFunc = macroTimerFunc;\n  }\n\n  /**\n   * Wrap a function so that if any code inside triggers state change,\n   * the changes are queued using a (macro) task instead of a microtask.\n   */\n  function withMacroTask(fn) {\n    return (\n      fn._withTask ||\n      (fn._withTask = function() {\n        useMacroTask = true;\n        var res = fn.apply(null, arguments);\n        useMacroTask = false;\n        return res;\n      })\n    );\n  }\n\n  function nextTick(cb, ctx) {\n    var _resolve;\n    callbacks.push(function() {\n      if (cb) {\n        try {\n          cb.call(ctx);\n        } catch (e) {\n          handleError(e, ctx, \"nextTick\");\n        }\n      } else if (_resolve) {\n        _resolve(ctx);\n      }\n    });\n    if (!pending) {\n      pending = true;\n      if (useMacroTask) {\n        macroTimerFunc();\n      } else {\n        microTimerFunc();\n      }\n    }\n    // $flow-disable-line\n    if (!cb && typeof Promise !== \"undefined\") {\n      return new Promise(function(resolve) {\n        _resolve = resolve;\n      });\n    }\n  }\n\n  /*  */\n\n  var mark;\n  var measure;\n\n  {\n    var perf = inBrowser && window.performance;\n    /* istanbul ignore if */\n    if (perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures) {\n      mark = function(tag) {\n        return perf.mark(tag);\n      };\n      measure = function(name, startTag, endTag) {\n        perf.measure(name, startTag, endTag);\n        perf.clearMarks(startTag);\n        perf.clearMarks(endTag);\n        perf.clearMeasures(name);\n      };\n    }\n  }\n\n  /* not type checking this file because flow doesn't play well with Proxy */\n\n  var initProxy;\n\n  {\n    var allowedGlobals = makeMap(\n      \"Infinity,undefined,NaN,isFinite,isNaN,\" +\n        \"parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,\" +\n        \"Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,\" +\n        \"require\" // for Webpack/Browserify\n    );\n\n    var warnNonPresent = function(target, key) {\n      warn(\n        'Property or method \"' +\n          key +\n          '\" is not defined on the instance but ' +\n          \"referenced during render. Make sure that this property is reactive, \" +\n          \"either in the data option, or for class-based components, by \" +\n          \"initializing the property. \" +\n          \"See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.\",\n        target\n      );\n    };\n\n    var hasProxy = typeof Proxy !== \"undefined\" && isNative(Proxy);\n\n    if (hasProxy) {\n      var isBuiltInModifier = makeMap(\"stop,prevent,self,ctrl,shift,alt,meta,exact\");\n      config.keyCodes = new Proxy(config.keyCodes, {\n        set: function set(target, key, value) {\n          if (isBuiltInModifier(key)) {\n            warn(\"Avoid overwriting built-in modifier in config.keyCodes: .\" + key);\n            return false;\n          } else {\n            target[key] = value;\n            return true;\n          }\n        }\n      });\n    }\n\n    var hasHandler = {\n      has: function has(target, key) {\n        var has = key in target;\n        var isAllowed = allowedGlobals(key) || key.charAt(0) === \"_\";\n        if (!has && !isAllowed) {\n          warnNonPresent(target, key);\n        }\n        return has || !isAllowed;\n      }\n    };\n\n    var getHandler = {\n      get: function get(target, key) {\n        if (typeof key === \"string\" && !(key in target)) {\n          warnNonPresent(target, key);\n        }\n        return target[key];\n      }\n    };\n\n    initProxy = function initProxy(vm) {\n      if (hasProxy) {\n        // determine which proxy handler to use\n        var options = vm.$options;\n        var handlers = options.render && options.render._withStripped ? getHandler : hasHandler;\n        vm._renderProxy = new Proxy(vm, handlers);\n      } else {\n        vm._renderProxy = vm;\n      }\n    };\n  }\n\n  /*  */\n\n  var seenObjects = new _Set();\n\n  /**\n   * Recursively traverse an object to evoke all converted\n   * getters, so that every nested property inside the object\n   * is collected as a \"deep\" dependency.\n   */\n  function traverse(val) {\n    _traverse(val, seenObjects);\n    seenObjects.clear();\n  }\n\n  function _traverse(val, seen) {\n    var i, keys;\n    var isA = Array.isArray(val);\n    if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {\n      return;\n    }\n    if (val.__ob__) {\n      var depId = val.__ob__.dep.id;\n      if (seen.has(depId)) {\n        return;\n      }\n      seen.add(depId);\n    }\n    if (isA) {\n      i = val.length;\n      while (i--) {\n        _traverse(val[i], seen);\n      }\n    } else {\n      keys = Object.keys(val);\n      i = keys.length;\n      while (i--) {\n        _traverse(val[keys[i]], seen);\n      }\n    }\n  }\n\n  /*  */\n\n  var normalizeEvent = cached(function(name) {\n    var passive = name.charAt(0) === \"&\";\n    name = passive ? name.slice(1) : name;\n    var once$$1 = name.charAt(0) === \"~\"; // Prefixed last, checked first\n    name = once$$1 ? name.slice(1) : name;\n    var capture = name.charAt(0) === \"!\";\n    name = capture ? name.slice(1) : name;\n    return {\n      name: name,\n      once: once$$1,\n      capture: capture,\n      passive: passive\n    };\n  });\n\n  function createFnInvoker(fns) {\n    function invoker() {\n      var arguments$1 = arguments;\n\n      var fns = invoker.fns;\n      if (Array.isArray(fns)) {\n        var cloned = fns.slice();\n        for (var i = 0; i < cloned.length; i++) {\n          cloned[i].apply(null, arguments$1);\n        }\n      } else {\n        // return handler return value for single handlers\n        return fns.apply(null, arguments);\n      }\n    }\n    invoker.fns = fns;\n    return invoker;\n  }\n\n  function updateListeners(on, oldOn, add, remove$$1, vm) {\n    var name, def, cur, old, event;\n    for (name in on) {\n      def = cur = on[name];\n      old = oldOn[name];\n      event = normalizeEvent(name);\n      /* istanbul ignore if */\n      if (isUndef(cur)) {\n        \"development\" !== \"production\" &&\n          warn('Invalid handler for event \"' + event.name + '\": got ' + String(cur), vm);\n      } else if (isUndef(old)) {\n        if (isUndef(cur.fns)) {\n          cur = on[name] = createFnInvoker(cur);\n        }\n        add(event.name, cur, event.once, event.capture, event.passive, event.params);\n      } else if (cur !== old) {\n        old.fns = cur;\n        on[name] = old;\n      }\n    }\n    for (name in oldOn) {\n      if (isUndef(on[name])) {\n        event = normalizeEvent(name);\n        remove$$1(event.name, oldOn[name], event.capture);\n      }\n    }\n  }\n\n  /*  */\n\n  function mergeVNodeHook(def, hookKey, hook) {\n    if (def instanceof VNode) {\n      def = def.data.hook || (def.data.hook = {});\n    }\n    var invoker;\n    var oldHook = def[hookKey];\n\n    function wrappedHook() {\n      hook.apply(this, arguments);\n      // important: remove merged hook to ensure it's called only once\n      // and prevent memory leak\n      remove(invoker.fns, wrappedHook);\n    }\n\n    if (isUndef(oldHook)) {\n      // no existing hook\n      invoker = createFnInvoker([wrappedHook]);\n    } else {\n      /* istanbul ignore if */\n      if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {\n        // already a merged invoker\n        invoker = oldHook;\n        invoker.fns.push(wrappedHook);\n      } else {\n        // existing plain hook\n        invoker = createFnInvoker([oldHook, wrappedHook]);\n      }\n    }\n\n    invoker.merged = true;\n    def[hookKey] = invoker;\n  }\n\n  /*  */\n\n  function extractPropsFromVNodeData(data, Ctor, tag) {\n    // we are only extracting raw values here.\n    // validation and default values are handled in the child\n    // component itself.\n    var propOptions = Ctor.options.props;\n    if (isUndef(propOptions)) {\n      return;\n    }\n    var res = {};\n    var attrs = data.attrs;\n    var props = data.props;\n    if (isDef(attrs) || isDef(props)) {\n      for (var key in propOptions) {\n        var altKey = hyphenate(key);\n        {\n          var keyInLowerCase = key.toLowerCase();\n          if (key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase)) {\n            tip(\n              'Prop \"' +\n                keyInLowerCase +\n                '\" is passed to component ' +\n                formatComponentName(tag || Ctor) +\n                \", but the declared prop name is\" +\n                ' \"' +\n                key +\n                '\". ' +\n                \"Note that HTML attributes are case-insensitive and camelCased \" +\n                \"props need to use their kebab-case equivalents when using in-DOM \" +\n                'templates. You should probably use \"' +\n                altKey +\n                '\" instead of \"' +\n                key +\n                '\".'\n            );\n          }\n        }\n        checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false);\n      }\n    }\n    return res;\n  }\n\n  function checkProp(res, hash, key, altKey, preserve) {\n    if (isDef(hash)) {\n      if (hasOwn(hash, key)) {\n        res[key] = hash[key];\n        if (!preserve) {\n          delete hash[key];\n        }\n        return true;\n      } else if (hasOwn(hash, altKey)) {\n        res[key] = hash[altKey];\n        if (!preserve) {\n          delete hash[altKey];\n        }\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /*  */\n\n  // The template compiler attempts to minimize the need for normalization by\n  // statically analyzing the template at compile time.\n  //\n  // For plain HTML markup, normalization can be completely skipped because the\n  // generated render function is guaranteed to return Array<VNode>. There are\n  // two cases where extra normalization is needed:\n\n  // 1. When the children contains components - because a functional component\n  // may return an Array instead of a single root. In this case, just a simple\n  // normalization is needed - if any child is an Array, we flatten the whole\n  // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep\n  // because functional components already normalize their own children.\n  function simpleNormalizeChildren(children) {\n    for (var i = 0; i < children.length; i++) {\n      if (Array.isArray(children[i])) {\n        return Array.prototype.concat.apply([], children);\n      }\n    }\n    return children;\n  }\n\n  // 2. When the children contains constructs that always generated nested Arrays,\n  // e.g. <template>, <slot>, v-for, or when the children is provided by user\n  // with hand-written render functions / JSX. In such cases a full normalization\n  // is needed to cater to all possible types of children values.\n  function normalizeChildren(children) {\n    return isPrimitive(children)\n      ? [createTextVNode(children)]\n      : Array.isArray(children)\n        ? normalizeArrayChildren(children)\n        : undefined;\n  }\n\n  function isTextNode(node) {\n    return isDef(node) && isDef(node.text) && isFalse(node.isComment);\n  }\n\n  function normalizeArrayChildren(children, nestedIndex) {\n    var res = [];\n    var i, c, lastIndex, last;\n    for (i = 0; i < children.length; i++) {\n      c = children[i];\n      if (isUndef(c) || typeof c === \"boolean\") {\n        continue;\n      }\n      lastIndex = res.length - 1;\n      last = res[lastIndex];\n      //  nested\n      if (Array.isArray(c)) {\n        if (c.length > 0) {\n          c = normalizeArrayChildren(c, (nestedIndex || \"\") + \"_\" + i);\n          // merge adjacent text nodes\n          if (isTextNode(c[0]) && isTextNode(last)) {\n            res[lastIndex] = createTextVNode(last.text + c[0].text);\n            c.shift();\n          }\n          res.push.apply(res, c);\n        }\n      } else if (isPrimitive(c)) {\n        if (isTextNode(last)) {\n          // merge adjacent text nodes\n          // this is necessary for SSR hydration because text nodes are\n          // essentially merged when rendered to HTML strings\n          res[lastIndex] = createTextVNode(last.text + c);\n        } else if (c !== \"\") {\n          // convert primitive to vnode\n          res.push(createTextVNode(c));\n        }\n      } else {\n        if (isTextNode(c) && isTextNode(last)) {\n          // merge adjacent text nodes\n          res[lastIndex] = createTextVNode(last.text + c.text);\n        } else {\n          // default key for nested array children (likely generated by v-for)\n          if (isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {\n            c.key = \"__vlist\" + nestedIndex + \"_\" + i + \"__\";\n          }\n          res.push(c);\n        }\n      }\n    }\n    return res;\n  }\n\n  /*  */\n\n  function ensureCtor(comp, base) {\n    if (comp.__esModule || (hasSymbol && comp[Symbol.toStringTag] === \"Module\")) {\n      comp = comp.default;\n    }\n    return isObject(comp) ? base.extend(comp) : comp;\n  }\n\n  function createAsyncPlaceholder(factory, data, context, children, tag) {\n    var node = createEmptyVNode();\n    node.asyncFactory = factory;\n    node.asyncMeta = { data: data, context: context, children: children, tag: tag };\n    return node;\n  }\n\n  function resolveAsyncComponent(factory, baseCtor, context) {\n    if (isTrue(factory.error) && isDef(factory.errorComp)) {\n      return factory.errorComp;\n    }\n\n    if (isDef(factory.resolved)) {\n      return factory.resolved;\n    }\n\n    if (isTrue(factory.loading) && isDef(factory.loadingComp)) {\n      return factory.loadingComp;\n    }\n\n    if (isDef(factory.contexts)) {\n      // already pending\n      factory.contexts.push(context);\n    } else {\n      var contexts = (factory.contexts = [context]);\n      var sync = true;\n\n      var forceRender = function() {\n        for (var i = 0, l = contexts.length; i < l; i++) {\n          contexts[i].$forceUpdate();\n        }\n      };\n\n      var resolve = once(function(res) {\n        // cache resolved\n        factory.resolved = ensureCtor(res, baseCtor);\n        // invoke callbacks only if this is not a synchronous resolve\n        // (async resolves are shimmed as synchronous during SSR)\n        if (!sync) {\n          forceRender();\n        }\n      });\n\n      var reject = once(function(reason) {\n        \"development\" !== \"production\" &&\n          warn(\"Failed to resolve async component: \" + String(factory) + (reason ? \"\\nReason: \" + reason : \"\"));\n        if (isDef(factory.errorComp)) {\n          factory.error = true;\n          forceRender();\n        }\n      });\n\n      var res = factory(resolve, reject);\n\n      if (isObject(res)) {\n        if (typeof res.then === \"function\") {\n          // () => Promise\n          if (isUndef(factory.resolved)) {\n            res.then(resolve, reject);\n          }\n        } else if (isDef(res.component) && typeof res.component.then === \"function\") {\n          res.component.then(resolve, reject);\n\n          if (isDef(res.error)) {\n            factory.errorComp = ensureCtor(res.error, baseCtor);\n          }\n\n          if (isDef(res.loading)) {\n            factory.loadingComp = ensureCtor(res.loading, baseCtor);\n            if (res.delay === 0) {\n              factory.loading = true;\n            } else {\n              setTimeout(function() {\n                if (isUndef(factory.resolved) && isUndef(factory.error)) {\n                  factory.loading = true;\n                  forceRender();\n                }\n              }, res.delay || 200);\n            }\n          }\n\n          if (isDef(res.timeout)) {\n            setTimeout(function() {\n              if (isUndef(factory.resolved)) {\n                reject(\"timeout (\" + res.timeout + \"ms)\");\n              }\n            }, res.timeout);\n          }\n        }\n      }\n\n      sync = false;\n      // return in case resolved synchronously\n      return factory.loading ? factory.loadingComp : factory.resolved;\n    }\n  }\n\n  /*  */\n\n  function isAsyncPlaceholder(node) {\n    return node.isComment && node.asyncFactory;\n  }\n\n  /*  */\n\n  function getFirstComponentChild(children) {\n    if (Array.isArray(children)) {\n      for (var i = 0; i < children.length; i++) {\n        var c = children[i];\n        if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {\n          return c;\n        }\n      }\n    }\n  }\n\n  /*  */\n\n  /*  */\n\n  function initEvents(vm) {\n    vm._events = Object.create(null);\n    vm._hasHookEvent = false;\n    // init parent attached events\n    var listeners = vm.$options._parentListeners;\n    if (listeners) {\n      updateComponentListeners(vm, listeners);\n    }\n  }\n\n  var target;\n\n  function add(event, fn, once) {\n    if (once) {\n      target.$once(event, fn);\n    } else {\n      target.$on(event, fn);\n    }\n  }\n\n  function remove$1(event, fn) {\n    target.$off(event, fn);\n  }\n\n  function updateComponentListeners(vm, listeners, oldListeners) {\n    target = vm;\n    updateListeners(listeners, oldListeners || {}, add, remove$1, vm);\n    target = undefined;\n  }\n\n  function eventsMixin(Vue) {\n    var hookRE = /^hook:/;\n    Vue.prototype.$on = function(event, fn) {\n      var this$1 = this;\n\n      var vm = this;\n      if (Array.isArray(event)) {\n        for (var i = 0, l = event.length; i < l; i++) {\n          this$1.$on(event[i], fn);\n        }\n      } else {\n        (vm._events[event] || (vm._events[event] = [])).push(fn);\n        // optimize hook:event cost by using a boolean flag marked at registration\n        // instead of a hash lookup\n        if (hookRE.test(event)) {\n          vm._hasHookEvent = true;\n        }\n      }\n      return vm;\n    };\n\n    Vue.prototype.$once = function(event, fn) {\n      var vm = this;\n      function on() {\n        vm.$off(event, on);\n        fn.apply(vm, arguments);\n      }\n      on.fn = fn;\n      vm.$on(event, on);\n      return vm;\n    };\n\n    Vue.prototype.$off = function(event, fn) {\n      var this$1 = this;\n\n      var vm = this;\n      // all\n      if (!arguments.length) {\n        vm._events = Object.create(null);\n        return vm;\n      }\n      // array of events\n      if (Array.isArray(event)) {\n        for (var i = 0, l = event.length; i < l; i++) {\n          this$1.$off(event[i], fn);\n        }\n        return vm;\n      }\n      // specific event\n      var cbs = vm._events[event];\n      if (!cbs) {\n        return vm;\n      }\n      if (!fn) {\n        vm._events[event] = null;\n        return vm;\n      }\n      if (fn) {\n        // specific handler\n        var cb;\n        var i$1 = cbs.length;\n        while (i$1--) {\n          cb = cbs[i$1];\n          if (cb === fn || cb.fn === fn) {\n            cbs.splice(i$1, 1);\n            break;\n          }\n        }\n      }\n      return vm;\n    };\n\n    Vue.prototype.$emit = function(event) {\n      var vm = this;\n      {\n        var lowerCaseEvent = event.toLowerCase();\n        if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {\n          tip(\n            'Event \"' +\n              lowerCaseEvent +\n              '\" is emitted in component ' +\n              formatComponentName(vm) +\n              ' but the handler is registered for \"' +\n              event +\n              '\". ' +\n              \"Note that HTML attributes are case-insensitive and you cannot use \" +\n              \"v-on to listen to camelCase events when using in-DOM templates. \" +\n              'You should probably use \"' +\n              hyphenate(event) +\n              '\" instead of \"' +\n              event +\n              '\".'\n          );\n        }\n      }\n      var cbs = vm._events[event];\n      if (cbs) {\n        cbs = cbs.length > 1 ? toArray(cbs) : cbs;\n        var args = toArray(arguments, 1);\n        for (var i = 0, l = cbs.length; i < l; i++) {\n          try {\n            cbs[i].apply(vm, args);\n          } catch (e) {\n            handleError(e, vm, 'event handler for \"' + event + '\"');\n          }\n        }\n      }\n      return vm;\n    };\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for resolving raw children VNodes into a slot object.\n   */\n  function resolveSlots(children, context) {\n    var slots = {};\n    if (!children) {\n      return slots;\n    }\n    for (var i = 0, l = children.length; i < l; i++) {\n      var child = children[i];\n      var data = child.data;\n      // remove slot attribute if the node is resolved as a Vue slot node\n      if (data && data.attrs && data.attrs.slot) {\n        delete data.attrs.slot;\n      }\n      // named slots should only be respected if the vnode was rendered in the\n      // same context.\n      if ((child.context === context || child.fnContext === context) && data && data.slot != null) {\n        var name = data.slot;\n        var slot = slots[name] || (slots[name] = []);\n        if (child.tag === \"template\") {\n          slot.push.apply(slot, child.children || []);\n        } else {\n          slot.push(child);\n        }\n      } else {\n        (slots.default || (slots.default = [])).push(child);\n      }\n    }\n    // ignore slots that contains only whitespace\n    for (var name$1 in slots) {\n      if (slots[name$1].every(isWhitespace)) {\n        delete slots[name$1];\n      }\n    }\n    return slots;\n  }\n\n  function isWhitespace(node) {\n    return (node.isComment && !node.asyncFactory) || node.text === \" \";\n  }\n\n  function resolveScopedSlots(\n    fns, // see flow/vnode\n    res\n  ) {\n    res = res || {};\n    for (var i = 0; i < fns.length; i++) {\n      if (Array.isArray(fns[i])) {\n        resolveScopedSlots(fns[i], res);\n      } else {\n        res[fns[i].key] = fns[i].fn;\n      }\n    }\n    return res;\n  }\n\n  /*  */\n\n  var activeInstance = null;\n  var isUpdatingChildComponent = false;\n\n  function initLifecycle(vm) {\n    var options = vm.$options;\n\n    // locate first non-abstract parent\n    var parent = options.parent;\n    if (parent && !options.abstract) {\n      while (parent.$options.abstract && parent.$parent) {\n        parent = parent.$parent;\n      }\n      parent.$children.push(vm);\n    }\n\n    vm.$parent = parent;\n    vm.$root = parent ? parent.$root : vm;\n\n    vm.$children = [];\n    vm.$refs = {};\n\n    vm._watcher = null;\n    vm._inactive = null;\n    vm._directInactive = false;\n    vm._isMounted = false;\n    vm._isDestroyed = false;\n    vm._isBeingDestroyed = false;\n  }\n\n  function lifecycleMixin(Vue) {\n    Vue.prototype._update = function(vnode, hydrating) {\n      var vm = this;\n      if (vm._isMounted) {\n        callHook(vm, \"beforeUpdate\");\n      }\n      var prevEl = vm.$el;\n      var prevVnode = vm._vnode;\n      var prevActiveInstance = activeInstance;\n      activeInstance = vm;\n      vm._vnode = vnode;\n      // Vue.prototype.__patch__ is injected in entry points\n      // based on the rendering backend used.\n      if (!prevVnode) {\n        // initial render\n        vm.$el = vm.__patch__(\n          vm.$el,\n          vnode,\n          hydrating,\n          false /* removeOnly */,\n          vm.$options._parentElm,\n          vm.$options._refElm\n        );\n        // no need for the ref nodes after initial patch\n        // this prevents keeping a detached DOM tree in memory (#5851)\n        vm.$options._parentElm = vm.$options._refElm = null;\n      } else {\n        // updates\n        vm.$el = vm.__patch__(prevVnode, vnode);\n      }\n      activeInstance = prevActiveInstance;\n      // update __vue__ reference\n      if (prevEl) {\n        prevEl.__vue__ = null;\n      }\n      if (vm.$el) {\n        vm.$el.__vue__ = vm;\n      }\n      // if parent is an HOC, update its $el as well\n      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n        vm.$parent.$el = vm.$el;\n      }\n      // updated hook is called by the scheduler to ensure that children are\n      // updated in a parent's updated hook.\n    };\n\n    Vue.prototype.$forceUpdate = function() {\n      var vm = this;\n      if (vm._watcher) {\n        vm._watcher.update();\n      }\n    };\n\n    Vue.prototype.$destroy = function() {\n      var vm = this;\n      if (vm._isBeingDestroyed) {\n        return;\n      }\n      callHook(vm, \"beforeDestroy\");\n      vm._isBeingDestroyed = true;\n      // remove self from parent\n      var parent = vm.$parent;\n      if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {\n        remove(parent.$children, vm);\n      }\n      // teardown watchers\n      if (vm._watcher) {\n        vm._watcher.teardown();\n      }\n      var i = vm._watchers.length;\n      while (i--) {\n        vm._watchers[i].teardown();\n      }\n      // remove reference from data ob\n      // frozen object may not have observer.\n      if (vm._data.__ob__) {\n        vm._data.__ob__.vmCount--;\n      }\n      // call the last hook...\n      vm._isDestroyed = true;\n      // invoke destroy hooks on current rendered tree\n      vm.__patch__(vm._vnode, null);\n      // fire destroyed hook\n      callHook(vm, \"destroyed\");\n      // turn off all instance listeners.\n      vm.$off();\n      // remove __vue__ reference\n      if (vm.$el) {\n        vm.$el.__vue__ = null;\n      }\n      // release circular reference (#6759)\n      if (vm.$vnode) {\n        vm.$vnode.parent = null;\n      }\n    };\n  }\n\n  function mountComponent(vm, el, hydrating) {\n    vm.$el = el;\n    if (!vm.$options.render) {\n      vm.$options.render = createEmptyVNode;\n      {\n        /* istanbul ignore if */\n        if ((vm.$options.template && vm.$options.template.charAt(0) !== \"#\") || vm.$options.el || el) {\n          warn(\n            \"You are using the runtime-only build of Vue where the template \" +\n              \"compiler is not available. Either pre-compile the templates into \" +\n              \"render functions, or use the compiler-included build.\",\n            vm\n          );\n        } else {\n          warn(\"Failed to mount component: template or render function not defined.\", vm);\n        }\n      }\n    }\n    callHook(vm, \"beforeMount\");\n\n    var updateComponent;\n    /* istanbul ignore if */\n    if (\"development\" !== \"production\" && config.performance && mark) {\n      updateComponent = function() {\n        var name = vm._name;\n        var id = vm._uid;\n        var startTag = \"vue-perf-start:\" + id;\n        var endTag = \"vue-perf-end:\" + id;\n\n        mark(startTag);\n        var vnode = vm._render();\n        mark(endTag);\n        measure(\"vue \" + name + \" render\", startTag, endTag);\n\n        mark(startTag);\n        vm._update(vnode, hydrating);\n        mark(endTag);\n        measure(\"vue \" + name + \" patch\", startTag, endTag);\n      };\n    } else {\n      updateComponent = function() {\n        vm._update(vm._render(), hydrating);\n      };\n    }\n\n    // we set this to vm._watcher inside the watcher's constructor\n    // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n    // component's mounted hook), which relies on vm._watcher being already defined\n    new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);\n    hydrating = false;\n\n    // manually mounted instance, call mounted on self\n    // mounted is called for render-created child components in its inserted hook\n    if (vm.$vnode == null) {\n      vm._isMounted = true;\n      callHook(vm, \"mounted\");\n    }\n    return vm;\n  }\n\n  function updateChildComponent(vm, propsData, listeners, parentVnode, renderChildren) {\n    {\n      isUpdatingChildComponent = true;\n    }\n\n    // determine whether component has slot children\n    // we need to do this before overwriting $options._renderChildren\n    var hasChildren = !!(\n      renderChildren || // has new static slots\n      vm.$options._renderChildren || // has old static slots\n      parentVnode.data.scopedSlots || // has new scoped slots\n      vm.$scopedSlots !== emptyObject\n    ); // has old scoped slots\n\n    vm.$options._parentVnode = parentVnode;\n    vm.$vnode = parentVnode; // update vm's placeholder node without re-render\n\n    if (vm._vnode) {\n      // update child tree's parent\n      vm._vnode.parent = parentVnode;\n    }\n    vm.$options._renderChildren = renderChildren;\n\n    // update $attrs and $listeners hash\n    // these are also reactive so they may trigger child update if the child\n    // used them during render\n    vm.$attrs = parentVnode.data.attrs || emptyObject;\n    vm.$listeners = listeners || emptyObject;\n\n    // update props\n    if (propsData && vm.$options.props) {\n      toggleObserving(false);\n      var props = vm._props;\n      var propKeys = vm.$options._propKeys || [];\n      for (var i = 0; i < propKeys.length; i++) {\n        var key = propKeys[i];\n        var propOptions = vm.$options.props; // wtf flow?\n        props[key] = validateProp(key, propOptions, propsData, vm);\n      }\n      toggleObserving(true);\n      // keep a copy of raw propsData\n      vm.$options.propsData = propsData;\n    }\n\n    // update listeners\n    listeners = listeners || emptyObject;\n    var oldListeners = vm.$options._parentListeners;\n    vm.$options._parentListeners = listeners;\n    updateComponentListeners(vm, listeners, oldListeners);\n\n    // resolve slots + force update if has children\n    if (hasChildren) {\n      vm.$slots = resolveSlots(renderChildren, parentVnode.context);\n      vm.$forceUpdate();\n    }\n\n    {\n      isUpdatingChildComponent = false;\n    }\n  }\n\n  function isInInactiveTree(vm) {\n    while (vm && (vm = vm.$parent)) {\n      if (vm._inactive) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  function activateChildComponent(vm, direct) {\n    if (direct) {\n      vm._directInactive = false;\n      if (isInInactiveTree(vm)) {\n        return;\n      }\n    } else if (vm._directInactive) {\n      return;\n    }\n    if (vm._inactive || vm._inactive === null) {\n      vm._inactive = false;\n      for (var i = 0; i < vm.$children.length; i++) {\n        activateChildComponent(vm.$children[i]);\n      }\n      callHook(vm, \"activated\");\n    }\n  }\n\n  function deactivateChildComponent(vm, direct) {\n    if (direct) {\n      vm._directInactive = true;\n      if (isInInactiveTree(vm)) {\n        return;\n      }\n    }\n    if (!vm._inactive) {\n      vm._inactive = true;\n      for (var i = 0; i < vm.$children.length; i++) {\n        deactivateChildComponent(vm.$children[i]);\n      }\n      callHook(vm, \"deactivated\");\n    }\n  }\n\n  function callHook(vm, hook) {\n    // #7573 disable dep collection when invoking lifecycle hooks\n    pushTarget();\n    var handlers = vm.$options[hook];\n    if (handlers) {\n      for (var i = 0, j = handlers.length; i < j; i++) {\n        try {\n          handlers[i].call(vm);\n        } catch (e) {\n          handleError(e, vm, hook + \" hook\");\n        }\n      }\n    }\n    if (vm._hasHookEvent) {\n      vm.$emit(\"hook:\" + hook);\n    }\n    popTarget();\n  }\n\n  /*  */\n\n  var MAX_UPDATE_COUNT = 100;\n\n  var queue = [];\n  var activatedChildren = [];\n  var has = {};\n  var circular = {};\n  var waiting = false;\n  var flushing = false;\n  var index = 0;\n\n  /**\n   * Reset the scheduler's state.\n   */\n  function resetSchedulerState() {\n    index = queue.length = activatedChildren.length = 0;\n    has = {};\n    {\n      circular = {};\n    }\n    waiting = flushing = false;\n  }\n\n  /**\n   * Flush both queues and run the watchers.\n   */\n  function flushSchedulerQueue() {\n    flushing = true;\n    var watcher, id;\n\n    // Sort queue before flush.\n    // This ensures that:\n    // 1. Components are updated from parent to child. (because parent is always\n    //    created before the child)\n    // 2. A component's user watchers are run before its render watcher (because\n    //    user watchers are created before the render watcher)\n    // 3. If a component is destroyed during a parent component's watcher run,\n    //    its watchers can be skipped.\n    queue.sort(function(a, b) {\n      return a.id - b.id;\n    });\n\n    // do not cache length because more watchers might be pushed\n    // as we run existing watchers\n    for (index = 0; index < queue.length; index++) {\n      watcher = queue[index];\n      id = watcher.id;\n      has[id] = null;\n      watcher.run();\n      // in dev build, check and stop circular updates.\n      if (\"development\" !== \"production\" && has[id] != null) {\n        circular[id] = (circular[id] || 0) + 1;\n        if (circular[id] > MAX_UPDATE_COUNT) {\n          warn(\n            \"You may have an infinite update loop \" +\n              (watcher.user\n                ? 'in watcher with expression \"' + watcher.expression + '\"'\n                : \"in a component render function.\"),\n            watcher.vm\n          );\n          break;\n        }\n      }\n    }\n\n    // keep copies of post queues before resetting state\n    var activatedQueue = activatedChildren.slice();\n    var updatedQueue = queue.slice();\n\n    resetSchedulerState();\n\n    // call component updated and activated hooks\n    callActivatedHooks(activatedQueue);\n    callUpdatedHooks(updatedQueue);\n\n    // devtool hook\n    /* istanbul ignore if */\n    if (devtools && config.devtools) {\n      devtools.emit(\"flush\");\n    }\n  }\n\n  function callUpdatedHooks(queue) {\n    var i = queue.length;\n    while (i--) {\n      var watcher = queue[i];\n      var vm = watcher.vm;\n      if (vm._watcher === watcher && vm._isMounted) {\n        callHook(vm, \"updated\");\n      }\n    }\n  }\n\n  /**\n   * Queue a kept-alive component that was activated during patch.\n   * The queue will be processed after the entire tree has been patched.\n   */\n  function queueActivatedComponent(vm) {\n    // setting _inactive to false here so that a render function can\n    // rely on checking whether it's in an inactive tree (e.g. router-view)\n    vm._inactive = false;\n    activatedChildren.push(vm);\n  }\n\n  function callActivatedHooks(queue) {\n    for (var i = 0; i < queue.length; i++) {\n      queue[i]._inactive = true;\n      activateChildComponent(queue[i], true /* true */);\n    }\n  }\n\n  /**\n   * Push a watcher into the watcher queue.\n   * Jobs with duplicate IDs will be skipped unless it's\n   * pushed when the queue is being flushed.\n   */\n  function queueWatcher(watcher) {\n    var id = watcher.id;\n    if (has[id] == null) {\n      has[id] = true;\n      if (!flushing) {\n        queue.push(watcher);\n      } else {\n        // if already flushing, splice the watcher based on its id\n        // if already past its id, it will be run next immediately.\n        var i = queue.length - 1;\n        while (i > index && queue[i].id > watcher.id) {\n          i--;\n        }\n        queue.splice(i + 1, 0, watcher);\n      }\n      // queue the flush\n      if (!waiting) {\n        waiting = true;\n        nextTick(flushSchedulerQueue);\n      }\n    }\n  }\n\n  /*  */\n\n  var uid$1 = 0;\n\n  /**\n   * A watcher parses an expression, collects dependencies,\n   * and fires callback when the expression value changes.\n   * This is used for both the $watch() api and directives.\n   */\n  var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {\n    this.vm = vm;\n    if (isRenderWatcher) {\n      vm._watcher = this;\n    }\n    vm._watchers.push(this);\n    // options\n    if (options) {\n      this.deep = !!options.deep;\n      this.user = !!options.user;\n      this.lazy = !!options.lazy;\n      this.sync = !!options.sync;\n    } else {\n      this.deep = this.user = this.lazy = this.sync = false;\n    }\n    this.cb = cb;\n    this.id = ++uid$1; // uid for batching\n    this.active = true;\n    this.dirty = this.lazy; // for lazy watchers\n    this.deps = [];\n    this.newDeps = [];\n    this.depIds = new _Set();\n    this.newDepIds = new _Set();\n    this.expression = expOrFn.toString();\n    // parse expression for getter\n    if (typeof expOrFn === \"function\") {\n      this.getter = expOrFn;\n    } else {\n      this.getter = parsePath(expOrFn);\n      if (!this.getter) {\n        this.getter = function() {};\n        \"development\" !== \"production\" &&\n          warn(\n            'Failed watching path: \"' +\n              expOrFn +\n              '\" ' +\n              \"Watcher only accepts simple dot-delimited paths. \" +\n              \"For full control, use a function instead.\",\n            vm\n          );\n      }\n    }\n    this.value = this.lazy ? undefined : this.get();\n  };\n\n  /**\n   * Evaluate the getter, and re-collect dependencies.\n   */\n  Watcher.prototype.get = function get() {\n    pushTarget(this);\n    var value;\n    var vm = this.vm;\n    try {\n      value = this.getter.call(vm, vm);\n    } catch (e) {\n      if (this.user) {\n        handleError(e, vm, 'getter for watcher \"' + this.expression + '\"');\n      } else {\n        throw e;\n      }\n    } finally {\n      // \"touch\" every property so they are all tracked as\n      // dependencies for deep watching\n      if (this.deep) {\n        traverse(value);\n      }\n      popTarget();\n      this.cleanupDeps();\n    }\n    return value;\n  };\n\n  /**\n   * Add a dependency to this directive.\n   */\n  Watcher.prototype.addDep = function addDep(dep) {\n    var id = dep.id;\n    if (!this.newDepIds.has(id)) {\n      this.newDepIds.add(id);\n      this.newDeps.push(dep);\n      if (!this.depIds.has(id)) {\n        dep.addSub(this);\n      }\n    }\n  };\n\n  /**\n   * Clean up for dependency collection.\n   */\n  Watcher.prototype.cleanupDeps = function cleanupDeps() {\n    var this$1 = this;\n\n    var i = this.deps.length;\n    while (i--) {\n      var dep = this$1.deps[i];\n      if (!this$1.newDepIds.has(dep.id)) {\n        dep.removeSub(this$1);\n      }\n    }\n    var tmp = this.depIds;\n    this.depIds = this.newDepIds;\n    this.newDepIds = tmp;\n    this.newDepIds.clear();\n    tmp = this.deps;\n    this.deps = this.newDeps;\n    this.newDeps = tmp;\n    this.newDeps.length = 0;\n  };\n\n  /**\n   * Subscriber interface.\n   * Will be called when a dependency changes.\n   */\n  Watcher.prototype.update = function update() {\n    /* istanbul ignore else */\n    if (this.lazy) {\n      this.dirty = true;\n    } else if (this.sync) {\n      this.run();\n    } else {\n      queueWatcher(this);\n    }\n  };\n\n  /**\n   * Scheduler job interface.\n   * Will be called by the scheduler.\n   */\n  Watcher.prototype.run = function run() {\n    if (this.active) {\n      var value = this.get();\n      if (\n        value !== this.value ||\n        // Deep watchers and watchers on Object/Arrays should fire even\n        // when the value is the same, because the value may\n        // have mutated.\n        isObject(value) ||\n        this.deep\n      ) {\n        // set new value\n        var oldValue = this.value;\n        this.value = value;\n        if (this.user) {\n          try {\n            this.cb.call(this.vm, value, oldValue);\n          } catch (e) {\n            handleError(e, this.vm, 'callback for watcher \"' + this.expression + '\"');\n          }\n        } else {\n          this.cb.call(this.vm, value, oldValue);\n        }\n      }\n    }\n  };\n\n  /**\n   * Evaluate the value of the watcher.\n   * This only gets called for lazy watchers.\n   */\n  Watcher.prototype.evaluate = function evaluate() {\n    this.value = this.get();\n    this.dirty = false;\n  };\n\n  /**\n   * Depend on all deps collected by this watcher.\n   */\n  Watcher.prototype.depend = function depend() {\n    var this$1 = this;\n\n    var i = this.deps.length;\n    while (i--) {\n      this$1.deps[i].depend();\n    }\n  };\n\n  /**\n   * Remove self from all dependencies' subscriber list.\n   */\n  Watcher.prototype.teardown = function teardown() {\n    var this$1 = this;\n\n    if (this.active) {\n      // remove self from vm's watcher list\n      // this is a somewhat expensive operation so we skip it\n      // if the vm is being destroyed.\n      if (!this.vm._isBeingDestroyed) {\n        remove(this.vm._watchers, this);\n      }\n      var i = this.deps.length;\n      while (i--) {\n        this$1.deps[i].removeSub(this$1);\n      }\n      this.active = false;\n    }\n  };\n\n  /*  */\n\n  var sharedPropertyDefinition = {\n    enumerable: true,\n    configurable: true,\n    get: noop,\n    set: noop\n  };\n\n  function proxy(target, sourceKey, key) {\n    sharedPropertyDefinition.get = function proxyGetter() {\n      return this[sourceKey][key];\n    };\n    sharedPropertyDefinition.set = function proxySetter(val) {\n      this[sourceKey][key] = val;\n    };\n    Object.defineProperty(target, key, sharedPropertyDefinition);\n  }\n\n  function initState(vm) {\n    vm._watchers = [];\n    var opts = vm.$options;\n    if (opts.props) {\n      initProps(vm, opts.props);\n    }\n    if (opts.methods) {\n      initMethods(vm, opts.methods);\n    }\n    if (opts.data) {\n      initData(vm);\n    } else {\n      observe((vm._data = {}), true /* asRootData */);\n    }\n    if (opts.computed) {\n      initComputed(vm, opts.computed);\n    }\n    if (opts.watch && opts.watch !== nativeWatch) {\n      initWatch(vm, opts.watch);\n    }\n  }\n\n  function initProps(vm, propsOptions) {\n    var propsData = vm.$options.propsData || {};\n    var props = (vm._props = {});\n    // cache prop keys so that future props updates can iterate using Array\n    // instead of dynamic object key enumeration.\n    var keys = (vm.$options._propKeys = []);\n    var isRoot = !vm.$parent;\n    // root instance props should be converted\n    if (!isRoot) {\n      toggleObserving(false);\n    }\n    var loop = function(key) {\n      keys.push(key);\n      var value = validateProp(key, propsOptions, propsData, vm);\n      /* istanbul ignore else */\n      {\n        var hyphenatedKey = hyphenate(key);\n        if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) {\n          warn('\"' + hyphenatedKey + '\" is a reserved attribute and cannot be used as component prop.', vm);\n        }\n        defineReactive(props, key, value, function() {\n          if (vm.$parent && !isUpdatingChildComponent) {\n            warn(\n              \"Avoid mutating a prop directly since the value will be \" +\n                \"overwritten whenever the parent component re-renders. \" +\n                \"Instead, use a data or computed property based on the prop's \" +\n                'value. Prop being mutated: \"' +\n                key +\n                '\"',\n              vm\n            );\n          }\n        });\n      }\n      // static props are already proxied on the component's prototype\n      // during Vue.extend(). We only need to proxy props defined at\n      // instantiation here.\n      if (!(key in vm)) {\n        proxy(vm, \"_props\", key);\n      }\n    };\n\n    for (var key in propsOptions) loop(key);\n    toggleObserving(true);\n  }\n\n  function initData(vm) {\n    var data = vm.$options.data;\n    data = vm._data = typeof data === \"function\" ? getData(data, vm) : data || {};\n    if (!isPlainObject(data)) {\n      data = {};\n      \"development\" !== \"production\" &&\n        warn(\n          \"data functions should return an object:\\n\" +\n            \"https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function\",\n          vm\n        );\n    }\n    // proxy data on instance\n    var keys = Object.keys(data);\n    var props = vm.$options.props;\n    var methods = vm.$options.methods;\n    var i = keys.length;\n    while (i--) {\n      var key = keys[i];\n      {\n        if (methods && hasOwn(methods, key)) {\n          warn('Method \"' + key + '\" has already been defined as a data property.', vm);\n        }\n      }\n      if (props && hasOwn(props, key)) {\n        \"development\" !== \"production\" &&\n          warn(\n            'The data property \"' + key + '\" is already declared as a prop. ' + \"Use prop default value instead.\",\n            vm\n          );\n      } else if (!isReserved(key)) {\n        proxy(vm, \"_data\", key);\n      }\n    }\n    // observe data\n    observe(data, true /* asRootData */);\n  }\n\n  function getData(data, vm) {\n    // #7573 disable dep collection when invoking data getters\n    pushTarget();\n    try {\n      return data.call(vm, vm);\n    } catch (e) {\n      handleError(e, vm, \"data()\");\n      return {};\n    } finally {\n      popTarget();\n    }\n  }\n\n  var computedWatcherOptions = { lazy: true };\n\n  function initComputed(vm, computed) {\n    // $flow-disable-line\n    var watchers = (vm._computedWatchers = Object.create(null));\n    // computed properties are just getters during SSR\n    var isSSR = isServerRendering();\n\n    for (var key in computed) {\n      var userDef = computed[key];\n      var getter = typeof userDef === \"function\" ? userDef : userDef.get;\n      if (\"development\" !== \"production\" && getter == null) {\n        warn('Getter is missing for computed property \"' + key + '\".', vm);\n      }\n\n      if (!isSSR) {\n        // create internal watcher for the computed property.\n        watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions);\n      }\n\n      // component-defined computed properties are already defined on the\n      // component prototype. We only need to define computed properties defined\n      // at instantiation here.\n      if (!(key in vm)) {\n        defineComputed(vm, key, userDef);\n      } else {\n        if (key in vm.$data) {\n          warn('The computed property \"' + key + '\" is already defined in data.', vm);\n        } else if (vm.$options.props && key in vm.$options.props) {\n          warn('The computed property \"' + key + '\" is already defined as a prop.', vm);\n        }\n      }\n    }\n  }\n\n  function defineComputed(target, key, userDef) {\n    var shouldCache = !isServerRendering();\n    if (typeof userDef === \"function\") {\n      sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef;\n      sharedPropertyDefinition.set = noop;\n    } else {\n      sharedPropertyDefinition.get = userDef.get\n        ? shouldCache && userDef.cache !== false\n          ? createComputedGetter(key)\n          : userDef.get\n        : noop;\n      sharedPropertyDefinition.set = userDef.set ? userDef.set : noop;\n    }\n    if (\"development\" !== \"production\" && sharedPropertyDefinition.set === noop) {\n      sharedPropertyDefinition.set = function() {\n        warn('Computed property \"' + key + '\" was assigned to but it has no setter.', this);\n      };\n    }\n    Object.defineProperty(target, key, sharedPropertyDefinition);\n  }\n\n  function createComputedGetter(key) {\n    return function computedGetter() {\n      var watcher = this._computedWatchers && this._computedWatchers[key];\n      if (watcher) {\n        if (watcher.dirty) {\n          watcher.evaluate();\n        }\n        if (Dep.target) {\n          watcher.depend();\n        }\n        return watcher.value;\n      }\n    };\n  }\n\n  function initMethods(vm, methods) {\n    var props = vm.$options.props;\n    for (var key in methods) {\n      {\n        if (methods[key] == null) {\n          warn(\n            'Method \"' +\n              key +\n              '\" has an undefined value in the component definition. ' +\n              \"Did you reference the function correctly?\",\n            vm\n          );\n        }\n        if (props && hasOwn(props, key)) {\n          warn('Method \"' + key + '\" has already been defined as a prop.', vm);\n        }\n        if (key in vm && isReserved(key)) {\n          warn(\n            'Method \"' +\n              key +\n              '\" conflicts with an existing Vue instance method. ' +\n              \"Avoid defining component methods that start with _ or $.\"\n          );\n        }\n      }\n      vm[key] = methods[key] == null ? noop : bind(methods[key], vm);\n    }\n  }\n\n  function initWatch(vm, watch) {\n    for (var key in watch) {\n      var handler = watch[key];\n      if (Array.isArray(handler)) {\n        for (var i = 0; i < handler.length; i++) {\n          createWatcher(vm, key, handler[i]);\n        }\n      } else {\n        createWatcher(vm, key, handler);\n      }\n    }\n  }\n\n  function createWatcher(vm, expOrFn, handler, options) {\n    if (isPlainObject(handler)) {\n      options = handler;\n      handler = handler.handler;\n    }\n    if (typeof handler === \"string\") {\n      handler = vm[handler];\n    }\n    return vm.$watch(expOrFn, handler, options);\n  }\n\n  function stateMixin(Vue) {\n    // flow somehow has problems with directly declared definition object\n    // when using Object.defineProperty, so we have to procedurally build up\n    // the object here.\n    var dataDef = {};\n    dataDef.get = function() {\n      return this._data;\n    };\n    var propsDef = {};\n    propsDef.get = function() {\n      return this._props;\n    };\n    {\n      dataDef.set = function(newData) {\n        warn(\"Avoid replacing instance root $data. \" + \"Use nested data properties instead.\", this);\n      };\n      propsDef.set = function() {\n        warn(\"$props is readonly.\", this);\n      };\n    }\n    Object.defineProperty(Vue.prototype, \"$data\", dataDef);\n    Object.defineProperty(Vue.prototype, \"$props\", propsDef);\n\n    Vue.prototype.$set = set;\n    Vue.prototype.$delete = del;\n\n    Vue.prototype.$watch = function(expOrFn, cb, options) {\n      var vm = this;\n      if (isPlainObject(cb)) {\n        return createWatcher(vm, expOrFn, cb, options);\n      }\n      options = options || {};\n      options.user = true;\n      var watcher = new Watcher(vm, expOrFn, cb, options);\n      if (options.immediate) {\n        cb.call(vm, watcher.value);\n      }\n      return function unwatchFn() {\n        watcher.teardown();\n      };\n    };\n  }\n\n  /*  */\n\n  function initProvide(vm) {\n    var provide = vm.$options.provide;\n    if (provide) {\n      vm._provided = typeof provide === \"function\" ? provide.call(vm) : provide;\n    }\n  }\n\n  function initInjections(vm) {\n    var result = resolveInject(vm.$options.inject, vm);\n    if (result) {\n      toggleObserving(false);\n      Object.keys(result).forEach(function(key) {\n        /* istanbul ignore else */\n        {\n          defineReactive(vm, key, result[key], function() {\n            warn(\n              \"Avoid mutating an injected value directly since the changes will be \" +\n                \"overwritten whenever the provided component re-renders. \" +\n                'injection being mutated: \"' +\n                key +\n                '\"',\n              vm\n            );\n          });\n        }\n      });\n      toggleObserving(true);\n    }\n  }\n\n  function resolveInject(inject, vm) {\n    if (inject) {\n      // inject is :any because flow is not smart enough to figure out cached\n      var result = Object.create(null);\n      var keys = hasSymbol\n        ? Reflect.ownKeys(inject).filter(function(key) {\n            /* istanbul ignore next */\n            return Object.getOwnPropertyDescriptor(inject, key).enumerable;\n          })\n        : Object.keys(inject);\n\n      for (var i = 0; i < keys.length; i++) {\n        var key = keys[i];\n        var provideKey = inject[key].from;\n        var source = vm;\n        while (source) {\n          if (source._provided && hasOwn(source._provided, provideKey)) {\n            result[key] = source._provided[provideKey];\n            break;\n          }\n          source = source.$parent;\n        }\n        if (!source) {\n          if (\"default\" in inject[key]) {\n            var provideDefault = inject[key].default;\n            result[key] = typeof provideDefault === \"function\" ? provideDefault.call(vm) : provideDefault;\n          } else {\n            warn('Injection \"' + key + '\" not found', vm);\n          }\n        }\n      }\n      return result;\n    }\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for rendering v-for lists.\n   */\n  function renderList(val, render) {\n    var ret, i, l, keys, key;\n    if (Array.isArray(val) || typeof val === \"string\") {\n      ret = new Array(val.length);\n      for (i = 0, l = val.length; i < l; i++) {\n        ret[i] = render(val[i], i);\n      }\n    } else if (typeof val === \"number\") {\n      ret = new Array(val);\n      for (i = 0; i < val; i++) {\n        ret[i] = render(i + 1, i);\n      }\n    } else if (isObject(val)) {\n      keys = Object.keys(val);\n      ret = new Array(keys.length);\n      for (i = 0, l = keys.length; i < l; i++) {\n        key = keys[i];\n        ret[i] = render(val[key], key, i);\n      }\n    }\n    if (isDef(ret)) {\n      ret._isVList = true;\n    }\n    return ret;\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for rendering <slot>\n   */\n  function renderSlot(name, fallback, props, bindObject) {\n    var scopedSlotFn = this.$scopedSlots[name];\n    var nodes;\n    if (scopedSlotFn) {\n      // scoped slot\n      props = props || {};\n      if (bindObject) {\n        if (\"development\" !== \"production\" && !isObject(bindObject)) {\n          warn(\"slot v-bind without argument expects an Object\", this);\n        }\n        props = extend(extend({}, bindObject), props);\n      }\n      nodes = scopedSlotFn(props) || fallback;\n    } else {\n      var slotNodes = this.$slots[name];\n      // warn duplicate slot usage\n      if (slotNodes) {\n        if (\"development\" !== \"production\" && slotNodes._rendered) {\n          warn(\n            'Duplicate presence of slot \"' +\n              name +\n              '\" found in the same render tree ' +\n              \"- this will likely cause render errors.\",\n            this\n          );\n        }\n        slotNodes._rendered = true;\n      }\n      nodes = slotNodes || fallback;\n    }\n\n    var target = props && props.slot;\n    if (target) {\n      return this.$createElement(\"template\", { slot: target }, nodes);\n    } else {\n      return nodes;\n    }\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for resolving filters\n   */\n  function resolveFilter(id) {\n    return resolveAsset(this.$options, \"filters\", id, true) || identity;\n  }\n\n  /*  */\n\n  function isKeyNotMatch(expect, actual) {\n    if (Array.isArray(expect)) {\n      return expect.indexOf(actual) === -1;\n    } else {\n      return expect !== actual;\n    }\n  }\n\n  /**\n   * Runtime helper for checking keyCodes from config.\n   * exposed as Vue.prototype._k\n   * passing in eventKeyName as last argument separately for backwards compat\n   */\n  function checkKeyCodes(eventKeyCode, key, builtInKeyCode, eventKeyName, builtInKeyName) {\n    var mappedKeyCode = config.keyCodes[key] || builtInKeyCode;\n    if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {\n      return isKeyNotMatch(builtInKeyName, eventKeyName);\n    } else if (mappedKeyCode) {\n      return isKeyNotMatch(mappedKeyCode, eventKeyCode);\n    } else if (eventKeyName) {\n      return hyphenate(eventKeyName) !== key;\n    }\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for merging v-bind=\"object\" into a VNode's data.\n   */\n  function bindObjectProps(data, tag, value, asProp, isSync) {\n    if (value) {\n      if (!isObject(value)) {\n        \"development\" !== \"production\" && warn(\"v-bind without argument expects an Object or Array value\", this);\n      } else {\n        if (Array.isArray(value)) {\n          value = toObject(value);\n        }\n        var hash;\n        var loop = function(key) {\n          if (key === \"class\" || key === \"style\" || isReservedAttribute(key)) {\n            hash = data;\n          } else {\n            var type = data.attrs && data.attrs.type;\n            hash =\n              asProp || config.mustUseProp(tag, type, key)\n                ? data.domProps || (data.domProps = {})\n                : data.attrs || (data.attrs = {});\n          }\n          if (!(key in hash)) {\n            hash[key] = value[key];\n\n            if (isSync) {\n              var on = data.on || (data.on = {});\n              on[\"update:\" + key] = function($event) {\n                value[key] = $event;\n              };\n            }\n          }\n        };\n\n        for (var key in value) loop(key);\n      }\n    }\n    return data;\n  }\n\n  /*  */\n\n  /**\n   * Runtime helper for rendering static trees.\n   */\n  function renderStatic(index, isInFor) {\n    var cached = this._staticTrees || (this._staticTrees = []);\n    var tree = cached[index];\n    // if has already-rendered static tree and not inside v-for,\n    // we can reuse the same tree.\n    if (tree && !isInFor) {\n      return tree;\n    }\n    // otherwise, render a fresh tree.\n    tree = cached[index] = this.$options.staticRenderFns[index].call(\n      this._renderProxy,\n      null,\n      this // for render fns generated for functional component templates\n    );\n    markStatic(tree, \"__static__\" + index, false);\n    return tree;\n  }\n\n  /**\n   * Runtime helper for v-once.\n   * Effectively it means marking the node as static with a unique key.\n   */\n  function markOnce(tree, index, key) {\n    markStatic(tree, \"__once__\" + index + (key ? \"_\" + key : \"\"), true);\n    return tree;\n  }\n\n  function markStatic(tree, key, isOnce) {\n    if (Array.isArray(tree)) {\n      for (var i = 0; i < tree.length; i++) {\n        if (tree[i] && typeof tree[i] !== \"string\") {\n          markStaticNode(tree[i], key + \"_\" + i, isOnce);\n        }\n      }\n    } else {\n      markStaticNode(tree, key, isOnce);\n    }\n  }\n\n  function markStaticNode(node, key, isOnce) {\n    node.isStatic = true;\n    node.key = key;\n    node.isOnce = isOnce;\n  }\n\n  /*  */\n\n  function bindObjectListeners(data, value) {\n    if (value) {\n      if (!isPlainObject(value)) {\n        \"development\" !== \"production\" && warn(\"v-on without argument expects an Object value\", this);\n      } else {\n        var on = (data.on = data.on ? extend({}, data.on) : {});\n        for (var key in value) {\n          var existing = on[key];\n          var ours = value[key];\n          on[key] = existing ? [].concat(existing, ours) : ours;\n        }\n      }\n    }\n    return data;\n  }\n\n  /*  */\n\n  function installRenderHelpers(target) {\n    target._o = markOnce;\n    target._n = toNumber;\n    target._s = toString;\n    target._l = renderList;\n    target._t = renderSlot;\n    target._q = looseEqual;\n    target._i = looseIndexOf;\n    target._m = renderStatic;\n    target._f = resolveFilter;\n    target._k = checkKeyCodes;\n    target._b = bindObjectProps;\n    target._v = createTextVNode;\n    target._e = createEmptyVNode;\n    target._u = resolveScopedSlots;\n    target._g = bindObjectListeners;\n  }\n\n  /*  */\n\n  function FunctionalRenderContext(data, props, children, parent, Ctor) {\n    var options = Ctor.options;\n    // ensure the createElement function in functional components\n    // gets a unique context - this is necessary for correct named slot check\n    var contextVm;\n    if (hasOwn(parent, \"_uid\")) {\n      contextVm = Object.create(parent);\n      // $flow-disable-line\n      contextVm._original = parent;\n    } else {\n      // the context vm passed in is a functional context as well.\n      // in this case we want to make sure we are able to get a hold to the\n      // real context instance.\n      contextVm = parent;\n      // $flow-disable-line\n      parent = parent._original;\n    }\n    var isCompiled = isTrue(options._compiled);\n    var needNormalization = !isCompiled;\n\n    this.data = data;\n    this.props = props;\n    this.children = children;\n    this.parent = parent;\n    this.listeners = data.on || emptyObject;\n    this.injections = resolveInject(options.inject, parent);\n    this.slots = function() {\n      return resolveSlots(children, parent);\n    };\n\n    // support for compiled functional template\n    if (isCompiled) {\n      // exposing $options for renderStatic()\n      this.$options = options;\n      // pre-resolve slots for renderSlot()\n      this.$slots = this.slots();\n      this.$scopedSlots = data.scopedSlots || emptyObject;\n    }\n\n    if (options._scopeId) {\n      this._c = function(a, b, c, d) {\n        var vnode = createElement(contextVm, a, b, c, d, needNormalization);\n        if (vnode && !Array.isArray(vnode)) {\n          vnode.fnScopeId = options._scopeId;\n          vnode.fnContext = parent;\n        }\n        return vnode;\n      };\n    } else {\n      this._c = function(a, b, c, d) {\n        return createElement(contextVm, a, b, c, d, needNormalization);\n      };\n    }\n  }\n\n  installRenderHelpers(FunctionalRenderContext.prototype);\n\n  function createFunctionalComponent(Ctor, propsData, data, contextVm, children) {\n    var options = Ctor.options;\n    var props = {};\n    var propOptions = options.props;\n    if (isDef(propOptions)) {\n      for (var key in propOptions) {\n        props[key] = validateProp(key, propOptions, propsData || emptyObject);\n      }\n    } else {\n      if (isDef(data.attrs)) {\n        mergeProps(props, data.attrs);\n      }\n      if (isDef(data.props)) {\n        mergeProps(props, data.props);\n      }\n    }\n\n    var renderContext = new FunctionalRenderContext(data, props, children, contextVm, Ctor);\n\n    var vnode = options.render.call(null, renderContext._c, renderContext);\n\n    if (vnode instanceof VNode) {\n      return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options);\n    } else if (Array.isArray(vnode)) {\n      var vnodes = normalizeChildren(vnode) || [];\n      var res = new Array(vnodes.length);\n      for (var i = 0; i < vnodes.length; i++) {\n        res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options);\n      }\n      return res;\n    }\n  }\n\n  function cloneAndMarkFunctionalResult(vnode, data, contextVm, options) {\n    // #7817 clone node before setting fnContext, otherwise if the node is reused\n    // (e.g. it was from a cached normal slot) the fnContext causes named slots\n    // that should not be matched to match.\n    var clone = cloneVNode(vnode);\n    clone.fnContext = contextVm;\n    clone.fnOptions = options;\n    if (data.slot) {\n      (clone.data || (clone.data = {})).slot = data.slot;\n    }\n    return clone;\n  }\n\n  function mergeProps(to, from) {\n    for (var key in from) {\n      to[camelize(key)] = from[key];\n    }\n  }\n\n  /*  */\n\n  // Register the component hook to weex native render engine.\n  // The hook will be triggered by native, not javascript.\n\n  // Updates the state of the component to weex native render engine.\n\n  /*  */\n\n  // https://github.com/Hanks10100/weex-native-directive/tree/master/component\n\n  // listening on native callback\n\n  /*  */\n\n  /*  */\n\n  // inline hooks to be invoked on component VNodes during patch\n  var componentVNodeHooks = {\n    init: function init(vnode, hydrating, parentElm, refElm) {\n      if (vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive) {\n        // kept-alive components, treat as a patch\n        var mountedNode = vnode; // work around flow\n        componentVNodeHooks.prepatch(mountedNode, mountedNode);\n      } else {\n        var child = (vnode.componentInstance = createComponentInstanceForVnode(\n          vnode,\n          activeInstance,\n          parentElm,\n          refElm\n        ));\n        child.$mount(hydrating ? vnode.elm : undefined, hydrating);\n      }\n    },\n\n    prepatch: function prepatch(oldVnode, vnode) {\n      var options = vnode.componentOptions;\n      var child = (vnode.componentInstance = oldVnode.componentInstance);\n      updateChildComponent(\n        child,\n        options.propsData, // updated props\n        options.listeners, // updated listeners\n        vnode, // new parent vnode\n        options.children // new children\n      );\n    },\n\n    insert: function insert(vnode) {\n      var context = vnode.context;\n      var componentInstance = vnode.componentInstance;\n      if (!componentInstance._isMounted) {\n        componentInstance._isMounted = true;\n        callHook(componentInstance, \"mounted\");\n      }\n      if (vnode.data.keepAlive) {\n        if (context._isMounted) {\n          // vue-router#1212\n          // During updates, a kept-alive component's child components may\n          // change, so directly walking the tree here may call activated hooks\n          // on incorrect children. Instead we push them into a queue which will\n          // be processed after the whole patch process ended.\n          queueActivatedComponent(componentInstance);\n        } else {\n          activateChildComponent(componentInstance, true /* direct */);\n        }\n      }\n    },\n\n    destroy: function destroy(vnode) {\n      var componentInstance = vnode.componentInstance;\n      if (!componentInstance._isDestroyed) {\n        if (!vnode.data.keepAlive) {\n          componentInstance.$destroy();\n        } else {\n          deactivateChildComponent(componentInstance, true /* direct */);\n        }\n      }\n    }\n  };\n\n  var hooksToMerge = Object.keys(componentVNodeHooks);\n\n  function createComponent(Ctor, data, context, children, tag) {\n    if (isUndef(Ctor)) {\n      return;\n    }\n\n    var baseCtor = context.$options._base;\n\n    // plain options object: turn it into a constructor\n    if (isObject(Ctor)) {\n      Ctor = baseCtor.extend(Ctor);\n    }\n\n    // if at this stage it's not a constructor or an async component factory,\n    // reject.\n    if (typeof Ctor !== \"function\") {\n      {\n        warn(\"Invalid Component definition: \" + String(Ctor), context);\n      }\n      return;\n    }\n\n    // async component\n    var asyncFactory;\n    if (isUndef(Ctor.cid)) {\n      asyncFactory = Ctor;\n      Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);\n      if (Ctor === undefined) {\n        // return a placeholder node for async component, which is rendered\n        // as a comment node but preserves all the raw information for the node.\n        // the information will be used for async server-rendering and hydration.\n        return createAsyncPlaceholder(asyncFactory, data, context, children, tag);\n      }\n    }\n\n    data = data || {};\n\n    // resolve constructor options in case global mixins are applied after\n    // component constructor creation\n    resolveConstructorOptions(Ctor);\n\n    // transform component v-model data into props & events\n    if (isDef(data.model)) {\n      transformModel(Ctor.options, data);\n    }\n\n    // extract props\n    var propsData = extractPropsFromVNodeData(data, Ctor, tag);\n\n    // functional component\n    if (isTrue(Ctor.options.functional)) {\n      return createFunctionalComponent(Ctor, propsData, data, context, children);\n    }\n\n    // extract listeners, since these needs to be treated as\n    // child component listeners instead of DOM listeners\n    var listeners = data.on;\n    // replace with listeners with .native modifier\n    // so it gets processed during parent component patch.\n    data.on = data.nativeOn;\n\n    if (isTrue(Ctor.options.abstract)) {\n      // abstract components do not keep anything\n      // other than props & listeners & slot\n\n      // work around flow\n      var slot = data.slot;\n      data = {};\n      if (slot) {\n        data.slot = slot;\n      }\n    }\n\n    // install component management hooks onto the placeholder node\n    installComponentHooks(data);\n\n    // return a placeholder vnode\n    var name = Ctor.options.name || tag;\n    var vnode = new VNode(\n      \"vue-component-\" + Ctor.cid + (name ? \"-\" + name : \"\"),\n      data,\n      undefined,\n      undefined,\n      undefined,\n      context,\n      { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },\n      asyncFactory\n    );\n\n    // Weex specific: invoke recycle-list optimized @render function for\n    // extracting cell-slot template.\n    // https://github.com/Hanks10100/weex-native-directive/tree/master/component\n    /* istanbul ignore if */\n    return vnode;\n  }\n\n  function createComponentInstanceForVnode(\n    vnode, // we know it's MountedComponentVNode but flow doesn't\n    parent, // activeInstance in lifecycle state\n    parentElm,\n    refElm\n  ) {\n    var options = {\n      _isComponent: true,\n      parent: parent,\n      _parentVnode: vnode,\n      _parentElm: parentElm || null,\n      _refElm: refElm || null\n    };\n    // check inline-template render functions\n    var inlineTemplate = vnode.data.inlineTemplate;\n    if (isDef(inlineTemplate)) {\n      options.render = inlineTemplate.render;\n      options.staticRenderFns = inlineTemplate.staticRenderFns;\n    }\n    return new vnode.componentOptions.Ctor(options);\n  }\n\n  function installComponentHooks(data) {\n    var hooks = data.hook || (data.hook = {});\n    for (var i = 0; i < hooksToMerge.length; i++) {\n      var key = hooksToMerge[i];\n      hooks[key] = componentVNodeHooks[key];\n    }\n  }\n\n  // transform component v-model info (value and callback) into\n  // prop and event handler respectively.\n  function transformModel(options, data) {\n    var prop = (options.model && options.model.prop) || \"value\";\n    var event = (options.model && options.model.event) || \"input\";\n    (data.props || (data.props = {}))[prop] = data.model.value;\n    var on = data.on || (data.on = {});\n    if (isDef(on[event])) {\n      on[event] = [data.model.callback].concat(on[event]);\n    } else {\n      on[event] = data.model.callback;\n    }\n  }\n\n  /*  */\n\n  var SIMPLE_NORMALIZE = 1;\n  var ALWAYS_NORMALIZE = 2;\n\n  // wrapper function for providing a more flexible interface\n  // without getting yelled at by flow\n  function createElement(context, tag, data, children, normalizationType, alwaysNormalize) {\n    if (Array.isArray(data) || isPrimitive(data)) {\n      normalizationType = children;\n      children = data;\n      data = undefined;\n    }\n    if (isTrue(alwaysNormalize)) {\n      normalizationType = ALWAYS_NORMALIZE;\n    }\n    return _createElement(context, tag, data, children, normalizationType);\n  }\n\n  function _createElement(context, tag, data, children, normalizationType) {\n    if (isDef(data) && isDef(data.__ob__)) {\n      \"development\" !== \"production\" &&\n        warn(\n          \"Avoid using observed data object as vnode data: \" +\n            JSON.stringify(data) +\n            \"\\n\" +\n            \"Always create fresh vnode data objects in each render!\",\n          context\n        );\n      return createEmptyVNode();\n    }\n    // object syntax in v-bind\n    if (isDef(data) && isDef(data.is)) {\n      tag = data.is;\n    }\n    if (!tag) {\n      // in case of component :is set to falsy value\n      return createEmptyVNode();\n    }\n    // warn against non-primitive key\n    if (\"development\" !== \"production\" && isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {\n      {\n        warn(\"Avoid using non-primitive value as key, \" + \"use string/number value instead.\", context);\n      }\n    }\n    // support single function children as default scoped slot\n    if (Array.isArray(children) && typeof children[0] === \"function\") {\n      data = data || {};\n      data.scopedSlots = { default: children[0] };\n      children.length = 0;\n    }\n    if (normalizationType === ALWAYS_NORMALIZE) {\n      children = normalizeChildren(children);\n    } else if (normalizationType === SIMPLE_NORMALIZE) {\n      children = simpleNormalizeChildren(children);\n    }\n    var vnode, ns;\n    if (typeof tag === \"string\") {\n      var Ctor;\n      ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);\n      if (config.isReservedTag(tag)) {\n        // platform built-in elements\n        vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context);\n      } else if (isDef((Ctor = resolveAsset(context.$options, \"components\", tag)))) {\n        // component\n        vnode = createComponent(Ctor, data, context, children, tag);\n      } else {\n        // unknown or unlisted namespaced elements\n        // check at runtime because it may get assigned a namespace when its\n        // parent normalizes children\n        vnode = new VNode(tag, data, children, undefined, undefined, context);\n      }\n    } else {\n      // direct component options / constructor\n      vnode = createComponent(tag, data, context, children);\n    }\n    if (Array.isArray(vnode)) {\n      return vnode;\n    } else if (isDef(vnode)) {\n      if (isDef(ns)) {\n        applyNS(vnode, ns);\n      }\n      if (isDef(data)) {\n        registerDeepBindings(data);\n      }\n      return vnode;\n    } else {\n      return createEmptyVNode();\n    }\n  }\n\n  function applyNS(vnode, ns, force) {\n    vnode.ns = ns;\n    if (vnode.tag === \"foreignObject\") {\n      // use default namespace inside foreignObject\n      ns = undefined;\n      force = true;\n    }\n    if (isDef(vnode.children)) {\n      for (var i = 0, l = vnode.children.length; i < l; i++) {\n        var child = vnode.children[i];\n        if (isDef(child.tag) && (isUndef(child.ns) || (isTrue(force) && child.tag !== \"svg\"))) {\n          applyNS(child, ns, force);\n        }\n      }\n    }\n  }\n\n  // ref #5318\n  // necessary to ensure parent re-render when deep bindings like :style and\n  // :class are used on slot nodes\n  function registerDeepBindings(data) {\n    if (isObject(data.style)) {\n      traverse(data.style);\n    }\n    if (isObject(data.class)) {\n      traverse(data.class);\n    }\n  }\n\n  /*  */\n\n  function initRender(vm) {\n    vm._vnode = null; // the root of the child tree\n    vm._staticTrees = null; // v-once cached trees\n    var options = vm.$options;\n    var parentVnode = (vm.$vnode = options._parentVnode); // the placeholder node in parent tree\n    var renderContext = parentVnode && parentVnode.context;\n    vm.$slots = resolveSlots(options._renderChildren, renderContext);\n    vm.$scopedSlots = emptyObject;\n    // bind the createElement fn to this instance\n    // so that we get proper render context inside it.\n    // args order: tag, data, children, normalizationType, alwaysNormalize\n    // internal version is used by render functions compiled from templates\n    vm._c = function(a, b, c, d) {\n      return createElement(vm, a, b, c, d, false);\n    };\n    // normalization is always applied for the public version, used in\n    // user-written render functions.\n    vm.$createElement = function(a, b, c, d) {\n      return createElement(vm, a, b, c, d, true);\n    };\n\n    // $attrs & $listeners are exposed for easier HOC creation.\n    // they need to be reactive so that HOCs using them are always updated\n    var parentData = parentVnode && parentVnode.data;\n\n    /* istanbul ignore else */\n    {\n      defineReactive(\n        vm,\n        \"$attrs\",\n        (parentData && parentData.attrs) || emptyObject,\n        function() {\n          !isUpdatingChildComponent && warn(\"$attrs is readonly.\", vm);\n        },\n        true\n      );\n      defineReactive(\n        vm,\n        \"$listeners\",\n        options._parentListeners || emptyObject,\n        function() {\n          !isUpdatingChildComponent && warn(\"$listeners is readonly.\", vm);\n        },\n        true\n      );\n    }\n  }\n\n  function renderMixin(Vue) {\n    // install runtime convenience helpers\n    installRenderHelpers(Vue.prototype);\n\n    Vue.prototype.$nextTick = function(fn) {\n      return nextTick(fn, this);\n    };\n\n    Vue.prototype._render = function() {\n      var vm = this;\n      var ref = vm.$options;\n      var render = ref.render;\n      var _parentVnode = ref._parentVnode;\n\n      // reset _rendered flag on slots for duplicate slot check\n      {\n        for (var key in vm.$slots) {\n          // $flow-disable-line\n          vm.$slots[key]._rendered = false;\n        }\n      }\n\n      if (_parentVnode) {\n        vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;\n      }\n\n      // set parent vnode. this allows render functions to have access\n      // to the data on the placeholder node.\n      vm.$vnode = _parentVnode;\n      // render self\n      var vnode;\n      try {\n        vnode = render.call(vm._renderProxy, vm.$createElement);\n      } catch (e) {\n        handleError(e, vm, \"render\");\n        // return error render result,\n        // or previous vnode to prevent render error causing blank component\n        /* istanbul ignore else */\n        {\n          if (vm.$options.renderError) {\n            try {\n              vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);\n            } catch (e) {\n              handleError(e, vm, \"renderError\");\n              vnode = vm._vnode;\n            }\n          } else {\n            vnode = vm._vnode;\n          }\n        }\n      }\n      // return empty vnode in case the render function errored out\n      if (!(vnode instanceof VNode)) {\n        if (\"development\" !== \"production\" && Array.isArray(vnode)) {\n          warn(\n            \"Multiple root nodes returned from render function. Render function \" + \"should return a single root node.\",\n            vm\n          );\n        }\n        vnode = createEmptyVNode();\n      }\n      // set parent\n      vnode.parent = _parentVnode;\n      return vnode;\n    };\n  }\n\n  /*  */\n\n  var uid$3 = 0;\n\n  function initMixin(Vue) {\n    Vue.prototype._init = function(options) {\n      var vm = this;\n      // a uid\n      vm._uid = uid$3++;\n\n      var startTag, endTag;\n      /* istanbul ignore if */\n      if (\"development\" !== \"production\" && config.performance && mark) {\n        startTag = \"vue-perf-start:\" + vm._uid;\n        endTag = \"vue-perf-end:\" + vm._uid;\n        mark(startTag);\n      }\n\n      // a flag to avoid this being observed\n      vm._isVue = true;\n      // merge options\n      if (options && options._isComponent) {\n        // optimize internal component instantiation\n        // since dynamic options merging is pretty slow, and none of the\n        // internal component options needs special treatment.\n        initInternalComponent(vm, options);\n      } else {\n        vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);\n      }\n      /* istanbul ignore else */\n      {\n        initProxy(vm);\n      }\n      // expose real self\n      vm._self = vm;\n      initLifecycle(vm);\n      initEvents(vm);\n      initRender(vm);\n      callHook(vm, \"beforeCreate\");\n      initInjections(vm); // resolve injections before data/props\n      initState(vm);\n      initProvide(vm); // resolve provide after data/props\n      callHook(vm, \"created\");\n\n      /* istanbul ignore if */\n      if (\"development\" !== \"production\" && config.performance && mark) {\n        vm._name = formatComponentName(vm, false);\n        mark(endTag);\n        measure(\"vue \" + vm._name + \" init\", startTag, endTag);\n      }\n\n      if (vm.$options.el) {\n        vm.$mount(vm.$options.el);\n      }\n    };\n  }\n\n  function initInternalComponent(vm, options) {\n    var opts = (vm.$options = Object.create(vm.constructor.options));\n    // doing this because it's faster than dynamic enumeration.\n    var parentVnode = options._parentVnode;\n    opts.parent = options.parent;\n    opts._parentVnode = parentVnode;\n    opts._parentElm = options._parentElm;\n    opts._refElm = options._refElm;\n\n    var vnodeComponentOptions = parentVnode.componentOptions;\n    opts.propsData = vnodeComponentOptions.propsData;\n    opts._parentListeners = vnodeComponentOptions.listeners;\n    opts._renderChildren = vnodeComponentOptions.children;\n    opts._componentTag = vnodeComponentOptions.tag;\n\n    if (options.render) {\n      opts.render = options.render;\n      opts.staticRenderFns = options.staticRenderFns;\n    }\n  }\n\n  function resolveConstructorOptions(Ctor) {\n    var options = Ctor.options;\n    if (Ctor.super) {\n      var superOptions = resolveConstructorOptions(Ctor.super);\n      var cachedSuperOptions = Ctor.superOptions;\n      if (superOptions !== cachedSuperOptions) {\n        // super option changed,\n        // need to resolve new options.\n        Ctor.superOptions = superOptions;\n        // check if there are any late-modified/attached options (#4976)\n        var modifiedOptions = resolveModifiedOptions(Ctor);\n        // update base extend options\n        if (modifiedOptions) {\n          extend(Ctor.extendOptions, modifiedOptions);\n        }\n        options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);\n        if (options.name) {\n          options.components[options.name] = Ctor;\n        }\n      }\n    }\n    return options;\n  }\n\n  function resolveModifiedOptions(Ctor) {\n    var modified;\n    var latest = Ctor.options;\n    var extended = Ctor.extendOptions;\n    var sealed = Ctor.sealedOptions;\n    for (var key in latest) {\n      if (latest[key] !== sealed[key]) {\n        if (!modified) {\n          modified = {};\n        }\n        modified[key] = dedupe(latest[key], extended[key], sealed[key]);\n      }\n    }\n    return modified;\n  }\n\n  function dedupe(latest, extended, sealed) {\n    // compare latest and sealed to ensure lifecycle hooks won't be duplicated\n    // between merges\n    if (Array.isArray(latest)) {\n      var res = [];\n      sealed = Array.isArray(sealed) ? sealed : [sealed];\n      extended = Array.isArray(extended) ? extended : [extended];\n      for (var i = 0; i < latest.length; i++) {\n        // push original options and not sealed options to exclude duplicated options\n        if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {\n          res.push(latest[i]);\n        }\n      }\n      return res;\n    } else {\n      return latest;\n    }\n  }\n\n  function Vue(options) {\n    if (\"development\" !== \"production\" && !(this instanceof Vue)) {\n      warn(\"Vue is a constructor and should be called with the `new` keyword\");\n    }\n    this._init(options);\n  }\n\n  initMixin(Vue);\n  stateMixin(Vue);\n  eventsMixin(Vue);\n  lifecycleMixin(Vue);\n  renderMixin(Vue);\n\n  /*  */\n\n  function initUse(Vue) {\n    Vue.use = function(plugin) {\n      var installedPlugins = this._installedPlugins || (this._installedPlugins = []);\n      if (installedPlugins.indexOf(plugin) > -1) {\n        return this;\n      }\n\n      // additional parameters\n      var args = toArray(arguments, 1);\n      args.unshift(this);\n      if (typeof plugin.install === \"function\") {\n        plugin.install.apply(plugin, args);\n      } else if (typeof plugin === \"function\") {\n        plugin.apply(null, args);\n      }\n      installedPlugins.push(plugin);\n      return this;\n    };\n  }\n\n  /*  */\n\n  function initMixin$1(Vue) {\n    Vue.mixin = function(mixin) {\n      this.options = mergeOptions(this.options, mixin);\n      return this;\n    };\n  }\n\n  /*  */\n\n  function initExtend(Vue) {\n    /**\n     * Each instance constructor, including Vue, has a unique\n     * cid. This enables us to create wrapped \"child\n     * constructors\" for prototypal inheritance and cache them.\n     */\n    Vue.cid = 0;\n    var cid = 1;\n\n    /**\n     * Class inheritance\n     */\n    Vue.extend = function(extendOptions) {\n      extendOptions = extendOptions || {};\n      var Super = this;\n      var SuperId = Super.cid;\n      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});\n      if (cachedCtors[SuperId]) {\n        return cachedCtors[SuperId];\n      }\n\n      var name = extendOptions.name || Super.options.name;\n      if (\"development\" !== \"production\" && name) {\n        validateComponentName(name);\n      }\n\n      var Sub = function VueComponent(options) {\n        this._init(options);\n      };\n      Sub.prototype = Object.create(Super.prototype);\n      Sub.prototype.constructor = Sub;\n      Sub.cid = cid++;\n      Sub.options = mergeOptions(Super.options, extendOptions);\n      Sub[\"super\"] = Super;\n\n      // For props and computed properties, we define the proxy getters on\n      // the Vue instances at extension time, on the extended prototype. This\n      // avoids Object.defineProperty calls for each instance created.\n      if (Sub.options.props) {\n        initProps$1(Sub);\n      }\n      if (Sub.options.computed) {\n        initComputed$1(Sub);\n      }\n\n      // allow further extension/mixin/plugin usage\n      Sub.extend = Super.extend;\n      Sub.mixin = Super.mixin;\n      Sub.use = Super.use;\n\n      // create asset registers, so extended classes\n      // can have their private assets too.\n      ASSET_TYPES.forEach(function(type) {\n        Sub[type] = Super[type];\n      });\n      // enable recursive self-lookup\n      if (name) {\n        Sub.options.components[name] = Sub;\n      }\n\n      // keep a reference to the super options at extension time.\n      // later at instantiation we can check if Super's options have\n      // been updated.\n      Sub.superOptions = Super.options;\n      Sub.extendOptions = extendOptions;\n      Sub.sealedOptions = extend({}, Sub.options);\n\n      // cache constructor\n      cachedCtors[SuperId] = Sub;\n      return Sub;\n    };\n  }\n\n  function initProps$1(Comp) {\n    var props = Comp.options.props;\n    for (var key in props) {\n      proxy(Comp.prototype, \"_props\", key);\n    }\n  }\n\n  function initComputed$1(Comp) {\n    var computed = Comp.options.computed;\n    for (var key in computed) {\n      defineComputed(Comp.prototype, key, computed[key]);\n    }\n  }\n\n  /*  */\n\n  function initAssetRegisters(Vue) {\n    /**\n     * Create asset registration methods.\n     */\n    ASSET_TYPES.forEach(function(type) {\n      Vue[type] = function(id, definition) {\n        if (!definition) {\n          return this.options[type + \"s\"][id];\n        } else {\n          /* istanbul ignore if */\n          if (\"development\" !== \"production\" && type === \"component\") {\n            validateComponentName(id);\n          }\n          if (type === \"component\" && isPlainObject(definition)) {\n            definition.name = definition.name || id;\n            definition = this.options._base.extend(definition);\n          }\n          if (type === \"directive\" && typeof definition === \"function\") {\n            definition = { bind: definition, update: definition };\n          }\n          this.options[type + \"s\"][id] = definition;\n          return definition;\n        }\n      };\n    });\n  }\n\n  /*  */\n\n  function getComponentName(opts) {\n    return opts && (opts.Ctor.options.name || opts.tag);\n  }\n\n  function matches(pattern, name) {\n    if (Array.isArray(pattern)) {\n      return pattern.indexOf(name) > -1;\n    } else if (typeof pattern === \"string\") {\n      return pattern.split(\",\").indexOf(name) > -1;\n    } else if (isRegExp(pattern)) {\n      return pattern.test(name);\n    }\n    /* istanbul ignore next */\n    return false;\n  }\n\n  function pruneCache(keepAliveInstance, filter) {\n    var cache = keepAliveInstance.cache;\n    var keys = keepAliveInstance.keys;\n    var _vnode = keepAliveInstance._vnode;\n    for (var key in cache) {\n      var cachedNode = cache[key];\n      if (cachedNode) {\n        var name = getComponentName(cachedNode.componentOptions);\n        if (name && !filter(name)) {\n          pruneCacheEntry(cache, key, keys, _vnode);\n        }\n      }\n    }\n  }\n\n  function pruneCacheEntry(cache, key, keys, current) {\n    var cached$$1 = cache[key];\n    if (cached$$1 && (!current || cached$$1.tag !== current.tag)) {\n      cached$$1.componentInstance.$destroy();\n    }\n    cache[key] = null;\n    remove(keys, key);\n  }\n\n  var patternTypes = [String, RegExp, Array];\n\n  var KeepAlive = {\n    name: \"keep-alive\",\n    abstract: true,\n\n    props: {\n      include: patternTypes,\n      exclude: patternTypes,\n      max: [String, Number]\n    },\n\n    created: function created() {\n      this.cache = Object.create(null);\n      this.keys = [];\n    },\n\n    destroyed: function destroyed() {\n      var this$1 = this;\n\n      for (var key in this$1.cache) {\n        pruneCacheEntry(this$1.cache, key, this$1.keys);\n      }\n    },\n\n    mounted: function mounted() {\n      var this$1 = this;\n\n      this.$watch(\"include\", function(val) {\n        pruneCache(this$1, function(name) {\n          return matches(val, name);\n        });\n      });\n      this.$watch(\"exclude\", function(val) {\n        pruneCache(this$1, function(name) {\n          return !matches(val, name);\n        });\n      });\n    },\n\n    render: function render() {\n      var slot = this.$slots.default;\n      var vnode = getFirstComponentChild(slot);\n      var componentOptions = vnode && vnode.componentOptions;\n      if (componentOptions) {\n        // check pattern\n        var name = getComponentName(componentOptions);\n        var ref = this;\n        var include = ref.include;\n        var exclude = ref.exclude;\n        if (\n          // not included\n          (include && (!name || !matches(include, name))) ||\n          // excluded\n          (exclude && name && matches(exclude, name))\n        ) {\n          return vnode;\n        }\n\n        var ref$1 = this;\n        var cache = ref$1.cache;\n        var keys = ref$1.keys;\n        var key =\n          vnode.key == null\n            ? // same constructor may get registered as different local components\n              // so cid alone is not enough (#3269)\n              componentOptions.Ctor.cid + (componentOptions.tag ? \"::\" + componentOptions.tag : \"\")\n            : vnode.key;\n        if (cache[key]) {\n          vnode.componentInstance = cache[key].componentInstance;\n          // make current key freshest\n          remove(keys, key);\n          keys.push(key);\n        } else {\n          cache[key] = vnode;\n          keys.push(key);\n          // prune oldest entry\n          if (this.max && keys.length > parseInt(this.max)) {\n            pruneCacheEntry(cache, keys[0], keys, this._vnode);\n          }\n        }\n\n        vnode.data.keepAlive = true;\n      }\n      return vnode || (slot && slot[0]);\n    }\n  };\n\n  var builtInComponents = {\n    KeepAlive: KeepAlive\n  };\n\n  /*  */\n\n  function initGlobalAPI(Vue) {\n    // config\n    var configDef = {};\n    configDef.get = function() {\n      return config;\n    };\n    {\n      configDef.set = function() {\n        warn(\"Do not replace the Vue.config object, set individual fields instead.\");\n      };\n    }\n    Object.defineProperty(Vue, \"config\", configDef);\n\n    // exposed util methods.\n    // NOTE: these are not considered part of the public API - avoid relying on\n    // them unless you are aware of the risk.\n    Vue.util = {\n      warn: warn,\n      extend: extend,\n      mergeOptions: mergeOptions,\n      defineReactive: defineReactive\n    };\n\n    Vue.set = set;\n    Vue.delete = del;\n    Vue.nextTick = nextTick;\n\n    Vue.options = Object.create(null);\n    ASSET_TYPES.forEach(function(type) {\n      Vue.options[type + \"s\"] = Object.create(null);\n    });\n\n    // this is used to identify the \"base\" constructor to extend all plain-object\n    // components with in Weex's multi-instance scenarios.\n    Vue.options._base = Vue;\n\n    extend(Vue.options.components, builtInComponents);\n\n    initUse(Vue);\n    initMixin$1(Vue);\n    initExtend(Vue);\n    initAssetRegisters(Vue);\n  }\n\n  initGlobalAPI(Vue);\n\n  Object.defineProperty(Vue.prototype, \"$isServer\", {\n    get: isServerRendering\n  });\n\n  Object.defineProperty(Vue.prototype, \"$ssrContext\", {\n    get: function get() {\n      /* istanbul ignore next */\n      return this.$vnode && this.$vnode.ssrContext;\n    }\n  });\n\n  // expose FunctionalRenderContext for ssr runtime helper installation\n  Object.defineProperty(Vue, \"FunctionalRenderContext\", {\n    value: FunctionalRenderContext\n  });\n\n  Vue.version = \"2.5.17\";\n\n  /*  */\n\n  // these are reserved for web because they are directly compiled away\n  // during template compilation\n  var isReservedAttr = makeMap(\"style,class\");\n\n  // attributes that should be using props for binding\n  var acceptValue = makeMap(\"input,textarea,option,select,progress\");\n  var mustUseProp = function(tag, type, attr) {\n    return (\n      (attr === \"value\" && acceptValue(tag) && type !== \"button\") ||\n      (attr === \"selected\" && tag === \"option\") ||\n      (attr === \"checked\" && tag === \"input\") ||\n      (attr === \"muted\" && tag === \"video\")\n    );\n  };\n\n  var isEnumeratedAttr = makeMap(\"contenteditable,draggable,spellcheck\");\n\n  var isBooleanAttr = makeMap(\n    \"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,\" +\n      \"default,defaultchecked,defaultmuted,defaultselected,defer,disabled,\" +\n      \"enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,\" +\n      \"muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,\" +\n      \"required,reversed,scoped,seamless,selected,sortable,translate,\" +\n      \"truespeed,typemustmatch,visible\"\n  );\n\n  var xlinkNS = \"http://www.w3.org/1999/xlink\";\n\n  var isXlink = function(name) {\n    return name.charAt(5) === \":\" && name.slice(0, 5) === \"xlink\";\n  };\n\n  var getXlinkProp = function(name) {\n    return isXlink(name) ? name.slice(6, name.length) : \"\";\n  };\n\n  var isFalsyAttrValue = function(val) {\n    return val == null || val === false;\n  };\n\n  /*  */\n\n  function genClassForVnode(vnode) {\n    var data = vnode.data;\n    var parentNode = vnode;\n    var childNode = vnode;\n    while (isDef(childNode.componentInstance)) {\n      childNode = childNode.componentInstance._vnode;\n      if (childNode && childNode.data) {\n        data = mergeClassData(childNode.data, data);\n      }\n    }\n    while (isDef((parentNode = parentNode.parent))) {\n      if (parentNode && parentNode.data) {\n        data = mergeClassData(data, parentNode.data);\n      }\n    }\n    return renderClass(data.staticClass, data.class);\n  }\n\n  function mergeClassData(child, parent) {\n    return {\n      staticClass: concat(child.staticClass, parent.staticClass),\n      class: isDef(child.class) ? [child.class, parent.class] : parent.class\n    };\n  }\n\n  function renderClass(staticClass, dynamicClass) {\n    if (isDef(staticClass) || isDef(dynamicClass)) {\n      return concat(staticClass, stringifyClass(dynamicClass));\n    }\n    /* istanbul ignore next */\n    return \"\";\n  }\n\n  function concat(a, b) {\n    return a ? (b ? a + \" \" + b : a) : b || \"\";\n  }\n\n  function stringifyClass(value) {\n    if (Array.isArray(value)) {\n      return stringifyArray(value);\n    }\n    if (isObject(value)) {\n      return stringifyObject(value);\n    }\n    if (typeof value === \"string\") {\n      return value;\n    }\n    /* istanbul ignore next */\n    return \"\";\n  }\n\n  function stringifyArray(value) {\n    var res = \"\";\n    var stringified;\n    for (var i = 0, l = value.length; i < l; i++) {\n      if (isDef((stringified = stringifyClass(value[i]))) && stringified !== \"\") {\n        if (res) {\n          res += \" \";\n        }\n        res += stringified;\n      }\n    }\n    return res;\n  }\n\n  function stringifyObject(value) {\n    var res = \"\";\n    for (var key in value) {\n      if (value[key]) {\n        if (res) {\n          res += \" \";\n        }\n        res += key;\n      }\n    }\n    return res;\n  }\n\n  /*  */\n\n  var namespaceMap = {\n    svg: \"http://www.w3.org/2000/svg\",\n    math: \"http://www.w3.org/1998/Math/MathML\"\n  };\n\n  var isHTMLTag = makeMap(\n    \"html,body,base,head,link,meta,style,title,\" +\n      \"address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,\" +\n      \"div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,\" +\n      \"a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,\" +\n      \"s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,\" +\n      \"embed,object,param,source,canvas,script,noscript,del,ins,\" +\n      \"caption,col,colgroup,table,thead,tbody,td,th,tr,\" +\n      \"button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,\" +\n      \"output,progress,select,textarea,\" +\n      \"details,dialog,menu,menuitem,summary,\" +\n      \"content,element,shadow,template,blockquote,iframe,tfoot\"\n  );\n\n  // this map is intentionally selective, only covering SVG elements that may\n  // contain child elements.\n  var isSVG = makeMap(\n    \"svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,\" +\n      \"foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,\" +\n      \"polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view\",\n    true\n  );\n\n  var isPreTag = function(tag) {\n    return tag === \"pre\";\n  };\n\n  var isReservedTag = function(tag) {\n    return isHTMLTag(tag) || isSVG(tag);\n  };\n\n  function getTagNamespace(tag) {\n    if (isSVG(tag)) {\n      return \"svg\";\n    }\n    // basic support for MathML\n    // note it doesn't support other MathML elements being component roots\n    if (tag === \"math\") {\n      return \"math\";\n    }\n  }\n\n  var unknownElementCache = Object.create(null);\n  function isUnknownElement(tag) {\n    /* istanbul ignore if */\n    if (!inBrowser) {\n      return true;\n    }\n    if (isReservedTag(tag)) {\n      return false;\n    }\n    tag = tag.toLowerCase();\n    /* istanbul ignore if */\n    if (unknownElementCache[tag] != null) {\n      return unknownElementCache[tag];\n    }\n    var el = document.createElement(tag);\n    if (tag.indexOf(\"-\") > -1) {\n      // http://stackoverflow.com/a/28210364/1070244\n      return (unknownElementCache[tag] =\n        el.constructor === window.HTMLUnknownElement || el.constructor === window.HTMLElement);\n    } else {\n      return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()));\n    }\n  }\n\n  var isTextInputType = makeMap(\"text,number,password,search,email,tel,url\");\n\n  /*  */\n\n  /**\n   * Query an element selector if it's not an element already.\n   */\n  function query(el) {\n    if (typeof el === \"string\") {\n      var selected = document.querySelector(el);\n      if (!selected) {\n        \"development\" !== \"production\" && warn(\"Cannot find element: \" + el);\n        return document.createElement(\"div\");\n      }\n      return selected;\n    } else {\n      return el;\n    }\n  }\n\n  /*  */\n\n  function createElement$1(tagName, vnode) {\n    var elm = document.createElement(tagName);\n    if (tagName !== \"select\") {\n      return elm;\n    }\n    // false or null will remove the attribute but undefined will not\n    if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {\n      elm.setAttribute(\"multiple\", \"multiple\");\n    }\n    return elm;\n  }\n\n  function createElementNS(namespace, tagName) {\n    return document.createElementNS(namespaceMap[namespace], tagName);\n  }\n\n  function createTextNode(text) {\n    return document.createTextNode(text);\n  }\n\n  function createComment(text) {\n    return document.createComment(text);\n  }\n\n  function insertBefore(parentNode, newNode, referenceNode) {\n    parentNode.insertBefore(newNode, referenceNode);\n  }\n\n  function removeChild(node, child) {\n    node.removeChild(child);\n  }\n\n  function appendChild(node, child) {\n    node.appendChild(child);\n  }\n\n  function parentNode(node) {\n    return node.parentNode;\n  }\n\n  function nextSibling(node) {\n    return node.nextSibling;\n  }\n\n  function tagName(node) {\n    return node.tagName;\n  }\n\n  function setTextContent(node, text) {\n    node.textContent = text;\n  }\n\n  function setStyleScope(node, scopeId) {\n    node.setAttribute(scopeId, \"\");\n  }\n\n  var nodeOps = Object.freeze({\n    createElement: createElement$1,\n    createElementNS: createElementNS,\n    createTextNode: createTextNode,\n    createComment: createComment,\n    insertBefore: insertBefore,\n    removeChild: removeChild,\n    appendChild: appendChild,\n    parentNode: parentNode,\n    nextSibling: nextSibling,\n    tagName: tagName,\n    setTextContent: setTextContent,\n    setStyleScope: setStyleScope\n  });\n\n  /*  */\n\n  var ref = {\n    create: function create(_, vnode) {\n      registerRef(vnode);\n    },\n    update: function update(oldVnode, vnode) {\n      if (oldVnode.data.ref !== vnode.data.ref) {\n        registerRef(oldVnode, true);\n        registerRef(vnode);\n      }\n    },\n    destroy: function destroy(vnode) {\n      registerRef(vnode, true);\n    }\n  };\n\n  function registerRef(vnode, isRemoval) {\n    var key = vnode.data.ref;\n    if (!isDef(key)) {\n      return;\n    }\n\n    var vm = vnode.context;\n    var ref = vnode.componentInstance || vnode.elm;\n    var refs = vm.$refs;\n    if (isRemoval) {\n      if (Array.isArray(refs[key])) {\n        remove(refs[key], ref);\n      } else if (refs[key] === ref) {\n        refs[key] = undefined;\n      }\n    } else {\n      if (vnode.data.refInFor) {\n        if (!Array.isArray(refs[key])) {\n          refs[key] = [ref];\n        } else if (refs[key].indexOf(ref) < 0) {\n          // $flow-disable-line\n          refs[key].push(ref);\n        }\n      } else {\n        refs[key] = ref;\n      }\n    }\n  }\n\n  /**\n   * Virtual DOM patching algorithm based on Snabbdom by\n   * Simon Friis Vindum (@paldepind)\n   * Licensed under the MIT License\n   * https://github.com/paldepind/snabbdom/blob/master/LICENSE\n   *\n   * modified by Evan You (@yyx990803)\n   *\n   * Not type-checking this because this file is perf-critical and the cost\n   * of making flow understand it is not worth it.\n   */\n\n  var emptyNode = new VNode(\"\", {}, []);\n\n  var hooks = [\"create\", \"activate\", \"update\", \"remove\", \"destroy\"];\n\n  function sameVnode(a, b) {\n    return (\n      a.key === b.key &&\n      ((a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b)) ||\n        (isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error)))\n    );\n  }\n\n  function sameInputType(a, b) {\n    if (a.tag !== \"input\") {\n      return true;\n    }\n    var i;\n    var typeA = isDef((i = a.data)) && isDef((i = i.attrs)) && i.type;\n    var typeB = isDef((i = b.data)) && isDef((i = i.attrs)) && i.type;\n    return typeA === typeB || (isTextInputType(typeA) && isTextInputType(typeB));\n  }\n\n  function createKeyToOldIdx(children, beginIdx, endIdx) {\n    var i, key;\n    var map = {};\n    for (i = beginIdx; i <= endIdx; ++i) {\n      key = children[i].key;\n      if (isDef(key)) {\n        map[key] = i;\n      }\n    }\n    return map;\n  }\n\n  function createPatchFunction(backend) {\n    var i, j;\n    var cbs = {};\n\n    var modules = backend.modules;\n    var nodeOps = backend.nodeOps;\n\n    for (i = 0; i < hooks.length; ++i) {\n      cbs[hooks[i]] = [];\n      for (j = 0; j < modules.length; ++j) {\n        if (isDef(modules[j][hooks[i]])) {\n          cbs[hooks[i]].push(modules[j][hooks[i]]);\n        }\n      }\n    }\n\n    function emptyNodeAt(elm) {\n      return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm);\n    }\n\n    function createRmCb(childElm, listeners) {\n      function remove() {\n        if (--remove.listeners === 0) {\n          removeNode(childElm);\n        }\n      }\n      remove.listeners = listeners;\n      return remove;\n    }\n\n    function removeNode(el) {\n      var parent = nodeOps.parentNode(el);\n      // element may have already been removed due to v-html / v-text\n      if (isDef(parent)) {\n        nodeOps.removeChild(parent, el);\n      }\n    }\n\n    function isUnknownElement$$1(vnode, inVPre) {\n      return (\n        !inVPre &&\n        !vnode.ns &&\n        !(\n          config.ignoredElements.length &&\n          config.ignoredElements.some(function(ignore) {\n            return isRegExp(ignore) ? ignore.test(vnode.tag) : ignore === vnode.tag;\n          })\n        ) &&\n        config.isUnknownElement(vnode.tag)\n      );\n    }\n\n    var creatingElmInVPre = 0;\n\n    function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {\n      if (isDef(vnode.elm) && isDef(ownerArray)) {\n        // This vnode was used in a previous render!\n        // now it's used as a new node, overwriting its elm would cause\n        // potential patch errors down the road when it's used as an insertion\n        // reference node. Instead, we clone the node on-demand before creating\n        // associated DOM element for it.\n        vnode = ownerArray[index] = cloneVNode(vnode);\n      }\n\n      vnode.isRootInsert = !nested; // for transition enter check\n      if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n        return;\n      }\n\n      var data = vnode.data;\n      var children = vnode.children;\n      var tag = vnode.tag;\n      if (isDef(tag)) {\n        {\n          if (data && data.pre) {\n            creatingElmInVPre++;\n          }\n          if (isUnknownElement$$1(vnode, creatingElmInVPre)) {\n            warn(\n              \"Unknown custom element: <\" +\n                tag +\n                \"> - did you \" +\n                \"register the component correctly? For recursive components, \" +\n                'make sure to provide the \"name\" option.',\n              vnode.context\n            );\n          }\n        }\n\n        vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode);\n        setScope(vnode);\n\n        /* istanbul ignore if */\n        {\n          createChildren(vnode, children, insertedVnodeQueue);\n          if (isDef(data)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue);\n          }\n          insert(parentElm, vnode.elm, refElm);\n        }\n\n        if (\"development\" !== \"production\" && data && data.pre) {\n          creatingElmInVPre--;\n        }\n      } else if (isTrue(vnode.isComment)) {\n        vnode.elm = nodeOps.createComment(vnode.text);\n        insert(parentElm, vnode.elm, refElm);\n      } else {\n        vnode.elm = nodeOps.createTextNode(vnode.text);\n        insert(parentElm, vnode.elm, refElm);\n      }\n    }\n\n    function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {\n      var i = vnode.data;\n      if (isDef(i)) {\n        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;\n        if (isDef((i = i.hook)) && isDef((i = i.init))) {\n          i(vnode, false /* hydrating */, parentElm, refElm);\n        }\n        // after calling the init hook, if the vnode is a child component\n        // it should've created a child instance and mounted it. the child\n        // component also has set the placeholder vnode's elm.\n        // in that case we can just return the element and be done.\n        if (isDef(vnode.componentInstance)) {\n          initComponent(vnode, insertedVnodeQueue);\n          if (isTrue(isReactivated)) {\n            reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);\n          }\n          return true;\n        }\n      }\n    }\n\n    function initComponent(vnode, insertedVnodeQueue) {\n      if (isDef(vnode.data.pendingInsert)) {\n        insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);\n        vnode.data.pendingInsert = null;\n      }\n      vnode.elm = vnode.componentInstance.$el;\n      if (isPatchable(vnode)) {\n        invokeCreateHooks(vnode, insertedVnodeQueue);\n        setScope(vnode);\n      } else {\n        // empty component root.\n        // skip all element-related modules except for ref (#3455)\n        registerRef(vnode);\n        // make sure to invoke the insert hook\n        insertedVnodeQueue.push(vnode);\n      }\n    }\n\n    function reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) {\n      var i;\n      // hack for #4339: a reactivated component with inner transition\n      // does not trigger because the inner node's created hooks are not called\n      // again. It's not ideal to involve module-specific logic in here but\n      // there doesn't seem to be a better way to do it.\n      var innerNode = vnode;\n      while (innerNode.componentInstance) {\n        innerNode = innerNode.componentInstance._vnode;\n        if (isDef((i = innerNode.data)) && isDef((i = i.transition))) {\n          for (i = 0; i < cbs.activate.length; ++i) {\n            cbs.activate[i](emptyNode, innerNode);\n          }\n          insertedVnodeQueue.push(innerNode);\n          break;\n        }\n      }\n      // unlike a newly created component,\n      // a reactivated keep-alive component doesn't insert itself\n      insert(parentElm, vnode.elm, refElm);\n    }\n\n    function insert(parent, elm, ref$$1) {\n      if (isDef(parent)) {\n        if (isDef(ref$$1)) {\n          if (ref$$1.parentNode === parent) {\n            nodeOps.insertBefore(parent, elm, ref$$1);\n          }\n        } else {\n          nodeOps.appendChild(parent, elm);\n        }\n      }\n    }\n\n    function createChildren(vnode, children, insertedVnodeQueue) {\n      if (Array.isArray(children)) {\n        {\n          checkDuplicateKeys(children);\n        }\n        for (var i = 0; i < children.length; ++i) {\n          createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);\n        }\n      } else if (isPrimitive(vnode.text)) {\n        nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));\n      }\n    }\n\n    function isPatchable(vnode) {\n      while (vnode.componentInstance) {\n        vnode = vnode.componentInstance._vnode;\n      }\n      return isDef(vnode.tag);\n    }\n\n    function invokeCreateHooks(vnode, insertedVnodeQueue) {\n      for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {\n        cbs.create[i$1](emptyNode, vnode);\n      }\n      i = vnode.data.hook; // Reuse variable\n      if (isDef(i)) {\n        if (isDef(i.create)) {\n          i.create(emptyNode, vnode);\n        }\n        if (isDef(i.insert)) {\n          insertedVnodeQueue.push(vnode);\n        }\n      }\n    }\n\n    // set scope id attribute for scoped CSS.\n    // this is implemented as a special case to avoid the overhead\n    // of going through the normal attribute patching process.\n    function setScope(vnode) {\n      var i;\n      if (isDef((i = vnode.fnScopeId))) {\n        nodeOps.setStyleScope(vnode.elm, i);\n      } else {\n        var ancestor = vnode;\n        while (ancestor) {\n          if (isDef((i = ancestor.context)) && isDef((i = i.$options._scopeId))) {\n            nodeOps.setStyleScope(vnode.elm, i);\n          }\n          ancestor = ancestor.parent;\n        }\n      }\n      // for slot content they should also get the scopeId from the host instance.\n      if (\n        isDef((i = activeInstance)) &&\n        i !== vnode.context &&\n        i !== vnode.fnContext &&\n        isDef((i = i.$options._scopeId))\n      ) {\n        nodeOps.setStyleScope(vnode.elm, i);\n      }\n    }\n\n    function addVnodes(parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {\n      for (; startIdx <= endIdx; ++startIdx) {\n        createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx);\n      }\n    }\n\n    function invokeDestroyHook(vnode) {\n      var i, j;\n      var data = vnode.data;\n      if (isDef(data)) {\n        if (isDef((i = data.hook)) && isDef((i = i.destroy))) {\n          i(vnode);\n        }\n        for (i = 0; i < cbs.destroy.length; ++i) {\n          cbs.destroy[i](vnode);\n        }\n      }\n      if (isDef((i = vnode.children))) {\n        for (j = 0; j < vnode.children.length; ++j) {\n          invokeDestroyHook(vnode.children[j]);\n        }\n      }\n    }\n\n    function removeVnodes(parentElm, vnodes, startIdx, endIdx) {\n      for (; startIdx <= endIdx; ++startIdx) {\n        var ch = vnodes[startIdx];\n        if (isDef(ch)) {\n          if (isDef(ch.tag)) {\n            removeAndInvokeRemoveHook(ch);\n            invokeDestroyHook(ch);\n          } else {\n            // Text node\n            removeNode(ch.elm);\n          }\n        }\n      }\n    }\n\n    function removeAndInvokeRemoveHook(vnode, rm) {\n      if (isDef(rm) || isDef(vnode.data)) {\n        var i;\n        var listeners = cbs.remove.length + 1;\n        if (isDef(rm)) {\n          // we have a recursively passed down rm callback\n          // increase the listeners count\n          rm.listeners += listeners;\n        } else {\n          // directly removing\n          rm = createRmCb(vnode.elm, listeners);\n        }\n        // recursively invoke hooks on child component root node\n        if (isDef((i = vnode.componentInstance)) && isDef((i = i._vnode)) && isDef(i.data)) {\n          removeAndInvokeRemoveHook(i, rm);\n        }\n        for (i = 0; i < cbs.remove.length; ++i) {\n          cbs.remove[i](vnode, rm);\n        }\n        if (isDef((i = vnode.data.hook)) && isDef((i = i.remove))) {\n          i(vnode, rm);\n        } else {\n          rm();\n        }\n      } else {\n        removeNode(vnode.elm);\n      }\n    }\n\n    function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n      var oldStartIdx = 0;\n      var newStartIdx = 0;\n      var oldEndIdx = oldCh.length - 1;\n      var oldStartVnode = oldCh[0];\n      var oldEndVnode = oldCh[oldEndIdx];\n      var newEndIdx = newCh.length - 1;\n      var newStartVnode = newCh[0];\n      var newEndVnode = newCh[newEndIdx];\n      var oldKeyToIdx, idxInOld, vnodeToMove, refElm;\n\n      // removeOnly is a special flag used only by <transition-group>\n      // to ensure removed elements stay in correct relative positions\n      // during leaving transitions\n      var canMove = !removeOnly;\n\n      {\n        checkDuplicateKeys(newCh);\n      }\n\n      while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n        if (isUndef(oldStartVnode)) {\n          oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left\n        } else if (isUndef(oldEndVnode)) {\n          oldEndVnode = oldCh[--oldEndIdx];\n        } else if (sameVnode(oldStartVnode, newStartVnode)) {\n          patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);\n          oldStartVnode = oldCh[++oldStartIdx];\n          newStartVnode = newCh[++newStartIdx];\n        } else if (sameVnode(oldEndVnode, newEndVnode)) {\n          patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);\n          oldEndVnode = oldCh[--oldEndIdx];\n          newEndVnode = newCh[--newEndIdx];\n        } else if (sameVnode(oldStartVnode, newEndVnode)) {\n          // Vnode moved right\n          patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);\n          canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));\n          oldStartVnode = oldCh[++oldStartIdx];\n          newEndVnode = newCh[--newEndIdx];\n        } else if (sameVnode(oldEndVnode, newStartVnode)) {\n          // Vnode moved left\n          patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);\n          canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);\n          oldEndVnode = oldCh[--oldEndIdx];\n          newStartVnode = newCh[++newStartIdx];\n        } else {\n          if (isUndef(oldKeyToIdx)) {\n            oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);\n          }\n          idxInOld = isDef(newStartVnode.key)\n            ? oldKeyToIdx[newStartVnode.key]\n            : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);\n          if (isUndef(idxInOld)) {\n            // New element\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);\n          } else {\n            vnodeToMove = oldCh[idxInOld];\n            if (sameVnode(vnodeToMove, newStartVnode)) {\n              patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);\n              oldCh[idxInOld] = undefined;\n              canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);\n            } else {\n              // same key but different element. treat as new element\n              createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);\n            }\n          }\n          newStartVnode = newCh[++newStartIdx];\n        }\n      }\n      if (oldStartIdx > oldEndIdx) {\n        refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;\n        addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);\n      } else if (newStartIdx > newEndIdx) {\n        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);\n      }\n    }\n\n    function checkDuplicateKeys(children) {\n      var seenKeys = {};\n      for (var i = 0; i < children.length; i++) {\n        var vnode = children[i];\n        var key = vnode.key;\n        if (isDef(key)) {\n          if (seenKeys[key]) {\n            warn(\"Duplicate keys detected: '\" + key + \"'. This may cause an update error.\", vnode.context);\n          } else {\n            seenKeys[key] = true;\n          }\n        }\n      }\n    }\n\n    function findIdxInOld(node, oldCh, start, end) {\n      for (var i = start; i < end; i++) {\n        var c = oldCh[i];\n        if (isDef(c) && sameVnode(node, c)) {\n          return i;\n        }\n      }\n    }\n\n    function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n      if (oldVnode === vnode) {\n        return;\n      }\n\n      var elm = (vnode.elm = oldVnode.elm);\n\n      if (isTrue(oldVnode.isAsyncPlaceholder)) {\n        if (isDef(vnode.asyncFactory.resolved)) {\n          hydrate(oldVnode.elm, vnode, insertedVnodeQueue);\n        } else {\n          vnode.isAsyncPlaceholder = true;\n        }\n        return;\n      }\n\n      // reuse element for static trees.\n      // note we only do this if the vnode is cloned -\n      // if the new node is not cloned it means the render functions have been\n      // reset by the hot-reload-api and we need to do a proper re-render.\n      if (\n        isTrue(vnode.isStatic) &&\n        isTrue(oldVnode.isStatic) &&\n        vnode.key === oldVnode.key &&\n        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))\n      ) {\n        vnode.componentInstance = oldVnode.componentInstance;\n        return;\n      }\n\n      var i;\n      var data = vnode.data;\n      if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {\n        i(oldVnode, vnode);\n      }\n\n      var oldCh = oldVnode.children;\n      var ch = vnode.children;\n      if (isDef(data) && isPatchable(vnode)) {\n        for (i = 0; i < cbs.update.length; ++i) {\n          cbs.update[i](oldVnode, vnode);\n        }\n        if (isDef((i = data.hook)) && isDef((i = i.update))) {\n          i(oldVnode, vnode);\n        }\n      }\n      if (isUndef(vnode.text)) {\n        if (isDef(oldCh) && isDef(ch)) {\n          if (oldCh !== ch) {\n            updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);\n          }\n        } else if (isDef(ch)) {\n          if (isDef(oldVnode.text)) {\n            nodeOps.setTextContent(elm, \"\");\n          }\n          addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);\n        } else if (isDef(oldCh)) {\n          removeVnodes(elm, oldCh, 0, oldCh.length - 1);\n        } else if (isDef(oldVnode.text)) {\n          nodeOps.setTextContent(elm, \"\");\n        }\n      } else if (oldVnode.text !== vnode.text) {\n        nodeOps.setTextContent(elm, vnode.text);\n      }\n      if (isDef(data)) {\n        if (isDef((i = data.hook)) && isDef((i = i.postpatch))) {\n          i(oldVnode, vnode);\n        }\n      }\n    }\n\n    function invokeInsertHook(vnode, queue, initial) {\n      // delay insert hooks for component root nodes, invoke them after the\n      // element is really inserted\n      if (isTrue(initial) && isDef(vnode.parent)) {\n        vnode.parent.data.pendingInsert = queue;\n      } else {\n        for (var i = 0; i < queue.length; ++i) {\n          queue[i].data.hook.insert(queue[i]);\n        }\n      }\n    }\n\n    var hydrationBailed = false;\n    // list of modules that can skip create hook during hydration because they\n    // are already rendered on the client or has no need for initialization\n    // Note: style is excluded because it relies on initial clone for future\n    // deep updates (#7063).\n    var isRenderedModule = makeMap(\"attrs,class,staticClass,staticStyle,key\");\n\n    // Note: this is a browser-only function so we can assume elms are DOM nodes.\n    function hydrate(elm, vnode, insertedVnodeQueue, inVPre) {\n      var i;\n      var tag = vnode.tag;\n      var data = vnode.data;\n      var children = vnode.children;\n      inVPre = inVPre || (data && data.pre);\n      vnode.elm = elm;\n\n      if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {\n        vnode.isAsyncPlaceholder = true;\n        return true;\n      }\n      // assert node match\n      {\n        if (!assertNodeMatch(elm, vnode, inVPre)) {\n          return false;\n        }\n      }\n      if (isDef(data)) {\n        if (isDef((i = data.hook)) && isDef((i = i.init))) {\n          i(vnode, true /* hydrating */);\n        }\n        if (isDef((i = vnode.componentInstance))) {\n          // child component. it should have hydrated its own tree.\n          initComponent(vnode, insertedVnodeQueue);\n          return true;\n        }\n      }\n      if (isDef(tag)) {\n        if (isDef(children)) {\n          // empty element, allow client to pick up and populate children\n          if (!elm.hasChildNodes()) {\n            createChildren(vnode, children, insertedVnodeQueue);\n          } else {\n            // v-html and domProps: innerHTML\n            if (isDef((i = data)) && isDef((i = i.domProps)) && isDef((i = i.innerHTML))) {\n              if (i !== elm.innerHTML) {\n                /* istanbul ignore if */\n                if (\"development\" !== \"production\" && typeof console !== \"undefined\" && !hydrationBailed) {\n                  hydrationBailed = true;\n                  console.warn(\"Parent: \", elm);\n                  console.warn(\"server innerHTML: \", i);\n                  console.warn(\"client innerHTML: \", elm.innerHTML);\n                }\n                return false;\n              }\n            } else {\n              // iterate and compare children lists\n              var childrenMatch = true;\n              var childNode = elm.firstChild;\n              for (var i$1 = 0; i$1 < children.length; i$1++) {\n                if (!childNode || !hydrate(childNode, children[i$1], insertedVnodeQueue, inVPre)) {\n                  childrenMatch = false;\n                  break;\n                }\n                childNode = childNode.nextSibling;\n              }\n              // if childNode is not null, it means the actual childNodes list is\n              // longer than the virtual children list.\n              if (!childrenMatch || childNode) {\n                /* istanbul ignore if */\n                if (\"development\" !== \"production\" && typeof console !== \"undefined\" && !hydrationBailed) {\n                  hydrationBailed = true;\n                  console.warn(\"Parent: \", elm);\n                  console.warn(\"Mismatching childNodes vs. VNodes: \", elm.childNodes, children);\n                }\n                return false;\n              }\n            }\n          }\n        }\n        if (isDef(data)) {\n          var fullInvoke = false;\n          for (var key in data) {\n            if (!isRenderedModule(key)) {\n              fullInvoke = true;\n              invokeCreateHooks(vnode, insertedVnodeQueue);\n              break;\n            }\n          }\n          if (!fullInvoke && data[\"class\"]) {\n            // ensure collecting deps for deep class bindings for future updates\n            traverse(data[\"class\"]);\n          }\n        }\n      } else if (elm.data !== vnode.text) {\n        elm.data = vnode.text;\n      }\n      return true;\n    }\n\n    function assertNodeMatch(node, vnode, inVPre) {\n      if (isDef(vnode.tag)) {\n        return (\n          vnode.tag.indexOf(\"vue-component\") === 0 ||\n          (!isUnknownElement$$1(vnode, inVPre) &&\n            vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase()))\n        );\n      } else {\n        return node.nodeType === (vnode.isComment ? 8 : 3);\n      }\n    }\n\n    return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {\n      if (isUndef(vnode)) {\n        if (isDef(oldVnode)) {\n          invokeDestroyHook(oldVnode);\n        }\n        return;\n      }\n\n      var isInitialPatch = false;\n      var insertedVnodeQueue = [];\n\n      if (isUndef(oldVnode)) {\n        // empty mount (likely as component), create new root element\n        isInitialPatch = true;\n        createElm(vnode, insertedVnodeQueue, parentElm, refElm);\n      } else {\n        var isRealElement = isDef(oldVnode.nodeType);\n        if (!isRealElement && sameVnode(oldVnode, vnode)) {\n          // patch existing root node\n          patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);\n        } else {\n          if (isRealElement) {\n            // mounting to a real element\n            // check if this is server-rendered content and if we can perform\n            // a successful hydration.\n            if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n              oldVnode.removeAttribute(SSR_ATTR);\n              hydrating = true;\n            }\n            if (isTrue(hydrating)) {\n              if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n                invokeInsertHook(vnode, insertedVnodeQueue, true);\n                return oldVnode;\n              } else {\n                warn(\n                  \"The client-side rendered virtual DOM tree is not matching \" +\n                    \"server-rendered content. This is likely caused by incorrect \" +\n                    \"HTML markup, for example nesting block-level elements inside \" +\n                    \"<p>, or missing <tbody>. Bailing hydration and performing \" +\n                    \"full client-side render.\"\n                );\n              }\n            }\n            // either not server-rendered, or hydration failed.\n            // create an empty node and replace it\n            oldVnode = emptyNodeAt(oldVnode);\n          }\n\n          // replacing existing element\n          var oldElm = oldVnode.elm;\n          var parentElm$1 = nodeOps.parentNode(oldElm);\n\n          // create new node\n          createElm(\n            vnode,\n            insertedVnodeQueue,\n            // extremely rare edge case: do not insert if old element is in a\n            // leaving transition. Only happens when combining transition +\n            // keep-alive + HOCs. (#4590)\n            oldElm._leaveCb ? null : parentElm$1,\n            nodeOps.nextSibling(oldElm)\n          );\n\n          // update parent placeholder node element, recursively\n          if (isDef(vnode.parent)) {\n            var ancestor = vnode.parent;\n            var patchable = isPatchable(vnode);\n            while (ancestor) {\n              for (var i = 0; i < cbs.destroy.length; ++i) {\n                cbs.destroy[i](ancestor);\n              }\n              ancestor.elm = vnode.elm;\n              if (patchable) {\n                for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {\n                  cbs.create[i$1](emptyNode, ancestor);\n                }\n                // #6513\n                // invoke insert hooks that may have been merged by create hooks.\n                // e.g. for directives that uses the \"inserted\" hook.\n                var insert = ancestor.data.hook.insert;\n                if (insert.merged) {\n                  // start at index 1 to avoid re-invoking component mounted hook\n                  for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {\n                    insert.fns[i$2]();\n                  }\n                }\n              } else {\n                registerRef(ancestor);\n              }\n              ancestor = ancestor.parent;\n            }\n          }\n\n          // destroy old node\n          if (isDef(parentElm$1)) {\n            removeVnodes(parentElm$1, [oldVnode], 0, 0);\n          } else if (isDef(oldVnode.tag)) {\n            invokeDestroyHook(oldVnode);\n          }\n        }\n      }\n\n      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);\n      return vnode.elm;\n    };\n  }\n\n  /*  */\n\n  var directives = {\n    create: updateDirectives,\n    update: updateDirectives,\n    destroy: function unbindDirectives(vnode) {\n      updateDirectives(vnode, emptyNode);\n    }\n  };\n\n  function updateDirectives(oldVnode, vnode) {\n    if (oldVnode.data.directives || vnode.data.directives) {\n      _update(oldVnode, vnode);\n    }\n  }\n\n  function _update(oldVnode, vnode) {\n    var isCreate = oldVnode === emptyNode;\n    var isDestroy = vnode === emptyNode;\n    var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);\n    var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);\n\n    var dirsWithInsert = [];\n    var dirsWithPostpatch = [];\n\n    var key, oldDir, dir;\n    for (key in newDirs) {\n      oldDir = oldDirs[key];\n      dir = newDirs[key];\n      if (!oldDir) {\n        // new directive, bind\n        callHook$1(dir, \"bind\", vnode, oldVnode);\n        if (dir.def && dir.def.inserted) {\n          dirsWithInsert.push(dir);\n        }\n      } else {\n        // existing directive, update\n        dir.oldValue = oldDir.value;\n        callHook$1(dir, \"update\", vnode, oldVnode);\n        if (dir.def && dir.def.componentUpdated) {\n          dirsWithPostpatch.push(dir);\n        }\n      }\n    }\n\n    if (dirsWithInsert.length) {\n      var callInsert = function() {\n        for (var i = 0; i < dirsWithInsert.length; i++) {\n          callHook$1(dirsWithInsert[i], \"inserted\", vnode, oldVnode);\n        }\n      };\n      if (isCreate) {\n        mergeVNodeHook(vnode, \"insert\", callInsert);\n      } else {\n        callInsert();\n      }\n    }\n\n    if (dirsWithPostpatch.length) {\n      mergeVNodeHook(vnode, \"postpatch\", function() {\n        for (var i = 0; i < dirsWithPostpatch.length; i++) {\n          callHook$1(dirsWithPostpatch[i], \"componentUpdated\", vnode, oldVnode);\n        }\n      });\n    }\n\n    if (!isCreate) {\n      for (key in oldDirs) {\n        if (!newDirs[key]) {\n          // no longer present, unbind\n          callHook$1(oldDirs[key], \"unbind\", oldVnode, oldVnode, isDestroy);\n        }\n      }\n    }\n  }\n\n  var emptyModifiers = Object.create(null);\n\n  function normalizeDirectives$1(dirs, vm) {\n    var res = Object.create(null);\n    if (!dirs) {\n      // $flow-disable-line\n      return res;\n    }\n    var i, dir;\n    for (i = 0; i < dirs.length; i++) {\n      dir = dirs[i];\n      if (!dir.modifiers) {\n        // $flow-disable-line\n        dir.modifiers = emptyModifiers;\n      }\n      res[getRawDirName(dir)] = dir;\n      dir.def = resolveAsset(vm.$options, \"directives\", dir.name, true);\n    }\n    // $flow-disable-line\n    return res;\n  }\n\n  function getRawDirName(dir) {\n    return dir.rawName || dir.name + \".\" + Object.keys(dir.modifiers || {}).join(\".\");\n  }\n\n  function callHook$1(dir, hook, vnode, oldVnode, isDestroy) {\n    var fn = dir.def && dir.def[hook];\n    if (fn) {\n      try {\n        fn(vnode.elm, dir, vnode, oldVnode, isDestroy);\n      } catch (e) {\n        handleError(e, vnode.context, \"directive \" + dir.name + \" \" + hook + \" hook\");\n      }\n    }\n  }\n\n  var baseModules = [ref, directives];\n\n  /*  */\n\n  function updateAttrs(oldVnode, vnode) {\n    var opts = vnode.componentOptions;\n    if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {\n      return;\n    }\n    if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n      return;\n    }\n    var key, cur, old;\n    var elm = vnode.elm;\n    var oldAttrs = oldVnode.data.attrs || {};\n    var attrs = vnode.data.attrs || {};\n    // clone observed objects, as the user probably wants to mutate it\n    if (isDef(attrs.__ob__)) {\n      attrs = vnode.data.attrs = extend({}, attrs);\n    }\n\n    for (key in attrs) {\n      cur = attrs[key];\n      old = oldAttrs[key];\n      if (old !== cur) {\n        setAttr(elm, key, cur);\n      }\n    }\n    // #4391: in IE9, setting type can reset value for input[type=radio]\n    // #6666: IE/Edge forces progress value down to 1 before setting a max\n    /* istanbul ignore if */\n    if ((isIE || isEdge) && attrs.value !== oldAttrs.value) {\n      setAttr(elm, \"value\", attrs.value);\n    }\n    for (key in oldAttrs) {\n      if (isUndef(attrs[key])) {\n        if (isXlink(key)) {\n          elm.removeAttributeNS(xlinkNS, getXlinkProp(key));\n        } else if (!isEnumeratedAttr(key)) {\n          elm.removeAttribute(key);\n        }\n      }\n    }\n  }\n\n  function setAttr(el, key, value) {\n    if (el.tagName.indexOf(\"-\") > -1) {\n      baseSetAttr(el, key, value);\n    } else if (isBooleanAttr(key)) {\n      // set attribute for blank value\n      // e.g. <option disabled>Select one</option>\n      if (isFalsyAttrValue(value)) {\n        el.removeAttribute(key);\n      } else {\n        // technically allowfullscreen is a boolean attribute for <iframe>,\n        // but Flash expects a value of \"true\" when used on <embed> tag\n        value = key === \"allowfullscreen\" && el.tagName === \"EMBED\" ? \"true\" : key;\n        el.setAttribute(key, value);\n      }\n    } else if (isEnumeratedAttr(key)) {\n      el.setAttribute(key, isFalsyAttrValue(value) || value === \"false\" ? \"false\" : \"true\");\n    } else if (isXlink(key)) {\n      if (isFalsyAttrValue(value)) {\n        el.removeAttributeNS(xlinkNS, getXlinkProp(key));\n      } else {\n        el.setAttributeNS(xlinkNS, key, value);\n      }\n    } else {\n      baseSetAttr(el, key, value);\n    }\n  }\n\n  function baseSetAttr(el, key, value) {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key);\n    } else {\n      // #7138: IE10 & 11 fires input event when setting placeholder on\n      // <textarea>... block the first input event and remove the blocker\n      // immediately.\n      /* istanbul ignore if */\n      if (isIE && !isIE9 && el.tagName === \"TEXTAREA\" && key === \"placeholder\" && !el.__ieph) {\n        var blocker = function(e) {\n          e.stopImmediatePropagation();\n          el.removeEventListener(\"input\", blocker);\n        };\n        el.addEventListener(\"input\", blocker);\n        // $flow-disable-line\n        el.__ieph = true; /* IE placeholder patched */\n      }\n      el.setAttribute(key, value);\n    }\n  }\n\n  var attrs = {\n    create: updateAttrs,\n    update: updateAttrs\n  };\n\n  /*  */\n\n  function updateClass(oldVnode, vnode) {\n    var el = vnode.elm;\n    var data = vnode.data;\n    var oldData = oldVnode.data;\n    if (\n      isUndef(data.staticClass) &&\n      isUndef(data.class) &&\n      (isUndef(oldData) || (isUndef(oldData.staticClass) && isUndef(oldData.class)))\n    ) {\n      return;\n    }\n\n    var cls = genClassForVnode(vnode);\n\n    // handle transition classes\n    var transitionClass = el._transitionClasses;\n    if (isDef(transitionClass)) {\n      cls = concat(cls, stringifyClass(transitionClass));\n    }\n\n    // set the class\n    if (cls !== el._prevClass) {\n      el.setAttribute(\"class\", cls);\n      el._prevClass = cls;\n    }\n  }\n\n  var klass = {\n    create: updateClass,\n    update: updateClass\n  };\n\n  /*  */\n\n  var validDivisionCharRE = /[\\w).+\\-_$\\]]/;\n\n  function parseFilters(exp) {\n    var inSingle = false;\n    var inDouble = false;\n    var inTemplateString = false;\n    var inRegex = false;\n    var curly = 0;\n    var square = 0;\n    var paren = 0;\n    var lastFilterIndex = 0;\n    var c, prev, i, expression, filters;\n\n    for (i = 0; i < exp.length; i++) {\n      prev = c;\n      c = exp.charCodeAt(i);\n      if (inSingle) {\n        if (c === 0x27 && prev !== 0x5c) {\n          inSingle = false;\n        }\n      } else if (inDouble) {\n        if (c === 0x22 && prev !== 0x5c) {\n          inDouble = false;\n        }\n      } else if (inTemplateString) {\n        if (c === 0x60 && prev !== 0x5c) {\n          inTemplateString = false;\n        }\n      } else if (inRegex) {\n        if (c === 0x2f && prev !== 0x5c) {\n          inRegex = false;\n        }\n      } else if (\n        c === 0x7c && // pipe\n        exp.charCodeAt(i + 1) !== 0x7c &&\n        exp.charCodeAt(i - 1) !== 0x7c &&\n        !curly &&\n        !square &&\n        !paren\n      ) {\n        if (expression === undefined) {\n          // first filter, end of expression\n          lastFilterIndex = i + 1;\n          expression = exp.slice(0, i).trim();\n        } else {\n          pushFilter();\n        }\n      } else {\n        switch (c) {\n          case 0x22:\n            inDouble = true;\n            break; // \"\n          case 0x27:\n            inSingle = true;\n            break; // '\n          case 0x60:\n            inTemplateString = true;\n            break; // `\n          case 0x28:\n            paren++;\n            break; // (\n          case 0x29:\n            paren--;\n            break; // )\n          case 0x5b:\n            square++;\n            break; // [\n          case 0x5d:\n            square--;\n            break; // ]\n          case 0x7b:\n            curly++;\n            break; // {\n          case 0x7d:\n            curly--;\n            break; // }\n        }\n        if (c === 0x2f) {\n          // /\n          var j = i - 1;\n          var p = void 0;\n          // find first non-whitespace prev char\n          for (; j >= 0; j--) {\n            p = exp.charAt(j);\n            if (p !== \" \") {\n              break;\n            }\n          }\n          if (!p || !validDivisionCharRE.test(p)) {\n            inRegex = true;\n          }\n        }\n      }\n    }\n\n    if (expression === undefined) {\n      expression = exp.slice(0, i).trim();\n    } else if (lastFilterIndex !== 0) {\n      pushFilter();\n    }\n\n    function pushFilter() {\n      (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());\n      lastFilterIndex = i + 1;\n    }\n\n    if (filters) {\n      for (i = 0; i < filters.length; i++) {\n        expression = wrapFilter(expression, filters[i]);\n      }\n    }\n\n    return expression;\n  }\n\n  function wrapFilter(exp, filter) {\n    var i = filter.indexOf(\"(\");\n    if (i < 0) {\n      // _f: resolveFilter\n      return '_f(\"' + filter + '\")(' + exp + \")\";\n    } else {\n      var name = filter.slice(0, i);\n      var args = filter.slice(i + 1);\n      return '_f(\"' + name + '\")(' + exp + (args !== \")\" ? \",\" + args : args);\n    }\n  }\n\n  /*  */\n\n  function baseWarn(msg) {\n    console.error(\"[Vue compiler]: \" + msg);\n  }\n\n  function pluckModuleFunction(modules, key) {\n    return modules\n      ? modules\n          .map(function(m) {\n            return m[key];\n          })\n          .filter(function(_) {\n            return _;\n          })\n      : [];\n  }\n\n  function addProp(el, name, value) {\n    (el.props || (el.props = [])).push({ name: name, value: value });\n    el.plain = false;\n  }\n\n  function addAttr(el, name, value) {\n    (el.attrs || (el.attrs = [])).push({ name: name, value: value });\n    el.plain = false;\n  }\n\n  // add a raw attr (use this in preTransforms)\n  function addRawAttr(el, name, value) {\n    el.attrsMap[name] = value;\n    el.attrsList.push({ name: name, value: value });\n  }\n\n  function addDirective(el, name, rawName, value, arg, modifiers) {\n    (el.directives || (el.directives = [])).push({\n      name: name,\n      rawName: rawName,\n      value: value,\n      arg: arg,\n      modifiers: modifiers\n    });\n    el.plain = false;\n  }\n\n  function addHandler(el, name, value, modifiers, important, warn) {\n    modifiers = modifiers || emptyObject;\n    // warn prevent and passive modifier\n    /* istanbul ignore if */\n    if (\"development\" !== \"production\" && warn && modifiers.prevent && modifiers.passive) {\n      warn(\"passive and prevent can't be used together. \" + \"Passive handler can't prevent default event.\");\n    }\n\n    // check capture modifier\n    if (modifiers.capture) {\n      delete modifiers.capture;\n      name = \"!\" + name; // mark the event as captured\n    }\n    if (modifiers.once) {\n      delete modifiers.once;\n      name = \"~\" + name; // mark the event as once\n    }\n    /* istanbul ignore if */\n    if (modifiers.passive) {\n      delete modifiers.passive;\n      name = \"&\" + name; // mark the event as passive\n    }\n\n    // normalize click.right and click.middle since they don't actually fire\n    // this is technically browser-specific, but at least for now browsers are\n    // the only target envs that have right/middle clicks.\n    if (name === \"click\") {\n      if (modifiers.right) {\n        name = \"contextmenu\";\n        delete modifiers.right;\n      } else if (modifiers.middle) {\n        name = \"mouseup\";\n      }\n    }\n\n    var events;\n    if (modifiers.native) {\n      delete modifiers.native;\n      events = el.nativeEvents || (el.nativeEvents = {});\n    } else {\n      events = el.events || (el.events = {});\n    }\n\n    var newHandler = {\n      value: value.trim()\n    };\n    if (modifiers !== emptyObject) {\n      newHandler.modifiers = modifiers;\n    }\n\n    var handlers = events[name];\n    /* istanbul ignore if */\n    if (Array.isArray(handlers)) {\n      important ? handlers.unshift(newHandler) : handlers.push(newHandler);\n    } else if (handlers) {\n      events[name] = important ? [newHandler, handlers] : [handlers, newHandler];\n    } else {\n      events[name] = newHandler;\n    }\n\n    el.plain = false;\n  }\n\n  function getBindingAttr(el, name, getStatic) {\n    var dynamicValue = getAndRemoveAttr(el, \":\" + name) || getAndRemoveAttr(el, \"v-bind:\" + name);\n    if (dynamicValue != null) {\n      return parseFilters(dynamicValue);\n    } else if (getStatic !== false) {\n      var staticValue = getAndRemoveAttr(el, name);\n      if (staticValue != null) {\n        return JSON.stringify(staticValue);\n      }\n    }\n  }\n\n  // note: this only removes the attr from the Array (attrsList) so that it\n  // doesn't get processed by processAttrs.\n  // By default it does NOT remove it from the map (attrsMap) because the map is\n  // needed during codegen.\n  function getAndRemoveAttr(el, name, removeFromMap) {\n    var val;\n    if ((val = el.attrsMap[name]) != null) {\n      var list = el.attrsList;\n      for (var i = 0, l = list.length; i < l; i++) {\n        if (list[i].name === name) {\n          list.splice(i, 1);\n          break;\n        }\n      }\n    }\n    if (removeFromMap) {\n      delete el.attrsMap[name];\n    }\n    return val;\n  }\n\n  /*  */\n\n  /**\n   * Cross-platform code generation for component v-model\n   */\n  function genComponentModel(el, value, modifiers) {\n    var ref = modifiers || {};\n    var number = ref.number;\n    var trim = ref.trim;\n\n    var baseValueExpression = \"$$v\";\n    var valueExpression = baseValueExpression;\n    if (trim) {\n      valueExpression =\n        \"(typeof \" +\n        baseValueExpression +\n        \" === 'string'\" +\n        \"? \" +\n        baseValueExpression +\n        \".trim()\" +\n        \": \" +\n        baseValueExpression +\n        \")\";\n    }\n    if (number) {\n      valueExpression = \"_n(\" + valueExpression + \")\";\n    }\n    var assignment = genAssignmentCode(value, valueExpression);\n\n    el.model = {\n      value: \"(\" + value + \")\",\n      expression: '\"' + value + '\"',\n      callback: \"function (\" + baseValueExpression + \") {\" + assignment + \"}\"\n    };\n  }\n\n  /**\n   * Cross-platform codegen helper for generating v-model value assignment code.\n   */\n  function genAssignmentCode(value, assignment) {\n    var res = parseModel(value);\n    if (res.key === null) {\n      return value + \"=\" + assignment;\n    } else {\n      return \"$set(\" + res.exp + \", \" + res.key + \", \" + assignment + \")\";\n    }\n  }\n\n  /**\n   * Parse a v-model expression into a base path and a final key segment.\n   * Handles both dot-path and possible square brackets.\n   *\n   * Possible cases:\n   *\n   * - test\n   * - test[key]\n   * - test[test1[key]]\n   * - test[\"a\"][key]\n   * - xxx.test[a[a].test1[key]]\n   * - test.xxx.a[\"asa\"][test1[key]]\n   *\n   */\n\n  var len;\n  var str;\n  var chr;\n  var index$1;\n  var expressionPos;\n  var expressionEndPos;\n\n  function parseModel(val) {\n    // Fix https://github.com/vuejs/vue/pull/7730\n    // allow v-model=\"obj.val \" (trailing whitespace)\n    val = val.trim();\n    len = val.length;\n\n    if (val.indexOf(\"[\") < 0 || val.lastIndexOf(\"]\") < len - 1) {\n      index$1 = val.lastIndexOf(\".\");\n      if (index$1 > -1) {\n        return {\n          exp: val.slice(0, index$1),\n          key: '\"' + val.slice(index$1 + 1) + '\"'\n        };\n      } else {\n        return {\n          exp: val,\n          key: null\n        };\n      }\n    }\n\n    str = val;\n    index$1 = expressionPos = expressionEndPos = 0;\n\n    while (!eof()) {\n      chr = next();\n      /* istanbul ignore if */\n      if (isStringStart(chr)) {\n        parseString(chr);\n      } else if (chr === 0x5b) {\n        parseBracket(chr);\n      }\n    }\n\n    return {\n      exp: val.slice(0, expressionPos),\n      key: val.slice(expressionPos + 1, expressionEndPos)\n    };\n  }\n\n  function next() {\n    return str.charCodeAt(++index$1);\n  }\n\n  function eof() {\n    return index$1 >= len;\n  }\n\n  function isStringStart(chr) {\n    return chr === 0x22 || chr === 0x27;\n  }\n\n  function parseBracket(chr) {\n    var inBracket = 1;\n    expressionPos = index$1;\n    while (!eof()) {\n      chr = next();\n      if (isStringStart(chr)) {\n        parseString(chr);\n        continue;\n      }\n      if (chr === 0x5b) {\n        inBracket++;\n      }\n      if (chr === 0x5d) {\n        inBracket--;\n      }\n      if (inBracket === 0) {\n        expressionEndPos = index$1;\n        break;\n      }\n    }\n  }\n\n  function parseString(chr) {\n    var stringQuote = chr;\n    while (!eof()) {\n      chr = next();\n      if (chr === stringQuote) {\n        break;\n      }\n    }\n  }\n\n  /*  */\n\n  var warn$1;\n\n  // in some cases, the event used has to be determined at runtime\n  // so we used some reserved tokens during compile.\n  var RANGE_TOKEN = \"__r\";\n  var CHECKBOX_RADIO_TOKEN = \"__c\";\n\n  function model(el, dir, _warn) {\n    warn$1 = _warn;\n    var value = dir.value;\n    var modifiers = dir.modifiers;\n    var tag = el.tag;\n    var type = el.attrsMap.type;\n\n    {\n      // inputs with type=\"file\" are read only and setting the input's\n      // value will throw an error.\n      if (tag === \"input\" && type === \"file\") {\n        warn$1(\n          \"<\" +\n            el.tag +\n            ' v-model=\"' +\n            value +\n            '\" type=\"file\">:\\n' +\n            \"File inputs are read only. Use a v-on:change listener instead.\"\n        );\n      }\n    }\n\n    if (el.component) {\n      genComponentModel(el, value, modifiers);\n      // component v-model doesn't need extra runtime\n      return false;\n    } else if (tag === \"select\") {\n      genSelect(el, value, modifiers);\n    } else if (tag === \"input\" && type === \"checkbox\") {\n      genCheckboxModel(el, value, modifiers);\n    } else if (tag === \"input\" && type === \"radio\") {\n      genRadioModel(el, value, modifiers);\n    } else if (tag === \"input\" || tag === \"textarea\") {\n      genDefaultModel(el, value, modifiers);\n    } else if (!config.isReservedTag(tag)) {\n      genComponentModel(el, value, modifiers);\n      // component v-model doesn't need extra runtime\n      return false;\n    } else {\n      warn$1(\n        \"<\" +\n          el.tag +\n          ' v-model=\"' +\n          value +\n          '\">: ' +\n          \"v-model is not supported on this element type. \" +\n          \"If you are working with contenteditable, it's recommended to \" +\n          \"wrap a library dedicated for that purpose inside a custom component.\"\n      );\n    }\n\n    // ensure runtime directive metadata\n    return true;\n  }\n\n  function genCheckboxModel(el, value, modifiers) {\n    var number = modifiers && modifiers.number;\n    var valueBinding = getBindingAttr(el, \"value\") || \"null\";\n    var trueValueBinding = getBindingAttr(el, \"true-value\") || \"true\";\n    var falseValueBinding = getBindingAttr(el, \"false-value\") || \"false\";\n    addProp(\n      el,\n      \"checked\",\n      \"Array.isArray(\" +\n        value +\n        \")\" +\n        \"?_i(\" +\n        value +\n        \",\" +\n        valueBinding +\n        \")>-1\" +\n        (trueValueBinding === \"true\" ? \":(\" + value + \")\" : \":_q(\" + value + \",\" + trueValueBinding + \")\")\n    );\n    addHandler(\n      el,\n      \"change\",\n      \"var $$a=\" +\n        value +\n        \",\" +\n        \"$$el=$event.target,\" +\n        \"$$c=$$el.checked?(\" +\n        trueValueBinding +\n        \"):(\" +\n        falseValueBinding +\n        \");\" +\n        \"if(Array.isArray($$a)){\" +\n        \"var $$v=\" +\n        (number ? \"_n(\" + valueBinding + \")\" : valueBinding) +\n        \",\" +\n        \"$$i=_i($$a,$$v);\" +\n        \"if($$el.checked){$$i<0&&(\" +\n        genAssignmentCode(value, \"$$a.concat([$$v])\") +\n        \")}\" +\n        \"else{$$i>-1&&(\" +\n        genAssignmentCode(value, \"$$a.slice(0,$$i).concat($$a.slice($$i+1))\") +\n        \")}\" +\n        \"}else{\" +\n        genAssignmentCode(value, \"$$c\") +\n        \"}\",\n      null,\n      true\n    );\n  }\n\n  function genRadioModel(el, value, modifiers) {\n    var number = modifiers && modifiers.number;\n    var valueBinding = getBindingAttr(el, \"value\") || \"null\";\n    valueBinding = number ? \"_n(\" + valueBinding + \")\" : valueBinding;\n    addProp(el, \"checked\", \"_q(\" + value + \",\" + valueBinding + \")\");\n    addHandler(el, \"change\", genAssignmentCode(value, valueBinding), null, true);\n  }\n\n  function genSelect(el, value, modifiers) {\n    var number = modifiers && modifiers.number;\n    var selectedVal =\n      \"Array.prototype.filter\" +\n      \".call($event.target.options,function(o){return o.selected})\" +\n      '.map(function(o){var val = \"_value\" in o ? o._value : o.value;' +\n      \"return \" +\n      (number ? \"_n(val)\" : \"val\") +\n      \"})\";\n\n    var assignment = \"$event.target.multiple ? $$selectedVal : $$selectedVal[0]\";\n    var code = \"var $$selectedVal = \" + selectedVal + \";\";\n    code = code + \" \" + genAssignmentCode(value, assignment);\n    addHandler(el, \"change\", code, null, true);\n  }\n\n  function genDefaultModel(el, value, modifiers) {\n    var type = el.attrsMap.type;\n\n    // warn if v-bind:value conflicts with v-model\n    // except for inputs with v-bind:type\n    {\n      var value$1 = el.attrsMap[\"v-bind:value\"] || el.attrsMap[\":value\"];\n      var typeBinding = el.attrsMap[\"v-bind:type\"] || el.attrsMap[\":type\"];\n      if (value$1 && !typeBinding) {\n        var binding = el.attrsMap[\"v-bind:value\"] ? \"v-bind:value\" : \":value\";\n        warn$1(\n          binding +\n            '=\"' +\n            value$1 +\n            '\" conflicts with v-model on the same element ' +\n            \"because the latter already expands to a value binding internally\"\n        );\n      }\n    }\n\n    var ref = modifiers || {};\n    var lazy = ref.lazy;\n    var number = ref.number;\n    var trim = ref.trim;\n    var needCompositionGuard = !lazy && type !== \"range\";\n    var event = lazy ? \"change\" : type === \"range\" ? RANGE_TOKEN : \"input\";\n\n    var valueExpression = \"$event.target.value\";\n    if (trim) {\n      valueExpression = \"$event.target.value.trim()\";\n    }\n    if (number) {\n      valueExpression = \"_n(\" + valueExpression + \")\";\n    }\n\n    var code = genAssignmentCode(value, valueExpression);\n    if (needCompositionGuard) {\n      code = \"if($event.target.composing)return;\" + code;\n    }\n\n    addProp(el, \"value\", \"(\" + value + \")\");\n    addHandler(el, event, code, null, true);\n    if (trim || number) {\n      addHandler(el, \"blur\", \"$forceUpdate()\");\n    }\n  }\n\n  /*  */\n\n  // normalize v-model event tokens that can only be determined at runtime.\n  // it's important to place the event as the first in the array because\n  // the whole point is ensuring the v-model callback gets called before\n  // user-attached handlers.\n  function normalizeEvents(on) {\n    /* istanbul ignore if */\n    if (isDef(on[RANGE_TOKEN])) {\n      // IE input[type=range] only supports `change` event\n      var event = isIE ? \"change\" : \"input\";\n      on[event] = [].concat(on[RANGE_TOKEN], on[event] || []);\n      delete on[RANGE_TOKEN];\n    }\n    // This was originally intended to fix #4521 but no longer necessary\n    // after 2.5. Keeping it for backwards compat with generated code from < 2.4\n    /* istanbul ignore if */\n    if (isDef(on[CHECKBOX_RADIO_TOKEN])) {\n      on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []);\n      delete on[CHECKBOX_RADIO_TOKEN];\n    }\n  }\n\n  var target$1;\n\n  function createOnceHandler(handler, event, capture) {\n    var _target = target$1; // save current target element in closure\n    return function onceHandler() {\n      var res = handler.apply(null, arguments);\n      if (res !== null) {\n        remove$2(event, onceHandler, capture, _target);\n      }\n    };\n  }\n\n  function add$1(event, handler, once$$1, capture, passive) {\n    handler = withMacroTask(handler);\n    if (once$$1) {\n      handler = createOnceHandler(handler, event, capture);\n    }\n    target$1.addEventListener(event, handler, supportsPassive ? { capture: capture, passive: passive } : capture);\n  }\n\n  function remove$2(event, handler, capture, _target) {\n    (_target || target$1).removeEventListener(event, handler._withTask || handler, capture);\n  }\n\n  function updateDOMListeners(oldVnode, vnode) {\n    if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {\n      return;\n    }\n    var on = vnode.data.on || {};\n    var oldOn = oldVnode.data.on || {};\n    target$1 = vnode.elm;\n    normalizeEvents(on);\n    updateListeners(on, oldOn, add$1, remove$2, vnode.context);\n    target$1 = undefined;\n  }\n\n  var events = {\n    create: updateDOMListeners,\n    update: updateDOMListeners\n  };\n\n  /*  */\n\n  function updateDOMProps(oldVnode, vnode) {\n    if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {\n      return;\n    }\n    var key, cur;\n    var elm = vnode.elm;\n    var oldProps = oldVnode.data.domProps || {};\n    var props = vnode.data.domProps || {};\n    // clone observed objects, as the user probably wants to mutate it\n    if (isDef(props.__ob__)) {\n      props = vnode.data.domProps = extend({}, props);\n    }\n\n    for (key in oldProps) {\n      if (isUndef(props[key])) {\n        elm[key] = \"\";\n      }\n    }\n    for (key in props) {\n      cur = props[key];\n      // ignore children if the node has textContent or innerHTML,\n      // as these will throw away existing DOM nodes and cause removal errors\n      // on subsequent patches (#3360)\n      if (key === \"textContent\" || key === \"innerHTML\") {\n        if (vnode.children) {\n          vnode.children.length = 0;\n        }\n        if (cur === oldProps[key]) {\n          continue;\n        }\n        // #6601 work around Chrome version <= 55 bug where single textNode\n        // replaced by innerHTML/textContent retains its parentNode property\n        if (elm.childNodes.length === 1) {\n          elm.removeChild(elm.childNodes[0]);\n        }\n      }\n\n      if (key === \"value\") {\n        // store value as _value as well since\n        // non-string values will be stringified\n        elm._value = cur;\n        // avoid resetting cursor position when value is the same\n        var strCur = isUndef(cur) ? \"\" : String(cur);\n        if (shouldUpdateValue(elm, strCur)) {\n          elm.value = strCur;\n        }\n      } else {\n        elm[key] = cur;\n      }\n    }\n  }\n\n  // check platforms/web/util/attrs.js acceptValue\n\n  function shouldUpdateValue(elm, checkVal) {\n    return (\n      !elm.composing &&\n      (elm.tagName === \"OPTION\" || isNotInFocusAndDirty(elm, checkVal) || isDirtyWithModifiers(elm, checkVal))\n    );\n  }\n\n  function isNotInFocusAndDirty(elm, checkVal) {\n    // return true when textbox (.number and .trim) loses focus and its value is\n    // not equal to the updated value\n    var notInFocus = true;\n    // #6157\n    // work around IE bug when accessing document.activeElement in an iframe\n    try {\n      notInFocus = document.activeElement !== elm;\n    } catch (e) {}\n    return notInFocus && elm.value !== checkVal;\n  }\n\n  function isDirtyWithModifiers(elm, newVal) {\n    var value = elm.value;\n    var modifiers = elm._vModifiers; // injected by v-model runtime\n    if (isDef(modifiers)) {\n      if (modifiers.lazy) {\n        // inputs with lazy should only be updated when not in focus\n        return false;\n      }\n      if (modifiers.number) {\n        return toNumber(value) !== toNumber(newVal);\n      }\n      if (modifiers.trim) {\n        return value.trim() !== newVal.trim();\n      }\n    }\n    return value !== newVal;\n  }\n\n  var domProps = {\n    create: updateDOMProps,\n    update: updateDOMProps\n  };\n\n  /*  */\n\n  var parseStyleText = cached(function(cssText) {\n    var res = {};\n    var listDelimiter = /;(?![^(]*\\))/g;\n    var propertyDelimiter = /:(.+)/;\n    cssText.split(listDelimiter).forEach(function(item) {\n      if (item) {\n        var tmp = item.split(propertyDelimiter);\n        tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim());\n      }\n    });\n    return res;\n  });\n\n  // merge static and dynamic style data on the same vnode\n  function normalizeStyleData(data) {\n    var style = normalizeStyleBinding(data.style);\n    // static style is pre-processed into an object during compilation\n    // and is always a fresh object, so it's safe to merge into it\n    return data.staticStyle ? extend(data.staticStyle, style) : style;\n  }\n\n  // normalize possible array / string values into Object\n  function normalizeStyleBinding(bindingStyle) {\n    if (Array.isArray(bindingStyle)) {\n      return toObject(bindingStyle);\n    }\n    if (typeof bindingStyle === \"string\") {\n      return parseStyleText(bindingStyle);\n    }\n    return bindingStyle;\n  }\n\n  /**\n   * parent component style should be after child's\n   * so that parent component's style could override it\n   */\n  function getStyle(vnode, checkChild) {\n    var res = {};\n    var styleData;\n\n    if (checkChild) {\n      var childNode = vnode;\n      while (childNode.componentInstance) {\n        childNode = childNode.componentInstance._vnode;\n        if (childNode && childNode.data && (styleData = normalizeStyleData(childNode.data))) {\n          extend(res, styleData);\n        }\n      }\n    }\n\n    if ((styleData = normalizeStyleData(vnode.data))) {\n      extend(res, styleData);\n    }\n\n    var parentNode = vnode;\n    while ((parentNode = parentNode.parent)) {\n      if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {\n        extend(res, styleData);\n      }\n    }\n    return res;\n  }\n\n  /*  */\n\n  var cssVarRE = /^--/;\n  var importantRE = /\\s*!important$/;\n  var setProp = function(el, name, val) {\n    /* istanbul ignore if */\n    if (cssVarRE.test(name)) {\n      el.style.setProperty(name, val);\n    } else if (importantRE.test(val)) {\n      el.style.setProperty(name, val.replace(importantRE, \"\"), \"important\");\n    } else {\n      var normalizedName = normalize(name);\n      if (Array.isArray(val)) {\n        // Support values array created by autoprefixer, e.g.\n        // {display: [\"-webkit-box\", \"-ms-flexbox\", \"flex\"]}\n        // Set them one by one, and the browser will only set those it can recognize\n        for (var i = 0, len = val.length; i < len; i++) {\n          el.style[normalizedName] = val[i];\n        }\n      } else {\n        el.style[normalizedName] = val;\n      }\n    }\n  };\n\n  var vendorNames = [\"Webkit\", \"Moz\", \"ms\"];\n\n  var emptyStyle;\n  var normalize = cached(function(prop) {\n    emptyStyle = emptyStyle || document.createElement(\"div\").style;\n    prop = camelize(prop);\n    if (prop !== \"filter\" && prop in emptyStyle) {\n      return prop;\n    }\n    var capName = prop.charAt(0).toUpperCase() + prop.slice(1);\n    for (var i = 0; i < vendorNames.length; i++) {\n      var name = vendorNames[i] + capName;\n      if (name in emptyStyle) {\n        return name;\n      }\n    }\n  });\n\n  function updateStyle(oldVnode, vnode) {\n    var data = vnode.data;\n    var oldData = oldVnode.data;\n\n    if (isUndef(data.staticStyle) && isUndef(data.style) && isUndef(oldData.staticStyle) && isUndef(oldData.style)) {\n      return;\n    }\n\n    var cur, name;\n    var el = vnode.elm;\n    var oldStaticStyle = oldData.staticStyle;\n    var oldStyleBinding = oldData.normalizedStyle || oldData.style || {};\n\n    // if static style exists, stylebinding already merged into it when doing normalizeStyleData\n    var oldStyle = oldStaticStyle || oldStyleBinding;\n\n    var style = normalizeStyleBinding(vnode.data.style) || {};\n\n    // store normalized style under a different key for next diff\n    // make sure to clone it if it's reactive, since the user likely wants\n    // to mutate it.\n    vnode.data.normalizedStyle = isDef(style.__ob__) ? extend({}, style) : style;\n\n    var newStyle = getStyle(vnode, true);\n\n    for (name in oldStyle) {\n      if (isUndef(newStyle[name])) {\n        setProp(el, name, \"\");\n      }\n    }\n    for (name in newStyle) {\n      cur = newStyle[name];\n      if (cur !== oldStyle[name]) {\n        // ie9 setting to null has no effect, must use empty string\n        setProp(el, name, cur == null ? \"\" : cur);\n      }\n    }\n  }\n\n  var style = {\n    create: updateStyle,\n    update: updateStyle\n  };\n\n  /*  */\n\n  /**\n   * Add class with compatibility for SVG since classList is not supported on\n   * SVG elements in IE\n   */\n  function addClass(el, cls) {\n    /* istanbul ignore if */\n    if (!cls || !(cls = cls.trim())) {\n      return;\n    }\n\n    /* istanbul ignore else */\n    if (el.classList) {\n      if (cls.indexOf(\" \") > -1) {\n        cls.split(/\\s+/).forEach(function(c) {\n          return el.classList.add(c);\n        });\n      } else {\n        el.classList.add(cls);\n      }\n    } else {\n      var cur = \" \" + (el.getAttribute(\"class\") || \"\") + \" \";\n      if (cur.indexOf(\" \" + cls + \" \") < 0) {\n        el.setAttribute(\"class\", (cur + cls).trim());\n      }\n    }\n  }\n\n  /**\n   * Remove class with compatibility for SVG since classList is not supported on\n   * SVG elements in IE\n   */\n  function removeClass(el, cls) {\n    /* istanbul ignore if */\n    if (!cls || !(cls = cls.trim())) {\n      return;\n    }\n\n    /* istanbul ignore else */\n    if (el.classList) {\n      if (cls.indexOf(\" \") > -1) {\n        cls.split(/\\s+/).forEach(function(c) {\n          return el.classList.remove(c);\n        });\n      } else {\n        el.classList.remove(cls);\n      }\n      if (!el.classList.length) {\n        el.removeAttribute(\"class\");\n      }\n    } else {\n      var cur = \" \" + (el.getAttribute(\"class\") || \"\") + \" \";\n      var tar = \" \" + cls + \" \";\n      while (cur.indexOf(tar) >= 0) {\n        cur = cur.replace(tar, \" \");\n      }\n      cur = cur.trim();\n      if (cur) {\n        el.setAttribute(\"class\", cur);\n      } else {\n        el.removeAttribute(\"class\");\n      }\n    }\n  }\n\n  /*  */\n\n  function resolveTransition(def) {\n    if (!def) {\n      return;\n    }\n    /* istanbul ignore else */\n    if (typeof def === \"object\") {\n      var res = {};\n      if (def.css !== false) {\n        extend(res, autoCssTransition(def.name || \"v\"));\n      }\n      extend(res, def);\n      return res;\n    } else if (typeof def === \"string\") {\n      return autoCssTransition(def);\n    }\n  }\n\n  var autoCssTransition = cached(function(name) {\n    return {\n      enterClass: name + \"-enter\",\n      enterToClass: name + \"-enter-to\",\n      enterActiveClass: name + \"-enter-active\",\n      leaveClass: name + \"-leave\",\n      leaveToClass: name + \"-leave-to\",\n      leaveActiveClass: name + \"-leave-active\"\n    };\n  });\n\n  var hasTransition = inBrowser && !isIE9;\n  var TRANSITION = \"transition\";\n  var ANIMATION = \"animation\";\n\n  // Transition property/event sniffing\n  var transitionProp = \"transition\";\n  var transitionEndEvent = \"transitionend\";\n  var animationProp = \"animation\";\n  var animationEndEvent = \"animationend\";\n  if (hasTransition) {\n    /* istanbul ignore if */\n    if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {\n      transitionProp = \"WebkitTransition\";\n      transitionEndEvent = \"webkitTransitionEnd\";\n    }\n    if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {\n      animationProp = \"WebkitAnimation\";\n      animationEndEvent = \"webkitAnimationEnd\";\n    }\n  }\n\n  // binding to window is necessary to make hot reload work in IE in strict mode\n  var raf = inBrowser\n    ? window.requestAnimationFrame\n      ? window.requestAnimationFrame.bind(window)\n      : setTimeout\n    : /* istanbul ignore next */ function(fn) {\n        return fn();\n      };\n\n  function nextFrame(fn) {\n    raf(function() {\n      raf(fn);\n    });\n  }\n\n  function addTransitionClass(el, cls) {\n    var transitionClasses = el._transitionClasses || (el._transitionClasses = []);\n    if (transitionClasses.indexOf(cls) < 0) {\n      transitionClasses.push(cls);\n      addClass(el, cls);\n    }\n  }\n\n  function removeTransitionClass(el, cls) {\n    if (el._transitionClasses) {\n      remove(el._transitionClasses, cls);\n    }\n    removeClass(el, cls);\n  }\n\n  function whenTransitionEnds(el, expectedType, cb) {\n    var ref = getTransitionInfo(el, expectedType);\n    var type = ref.type;\n    var timeout = ref.timeout;\n    var propCount = ref.propCount;\n    if (!type) {\n      return cb();\n    }\n    var event = type === TRANSITION ? transitionEndEvent : animationEndEvent;\n    var ended = 0;\n    var end = function() {\n      el.removeEventListener(event, onEnd);\n      cb();\n    };\n    var onEnd = function(e) {\n      if (e.target === el) {\n        if (++ended >= propCount) {\n          end();\n        }\n      }\n    };\n    setTimeout(function() {\n      if (ended < propCount) {\n        end();\n      }\n    }, timeout + 1);\n    el.addEventListener(event, onEnd);\n  }\n\n  var transformRE = /\\b(transform|all)(,|$)/;\n\n  function getTransitionInfo(el, expectedType) {\n    var styles = window.getComputedStyle(el);\n    var transitionDelays = styles[transitionProp + \"Delay\"].split(\", \");\n    var transitionDurations = styles[transitionProp + \"Duration\"].split(\", \");\n    var transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n    var animationDelays = styles[animationProp + \"Delay\"].split(\", \");\n    var animationDurations = styles[animationProp + \"Duration\"].split(\", \");\n    var animationTimeout = getTimeout(animationDelays, animationDurations);\n\n    var type;\n    var timeout = 0;\n    var propCount = 0;\n    /* istanbul ignore if */\n    if (expectedType === TRANSITION) {\n      if (transitionTimeout > 0) {\n        type = TRANSITION;\n        timeout = transitionTimeout;\n        propCount = transitionDurations.length;\n      }\n    } else if (expectedType === ANIMATION) {\n      if (animationTimeout > 0) {\n        type = ANIMATION;\n        timeout = animationTimeout;\n        propCount = animationDurations.length;\n      }\n    } else {\n      timeout = Math.max(transitionTimeout, animationTimeout);\n      type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null;\n      propCount = type ? (type === TRANSITION ? transitionDurations.length : animationDurations.length) : 0;\n    }\n    var hasTransform = type === TRANSITION && transformRE.test(styles[transitionProp + \"Property\"]);\n    return {\n      type: type,\n      timeout: timeout,\n      propCount: propCount,\n      hasTransform: hasTransform\n    };\n  }\n\n  function getTimeout(delays, durations) {\n    /* istanbul ignore next */\n    while (delays.length < durations.length) {\n      delays = delays.concat(delays);\n    }\n\n    return Math.max.apply(\n      null,\n      durations.map(function(d, i) {\n        return toMs(d) + toMs(delays[i]);\n      })\n    );\n  }\n\n  function toMs(s) {\n    return Number(s.slice(0, -1)) * 1000;\n  }\n\n  /*  */\n\n  function enter(vnode, toggleDisplay) {\n    var el = vnode.elm;\n\n    // call leave callback now\n    if (isDef(el._leaveCb)) {\n      el._leaveCb.cancelled = true;\n      el._leaveCb();\n    }\n\n    var data = resolveTransition(vnode.data.transition);\n    if (isUndef(data)) {\n      return;\n    }\n\n    /* istanbul ignore if */\n    if (isDef(el._enterCb) || el.nodeType !== 1) {\n      return;\n    }\n\n    var css = data.css;\n    var type = data.type;\n    var enterClass = data.enterClass;\n    var enterToClass = data.enterToClass;\n    var enterActiveClass = data.enterActiveClass;\n    var appearClass = data.appearClass;\n    var appearToClass = data.appearToClass;\n    var appearActiveClass = data.appearActiveClass;\n    var beforeEnter = data.beforeEnter;\n    var enter = data.enter;\n    var afterEnter = data.afterEnter;\n    var enterCancelled = data.enterCancelled;\n    var beforeAppear = data.beforeAppear;\n    var appear = data.appear;\n    var afterAppear = data.afterAppear;\n    var appearCancelled = data.appearCancelled;\n    var duration = data.duration;\n\n    // activeInstance will always be the <transition> component managing this\n    // transition. One edge case to check is when the <transition> is placed\n    // as the root node of a child component. In that case we need to check\n    // <transition>'s parent for appear check.\n    var context = activeInstance;\n    var transitionNode = activeInstance.$vnode;\n    while (transitionNode && transitionNode.parent) {\n      transitionNode = transitionNode.parent;\n      context = transitionNode.context;\n    }\n\n    var isAppear = !context._isMounted || !vnode.isRootInsert;\n\n    if (isAppear && !appear && appear !== \"\") {\n      return;\n    }\n\n    var startClass = isAppear && appearClass ? appearClass : enterClass;\n    var activeClass = isAppear && appearActiveClass ? appearActiveClass : enterActiveClass;\n    var toClass = isAppear && appearToClass ? appearToClass : enterToClass;\n\n    var beforeEnterHook = isAppear ? beforeAppear || beforeEnter : beforeEnter;\n    var enterHook = isAppear ? (typeof appear === \"function\" ? appear : enter) : enter;\n    var afterEnterHook = isAppear ? afterAppear || afterEnter : afterEnter;\n    var enterCancelledHook = isAppear ? appearCancelled || enterCancelled : enterCancelled;\n\n    var explicitEnterDuration = toNumber(isObject(duration) ? duration.enter : duration);\n\n    if (\"development\" !== \"production\" && explicitEnterDuration != null) {\n      checkDuration(explicitEnterDuration, \"enter\", vnode);\n    }\n\n    var expectsCSS = css !== false && !isIE9;\n    var userWantsControl = getHookArgumentsLength(enterHook);\n\n    var cb = (el._enterCb = once(function() {\n      if (expectsCSS) {\n        removeTransitionClass(el, toClass);\n        removeTransitionClass(el, activeClass);\n      }\n      if (cb.cancelled) {\n        if (expectsCSS) {\n          removeTransitionClass(el, startClass);\n        }\n        enterCancelledHook && enterCancelledHook(el);\n      } else {\n        afterEnterHook && afterEnterHook(el);\n      }\n      el._enterCb = null;\n    }));\n\n    if (!vnode.data.show) {\n      // remove pending leave element on enter by injecting an insert hook\n      mergeVNodeHook(vnode, \"insert\", function() {\n        var parent = el.parentNode;\n        var pendingNode = parent && parent._pending && parent._pending[vnode.key];\n        if (pendingNode && pendingNode.tag === vnode.tag && pendingNode.elm._leaveCb) {\n          pendingNode.elm._leaveCb();\n        }\n        enterHook && enterHook(el, cb);\n      });\n    }\n\n    // start enter transition\n    beforeEnterHook && beforeEnterHook(el);\n    if (expectsCSS) {\n      addTransitionClass(el, startClass);\n      addTransitionClass(el, activeClass);\n      nextFrame(function() {\n        removeTransitionClass(el, startClass);\n        if (!cb.cancelled) {\n          addTransitionClass(el, toClass);\n          if (!userWantsControl) {\n            if (isValidDuration(explicitEnterDuration)) {\n              setTimeout(cb, explicitEnterDuration);\n            } else {\n              whenTransitionEnds(el, type, cb);\n            }\n          }\n        }\n      });\n    }\n\n    if (vnode.data.show) {\n      toggleDisplay && toggleDisplay();\n      enterHook && enterHook(el, cb);\n    }\n\n    if (!expectsCSS && !userWantsControl) {\n      cb();\n    }\n  }\n\n  function leave(vnode, rm) {\n    var el = vnode.elm;\n\n    // call enter callback now\n    if (isDef(el._enterCb)) {\n      el._enterCb.cancelled = true;\n      el._enterCb();\n    }\n\n    var data = resolveTransition(vnode.data.transition);\n    if (isUndef(data) || el.nodeType !== 1) {\n      return rm();\n    }\n\n    /* istanbul ignore if */\n    if (isDef(el._leaveCb)) {\n      return;\n    }\n\n    var css = data.css;\n    var type = data.type;\n    var leaveClass = data.leaveClass;\n    var leaveToClass = data.leaveToClass;\n    var leaveActiveClass = data.leaveActiveClass;\n    var beforeLeave = data.beforeLeave;\n    var leave = data.leave;\n    var afterLeave = data.afterLeave;\n    var leaveCancelled = data.leaveCancelled;\n    var delayLeave = data.delayLeave;\n    var duration = data.duration;\n\n    var expectsCSS = css !== false && !isIE9;\n    var userWantsControl = getHookArgumentsLength(leave);\n\n    var explicitLeaveDuration = toNumber(isObject(duration) ? duration.leave : duration);\n\n    if (\"development\" !== \"production\" && isDef(explicitLeaveDuration)) {\n      checkDuration(explicitLeaveDuration, \"leave\", vnode);\n    }\n\n    var cb = (el._leaveCb = once(function() {\n      if (el.parentNode && el.parentNode._pending) {\n        el.parentNode._pending[vnode.key] = null;\n      }\n      if (expectsCSS) {\n        removeTransitionClass(el, leaveToClass);\n        removeTransitionClass(el, leaveActiveClass);\n      }\n      if (cb.cancelled) {\n        if (expectsCSS) {\n          removeTransitionClass(el, leaveClass);\n        }\n        leaveCancelled && leaveCancelled(el);\n      } else {\n        rm();\n        afterLeave && afterLeave(el);\n      }\n      el._leaveCb = null;\n    }));\n\n    if (delayLeave) {\n      delayLeave(performLeave);\n    } else {\n      performLeave();\n    }\n\n    function performLeave() {\n      // the delayed leave may have already been cancelled\n      if (cb.cancelled) {\n        return;\n      }\n      // record leaving element\n      if (!vnode.data.show) {\n        (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode;\n      }\n      beforeLeave && beforeLeave(el);\n      if (expectsCSS) {\n        addTransitionClass(el, leaveClass);\n        addTransitionClass(el, leaveActiveClass);\n        nextFrame(function() {\n          removeTransitionClass(el, leaveClass);\n          if (!cb.cancelled) {\n            addTransitionClass(el, leaveToClass);\n            if (!userWantsControl) {\n              if (isValidDuration(explicitLeaveDuration)) {\n                setTimeout(cb, explicitLeaveDuration);\n              } else {\n                whenTransitionEnds(el, type, cb);\n              }\n            }\n          }\n        });\n      }\n      leave && leave(el, cb);\n      if (!expectsCSS && !userWantsControl) {\n        cb();\n      }\n    }\n  }\n\n  // only used in dev mode\n  function checkDuration(val, name, vnode) {\n    if (typeof val !== \"number\") {\n      warn(\n        \"<transition> explicit \" + name + \" duration is not a valid number - \" + \"got \" + JSON.stringify(val) + \".\",\n        vnode.context\n      );\n    } else if (isNaN(val)) {\n      warn(\n        \"<transition> explicit \" + name + \" duration is NaN - \" + \"the duration expression might be incorrect.\",\n        vnode.context\n      );\n    }\n  }\n\n  function isValidDuration(val) {\n    return typeof val === \"number\" && !isNaN(val);\n  }\n\n  /**\n   * Normalize a transition hook's argument length. The hook may be:\n   * - a merged hook (invoker) with the original in .fns\n   * - a wrapped component method (check ._length)\n   * - a plain function (.length)\n   */\n  function getHookArgumentsLength(fn) {\n    if (isUndef(fn)) {\n      return false;\n    }\n    var invokerFns = fn.fns;\n    if (isDef(invokerFns)) {\n      // invoker\n      return getHookArgumentsLength(Array.isArray(invokerFns) ? invokerFns[0] : invokerFns);\n    } else {\n      return (fn._length || fn.length) > 1;\n    }\n  }\n\n  function _enter(_, vnode) {\n    if (vnode.data.show !== true) {\n      enter(vnode);\n    }\n  }\n\n  var transition = inBrowser\n    ? {\n        create: _enter,\n        activate: _enter,\n        remove: function remove$$1(vnode, rm) {\n          /* istanbul ignore else */\n          if (vnode.data.show !== true) {\n            leave(vnode, rm);\n          } else {\n            rm();\n          }\n        }\n      }\n    : {};\n\n  var platformModules = [attrs, klass, events, domProps, style, transition];\n\n  /*  */\n\n  // the directive module should be applied last, after all\n  // built-in modules have been applied.\n  var modules = platformModules.concat(baseModules);\n\n  var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });\n\n  /**\n   * Not type checking this file because flow doesn't like attaching\n   * properties to Elements.\n   */\n\n  /* istanbul ignore if */\n  if (isIE9) {\n    // http://www.matts411.com/post/internet-explorer-9-oninput/\n    document.addEventListener(\"selectionchange\", function() {\n      var el = document.activeElement;\n      if (el && el.vmodel) {\n        trigger(el, \"input\");\n      }\n    });\n  }\n\n  var directive = {\n    inserted: function inserted(el, binding, vnode, oldVnode) {\n      if (vnode.tag === \"select\") {\n        // #6903\n        if (oldVnode.elm && !oldVnode.elm._vOptions) {\n          mergeVNodeHook(vnode, \"postpatch\", function() {\n            directive.componentUpdated(el, binding, vnode);\n          });\n        } else {\n          setSelected(el, binding, vnode.context);\n        }\n        el._vOptions = [].map.call(el.options, getValue);\n      } else if (vnode.tag === \"textarea\" || isTextInputType(el.type)) {\n        el._vModifiers = binding.modifiers;\n        if (!binding.modifiers.lazy) {\n          el.addEventListener(\"compositionstart\", onCompositionStart);\n          el.addEventListener(\"compositionend\", onCompositionEnd);\n          // Safari < 10.2 & UIWebView doesn't fire compositionend when\n          // switching focus before confirming composition choice\n          // this also fixes the issue where some browsers e.g. iOS Chrome\n          // fires \"change\" instead of \"input\" on autocomplete.\n          el.addEventListener(\"change\", onCompositionEnd);\n          /* istanbul ignore if */\n          if (isIE9) {\n            el.vmodel = true;\n          }\n        }\n      }\n    },\n\n    componentUpdated: function componentUpdated(el, binding, vnode) {\n      if (vnode.tag === \"select\") {\n        setSelected(el, binding, vnode.context);\n        // in case the options rendered by v-for have changed,\n        // it's possible that the value is out-of-sync with the rendered options.\n        // detect such cases and filter out values that no longer has a matching\n        // option in the DOM.\n        var prevOptions = el._vOptions;\n        var curOptions = (el._vOptions = [].map.call(el.options, getValue));\n        if (\n          curOptions.some(function(o, i) {\n            return !looseEqual(o, prevOptions[i]);\n          })\n        ) {\n          // trigger change event if\n          // no matching option found for at least one value\n          var needReset = el.multiple\n            ? binding.value.some(function(v) {\n                return hasNoMatchingOption(v, curOptions);\n              })\n            : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions);\n          if (needReset) {\n            trigger(el, \"change\");\n          }\n        }\n      }\n    }\n  };\n\n  function setSelected(el, binding, vm) {\n    actuallySetSelected(el, binding, vm);\n    /* istanbul ignore if */\n    if (isIE || isEdge) {\n      setTimeout(function() {\n        actuallySetSelected(el, binding, vm);\n      }, 0);\n    }\n  }\n\n  function actuallySetSelected(el, binding, vm) {\n    var value = binding.value;\n    var isMultiple = el.multiple;\n    if (isMultiple && !Array.isArray(value)) {\n      \"development\" !== \"production\" &&\n        warn(\n          '<select multiple v-model=\"' +\n            binding.expression +\n            '\"> ' +\n            \"expects an Array value for its binding, but got \" +\n            Object.prototype.toString.call(value).slice(8, -1),\n          vm\n        );\n      return;\n    }\n    var selected, option;\n    for (var i = 0, l = el.options.length; i < l; i++) {\n      option = el.options[i];\n      if (isMultiple) {\n        selected = looseIndexOf(value, getValue(option)) > -1;\n        if (option.selected !== selected) {\n          option.selected = selected;\n        }\n      } else {\n        if (looseEqual(getValue(option), value)) {\n          if (el.selectedIndex !== i) {\n            el.selectedIndex = i;\n          }\n          return;\n        }\n      }\n    }\n    if (!isMultiple) {\n      el.selectedIndex = -1;\n    }\n  }\n\n  function hasNoMatchingOption(value, options) {\n    return options.every(function(o) {\n      return !looseEqual(o, value);\n    });\n  }\n\n  function getValue(option) {\n    return \"_value\" in option ? option._value : option.value;\n  }\n\n  function onCompositionStart(e) {\n    e.target.composing = true;\n  }\n\n  function onCompositionEnd(e) {\n    // prevent triggering an input event for no reason\n    if (!e.target.composing) {\n      return;\n    }\n    e.target.composing = false;\n    trigger(e.target, \"input\");\n  }\n\n  function trigger(el, type) {\n    var e = document.createEvent(\"HTMLEvents\");\n    e.initEvent(type, true, true);\n    el.dispatchEvent(e);\n  }\n\n  /*  */\n\n  // recursively search for possible transition defined inside the component root\n  function locateNode(vnode) {\n    return vnode.componentInstance && (!vnode.data || !vnode.data.transition)\n      ? locateNode(vnode.componentInstance._vnode)\n      : vnode;\n  }\n\n  var show = {\n    bind: function bind(el, ref, vnode) {\n      var value = ref.value;\n\n      vnode = locateNode(vnode);\n      var transition$$1 = vnode.data && vnode.data.transition;\n      var originalDisplay = (el.__vOriginalDisplay = el.style.display === \"none\" ? \"\" : el.style.display);\n      if (value && transition$$1) {\n        vnode.data.show = true;\n        enter(vnode, function() {\n          el.style.display = originalDisplay;\n        });\n      } else {\n        el.style.display = value ? originalDisplay : \"none\";\n      }\n    },\n\n    update: function update(el, ref, vnode) {\n      var value = ref.value;\n      var oldValue = ref.oldValue;\n\n      /* istanbul ignore if */\n      if (!value === !oldValue) {\n        return;\n      }\n      vnode = locateNode(vnode);\n      var transition$$1 = vnode.data && vnode.data.transition;\n      if (transition$$1) {\n        vnode.data.show = true;\n        if (value) {\n          enter(vnode, function() {\n            el.style.display = el.__vOriginalDisplay;\n          });\n        } else {\n          leave(vnode, function() {\n            el.style.display = \"none\";\n          });\n        }\n      } else {\n        el.style.display = value ? el.__vOriginalDisplay : \"none\";\n      }\n    },\n\n    unbind: function unbind(el, binding, vnode, oldVnode, isDestroy) {\n      if (!isDestroy) {\n        el.style.display = el.__vOriginalDisplay;\n      }\n    }\n  };\n\n  var platformDirectives = {\n    model: directive,\n    show: show\n  };\n\n  /*  */\n\n  // Provides transition support for a single element/component.\n  // supports transition mode (out-in / in-out)\n\n  var transitionProps = {\n    name: String,\n    appear: Boolean,\n    css: Boolean,\n    mode: String,\n    type: String,\n    enterClass: String,\n    leaveClass: String,\n    enterToClass: String,\n    leaveToClass: String,\n    enterActiveClass: String,\n    leaveActiveClass: String,\n    appearClass: String,\n    appearActiveClass: String,\n    appearToClass: String,\n    duration: [Number, String, Object]\n  };\n\n  // in case the child is also an abstract component, e.g. <keep-alive>\n  // we want to recursively retrieve the real component to be rendered\n  function getRealChild(vnode) {\n    var compOptions = vnode && vnode.componentOptions;\n    if (compOptions && compOptions.Ctor.options.abstract) {\n      return getRealChild(getFirstComponentChild(compOptions.children));\n    } else {\n      return vnode;\n    }\n  }\n\n  function extractTransitionData(comp) {\n    var data = {};\n    var options = comp.$options;\n    // props\n    for (var key in options.propsData) {\n      data[key] = comp[key];\n    }\n    // events.\n    // extract listeners and pass them directly to the transition methods\n    var listeners = options._parentListeners;\n    for (var key$1 in listeners) {\n      data[camelize(key$1)] = listeners[key$1];\n    }\n    return data;\n  }\n\n  function placeholder(h, rawChild) {\n    if (/\\d-keep-alive$/.test(rawChild.tag)) {\n      return h(\"keep-alive\", {\n        props: rawChild.componentOptions.propsData\n      });\n    }\n  }\n\n  function hasParentTransition(vnode) {\n    while ((vnode = vnode.parent)) {\n      if (vnode.data.transition) {\n        return true;\n      }\n    }\n  }\n\n  function isSameChild(child, oldChild) {\n    return oldChild.key === child.key && oldChild.tag === child.tag;\n  }\n\n  var Transition = {\n    name: \"transition\",\n    props: transitionProps,\n    abstract: true,\n\n    render: function render(h) {\n      var this$1 = this;\n\n      var children = this.$slots.default;\n      if (!children) {\n        return;\n      }\n\n      // filter out text nodes (possible whitespaces)\n      children = children.filter(function(c) {\n        return c.tag || isAsyncPlaceholder(c);\n      });\n      /* istanbul ignore if */\n      if (!children.length) {\n        return;\n      }\n\n      // warn multiple elements\n      if (\"development\" !== \"production\" && children.length > 1) {\n        warn(\"<transition> can only be used on a single element. Use \" + \"<transition-group> for lists.\", this.$parent);\n      }\n\n      var mode = this.mode;\n\n      // warn invalid mode\n      if (\"development\" !== \"production\" && mode && mode !== \"in-out\" && mode !== \"out-in\") {\n        warn(\"invalid <transition> mode: \" + mode, this.$parent);\n      }\n\n      var rawChild = children[0];\n\n      // if this is a component root node and the component's\n      // parent container node also has transition, skip.\n      if (hasParentTransition(this.$vnode)) {\n        return rawChild;\n      }\n\n      // apply transition data to child\n      // use getRealChild() to ignore abstract components e.g. keep-alive\n      var child = getRealChild(rawChild);\n      /* istanbul ignore if */\n      if (!child) {\n        return rawChild;\n      }\n\n      if (this._leaving) {\n        return placeholder(h, rawChild);\n      }\n\n      // ensure a key that is unique to the vnode type and to this transition\n      // component instance. This key will be used to remove pending leaving nodes\n      // during entering.\n      var id = \"__transition-\" + this._uid + \"-\";\n      child.key =\n        child.key == null\n          ? child.isComment\n            ? id + \"comment\"\n            : id + child.tag\n          : isPrimitive(child.key)\n            ? String(child.key).indexOf(id) === 0\n              ? child.key\n              : id + child.key\n            : child.key;\n\n      var data = ((child.data || (child.data = {})).transition = extractTransitionData(this));\n      var oldRawChild = this._vnode;\n      var oldChild = getRealChild(oldRawChild);\n\n      // mark v-show\n      // so that the transition module can hand over the control to the directive\n      if (\n        child.data.directives &&\n        child.data.directives.some(function(d) {\n          return d.name === \"show\";\n        })\n      ) {\n        child.data.show = true;\n      }\n\n      if (\n        oldChild &&\n        oldChild.data &&\n        !isSameChild(child, oldChild) &&\n        !isAsyncPlaceholder(oldChild) &&\n        // #6687 component root is a comment node\n        !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)\n      ) {\n        // replace old child transition data with fresh one\n        // important for dynamic transitions!\n        var oldData = (oldChild.data.transition = extend({}, data));\n        // handle transition mode\n        if (mode === \"out-in\") {\n          // return placeholder node and queue update when leave finishes\n          this._leaving = true;\n          mergeVNodeHook(oldData, \"afterLeave\", function() {\n            this$1._leaving = false;\n            this$1.$forceUpdate();\n          });\n          return placeholder(h, rawChild);\n        } else if (mode === \"in-out\") {\n          if (isAsyncPlaceholder(child)) {\n            return oldRawChild;\n          }\n          var delayedLeave;\n          var performLeave = function() {\n            delayedLeave();\n          };\n          mergeVNodeHook(data, \"afterEnter\", performLeave);\n          mergeVNodeHook(data, \"enterCancelled\", performLeave);\n          mergeVNodeHook(oldData, \"delayLeave\", function(leave) {\n            delayedLeave = leave;\n          });\n        }\n      }\n\n      return rawChild;\n    }\n  };\n\n  /*  */\n\n  // Provides transition support for list items.\n  // supports move transitions using the FLIP technique.\n\n  // Because the vdom's children update algorithm is \"unstable\" - i.e.\n  // it doesn't guarantee the relative positioning of removed elements,\n  // we force transition-group to update its children into two passes:\n  // in the first pass, we remove all nodes that need to be removed,\n  // triggering their leaving transition; in the second pass, we insert/move\n  // into the final desired state. This way in the second pass removed\n  // nodes will remain where they should be.\n\n  var props = extend(\n    {\n      tag: String,\n      moveClass: String\n    },\n    transitionProps\n  );\n\n  delete props.mode;\n\n  var TransitionGroup = {\n    props: props,\n\n    render: function render(h) {\n      var tag = this.tag || this.$vnode.data.tag || \"span\";\n      var map = Object.create(null);\n      var prevChildren = (this.prevChildren = this.children);\n      var rawChildren = this.$slots.default || [];\n      var children = (this.children = []);\n      var transitionData = extractTransitionData(this);\n\n      for (var i = 0; i < rawChildren.length; i++) {\n        var c = rawChildren[i];\n        if (c.tag) {\n          if (c.key != null && String(c.key).indexOf(\"__vlist\") !== 0) {\n            children.push(c);\n            map[c.key] = c;\n            (c.data || (c.data = {})).transition = transitionData;\n          } else {\n            var opts = c.componentOptions;\n            var name = opts ? opts.Ctor.options.name || opts.tag || \"\" : c.tag;\n            warn(\"<transition-group> children must be keyed: <\" + name + \">\");\n          }\n        }\n      }\n\n      if (prevChildren) {\n        var kept = [];\n        var removed = [];\n        for (var i$1 = 0; i$1 < prevChildren.length; i$1++) {\n          var c$1 = prevChildren[i$1];\n          c$1.data.transition = transitionData;\n          c$1.data.pos = c$1.elm.getBoundingClientRect();\n          if (map[c$1.key]) {\n            kept.push(c$1);\n          } else {\n            removed.push(c$1);\n          }\n        }\n        this.kept = h(tag, null, kept);\n        this.removed = removed;\n      }\n\n      return h(tag, null, children);\n    },\n\n    beforeUpdate: function beforeUpdate() {\n      // force removing pass\n      this.__patch__(\n        this._vnode,\n        this.kept,\n        false, // hydrating\n        true // removeOnly (!important, avoids unnecessary moves)\n      );\n      this._vnode = this.kept;\n    },\n\n    updated: function updated() {\n      var children = this.prevChildren;\n      var moveClass = this.moveClass || (this.name || \"v\") + \"-move\";\n      if (!children.length || !this.hasMove(children[0].elm, moveClass)) {\n        return;\n      }\n\n      // we divide the work into three loops to avoid mixing DOM reads and writes\n      // in each iteration - which helps prevent layout thrashing.\n      children.forEach(callPendingCbs);\n      children.forEach(recordPosition);\n      children.forEach(applyTranslation);\n\n      // force reflow to put everything in position\n      // assign to this to avoid being removed in tree-shaking\n      // $flow-disable-line\n      this._reflow = document.body.offsetHeight;\n\n      children.forEach(function(c) {\n        if (c.data.moved) {\n          var el = c.elm;\n          var s = el.style;\n          addTransitionClass(el, moveClass);\n          s.transform = s.WebkitTransform = s.transitionDuration = \"\";\n          el.addEventListener(\n            transitionEndEvent,\n            (el._moveCb = function cb(e) {\n              if (!e || /transform$/.test(e.propertyName)) {\n                el.removeEventListener(transitionEndEvent, cb);\n                el._moveCb = null;\n                removeTransitionClass(el, moveClass);\n              }\n            })\n          );\n        }\n      });\n    },\n\n    methods: {\n      hasMove: function hasMove(el, moveClass) {\n        /* istanbul ignore if */\n        if (!hasTransition) {\n          return false;\n        }\n        /* istanbul ignore if */\n        if (this._hasMove) {\n          return this._hasMove;\n        }\n        // Detect whether an element with the move class applied has\n        // CSS transitions. Since the element may be inside an entering\n        // transition at this very moment, we make a clone of it and remove\n        // all other transition classes applied to ensure only the move class\n        // is applied.\n        var clone = el.cloneNode();\n        if (el._transitionClasses) {\n          el._transitionClasses.forEach(function(cls) {\n            removeClass(clone, cls);\n          });\n        }\n        addClass(clone, moveClass);\n        clone.style.display = \"none\";\n        this.$el.appendChild(clone);\n        var info = getTransitionInfo(clone);\n        this.$el.removeChild(clone);\n        return (this._hasMove = info.hasTransform);\n      }\n    }\n  };\n\n  function callPendingCbs(c) {\n    /* istanbul ignore if */\n    if (c.elm._moveCb) {\n      c.elm._moveCb();\n    }\n    /* istanbul ignore if */\n    if (c.elm._enterCb) {\n      c.elm._enterCb();\n    }\n  }\n\n  function recordPosition(c) {\n    c.data.newPos = c.elm.getBoundingClientRect();\n  }\n\n  function applyTranslation(c) {\n    var oldPos = c.data.pos;\n    var newPos = c.data.newPos;\n    var dx = oldPos.left - newPos.left;\n    var dy = oldPos.top - newPos.top;\n    if (dx || dy) {\n      c.data.moved = true;\n      var s = c.elm.style;\n      s.transform = s.WebkitTransform = \"translate(\" + dx + \"px,\" + dy + \"px)\";\n      s.transitionDuration = \"0s\";\n    }\n  }\n\n  var platformComponents = {\n    Transition: Transition,\n    TransitionGroup: TransitionGroup\n  };\n\n  /*  */\n\n  // install platform specific utils\n  Vue.config.mustUseProp = mustUseProp;\n  Vue.config.isReservedTag = isReservedTag;\n  Vue.config.isReservedAttr = isReservedAttr;\n  Vue.config.getTagNamespace = getTagNamespace;\n  Vue.config.isUnknownElement = isUnknownElement;\n\n  // install platform runtime directives & components\n  extend(Vue.options.directives, platformDirectives);\n  extend(Vue.options.components, platformComponents);\n\n  // install platform patch function\n  Vue.prototype.__patch__ = inBrowser ? patch : noop;\n\n  // public mount method\n  Vue.prototype.$mount = function(el, hydrating) {\n    el = el && inBrowser ? query(el) : undefined;\n    return mountComponent(this, el, hydrating);\n  };\n\n  // devtools global hook\n  /* istanbul ignore next */\n  if (inBrowser) {\n    setTimeout(function() {\n      if (config.devtools) {\n        if (devtools) {\n          devtools.emit(\"init\", Vue);\n        } else if (\"development\" !== \"production\" && \"development\" !== \"test\" && isChrome) {\n          console[console.info ? \"info\" : \"log\"](\n            \"Download the Vue Devtools extension for a better development experience:\\n\" +\n              \"https://github.com/vuejs/vue-devtools\"\n          );\n        }\n      }\n      if (\n        \"development\" !== \"production\" &&\n        \"development\" !== \"test\" &&\n        config.productionTip !== false &&\n        typeof console !== \"undefined\"\n      ) {\n        console[console.info ? \"info\" : \"log\"](\n          \"You are running Vue in development mode.\\n\" +\n            \"Make sure to turn on production mode when deploying for production.\\n\" +\n            \"See more tips at https://vuejs.org/guide/deployment.html\"\n        );\n      }\n    }, 0);\n  }\n\n  /*  */\n\n  var defaultTagRE = /\\{\\{((?:.|\\n)+?)\\}\\}/g;\n  var regexEscapeRE = /[-.*+?^${}()|[\\]\\/\\\\]/g;\n\n  var buildRegex = cached(function(delimiters) {\n    var open = delimiters[0].replace(regexEscapeRE, \"\\\\$&\");\n    var close = delimiters[1].replace(regexEscapeRE, \"\\\\$&\");\n    return new RegExp(open + \"((?:.|\\\\n)+?)\" + close, \"g\");\n  });\n\n  function parseText(text, delimiters) {\n    var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;\n    if (!tagRE.test(text)) {\n      return;\n    }\n    var tokens = [];\n    var rawTokens = [];\n    var lastIndex = (tagRE.lastIndex = 0);\n    var match, index, tokenValue;\n    while ((match = tagRE.exec(text))) {\n      index = match.index;\n      // push text token\n      if (index > lastIndex) {\n        rawTokens.push((tokenValue = text.slice(lastIndex, index)));\n        tokens.push(JSON.stringify(tokenValue));\n      }\n      // tag token\n      var exp = parseFilters(match[1].trim());\n      tokens.push(\"_s(\" + exp + \")\");\n      rawTokens.push({ \"@binding\": exp });\n      lastIndex = index + match[0].length;\n    }\n    if (lastIndex < text.length) {\n      rawTokens.push((tokenValue = text.slice(lastIndex)));\n      tokens.push(JSON.stringify(tokenValue));\n    }\n    return {\n      expression: tokens.join(\"+\"),\n      tokens: rawTokens\n    };\n  }\n\n  /*  */\n\n  function transformNode(el, options) {\n    var warn = options.warn || baseWarn;\n    var staticClass = getAndRemoveAttr(el, \"class\");\n    if (\"development\" !== \"production\" && staticClass) {\n      var res = parseText(staticClass, options.delimiters);\n      if (res) {\n        warn(\n          'class=\"' +\n            staticClass +\n            '\": ' +\n            \"Interpolation inside attributes has been removed. \" +\n            \"Use v-bind or the colon shorthand instead. For example, \" +\n            'instead of <div class=\"{{ val }}\">, use <div :class=\"val\">.'\n        );\n      }\n    }\n    if (staticClass) {\n      el.staticClass = JSON.stringify(staticClass);\n    }\n    var classBinding = getBindingAttr(el, \"class\", false /* getStatic */);\n    if (classBinding) {\n      el.classBinding = classBinding;\n    }\n  }\n\n  function genData(el) {\n    var data = \"\";\n    if (el.staticClass) {\n      data += \"staticClass:\" + el.staticClass + \",\";\n    }\n    if (el.classBinding) {\n      data += \"class:\" + el.classBinding + \",\";\n    }\n    return data;\n  }\n\n  var klass$1 = {\n    staticKeys: [\"staticClass\"],\n    transformNode: transformNode,\n    genData: genData\n  };\n\n  /*  */\n\n  function transformNode$1(el, options) {\n    var warn = options.warn || baseWarn;\n    var staticStyle = getAndRemoveAttr(el, \"style\");\n    if (staticStyle) {\n      /* istanbul ignore if */\n      {\n        var res = parseText(staticStyle, options.delimiters);\n        if (res) {\n          warn(\n            'style=\"' +\n              staticStyle +\n              '\": ' +\n              \"Interpolation inside attributes has been removed. \" +\n              \"Use v-bind or the colon shorthand instead. For example, \" +\n              'instead of <div style=\"{{ val }}\">, use <div :style=\"val\">.'\n          );\n        }\n      }\n      el.staticStyle = JSON.stringify(parseStyleText(staticStyle));\n    }\n\n    var styleBinding = getBindingAttr(el, \"style\", false /* getStatic */);\n    if (styleBinding) {\n      el.styleBinding = styleBinding;\n    }\n  }\n\n  function genData$1(el) {\n    var data = \"\";\n    if (el.staticStyle) {\n      data += \"staticStyle:\" + el.staticStyle + \",\";\n    }\n    if (el.styleBinding) {\n      data += \"style:(\" + el.styleBinding + \"),\";\n    }\n    return data;\n  }\n\n  var style$1 = {\n    staticKeys: [\"staticStyle\"],\n    transformNode: transformNode$1,\n    genData: genData$1\n  };\n\n  /*  */\n\n  var decoder;\n\n  var he = {\n    decode: function decode(html) {\n      decoder = decoder || document.createElement(\"div\");\n      decoder.innerHTML = html;\n      return decoder.textContent;\n    }\n  };\n\n  /*  */\n\n  var isUnaryTag = makeMap(\n    \"area,base,br,col,embed,frame,hr,img,input,isindex,keygen,\" + \"link,meta,param,source,track,wbr\"\n  );\n\n  // Elements that you can, intentionally, leave open\n  // (and which close themselves)\n  var canBeLeftOpenTag = makeMap(\"colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source\");\n\n  // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3\n  // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content\n  var isNonPhrasingTag = makeMap(\n    \"address,article,aside,base,blockquote,body,caption,col,colgroup,dd,\" +\n      \"details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,\" +\n      \"h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,\" +\n      \"optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,\" +\n      \"title,tr,track\"\n  );\n\n  /**\n   * Not type-checking this file because it's mostly vendor code.\n   */\n\n  /*!\n * HTML Parser By John Resig (ejohn.org)\n * Modified by Juriy \"kangax\" Zaytsev\n * Original code by Erik Arvidsson, Mozilla Public License\n * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js\n */\n\n  // Regular Expressions for parsing tags and attributes\n  var attribute = /^\\s*([^\\s\"'<>\\/=]+)(?:\\s*(=)\\s*(?:\"([^\"]*)\"+|'([^']*)'+|([^\\s\"'=<>`]+)))?/;\n  // could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName\n  // but for Vue templates we can enforce a simple charset\n  var ncname = \"[a-zA-Z_][\\\\w\\\\-\\\\.]*\";\n  var qnameCapture = \"((?:\" + ncname + \"\\\\:)?\" + ncname + \")\";\n  var startTagOpen = new RegExp(\"^<\" + qnameCapture);\n  var startTagClose = /^\\s*(\\/?)>/;\n  var endTag = new RegExp(\"^<\\\\/\" + qnameCapture + \"[^>]*>\");\n  var doctype = /^<!DOCTYPE [^>]+>/i;\n  // #7298: escape - to avoid being pased as HTML comment when inlined in page\n  var comment = /^<!\\--/;\n  var conditionalComment = /^<!\\[/;\n\n  var IS_REGEX_CAPTURING_BROKEN = false;\n  \"x\".replace(/x(.)?/g, function(m, g) {\n    IS_REGEX_CAPTURING_BROKEN = g === \"\";\n  });\n\n  // Special Elements (can contain anything)\n  var isPlainTextElement = makeMap(\"script,style,textarea\", true);\n  var reCache = {};\n\n  var decodingMap = {\n    \"&lt;\": \"<\",\n    \"&gt;\": \">\",\n    \"&quot;\": '\"',\n    \"&amp;\": \"&\",\n    \"&#10;\": \"\\n\",\n    \"&#9;\": \"\\t\"\n  };\n  var encodedAttr = /&(?:lt|gt|quot|amp);/g;\n  var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g;\n\n  // #5992\n  var isIgnoreNewlineTag = makeMap(\"pre,textarea\", true);\n  var shouldIgnoreFirstNewline = function(tag, html) {\n    return tag && isIgnoreNewlineTag(tag) && html[0] === \"\\n\";\n  };\n\n  function decodeAttr(value, shouldDecodeNewlines) {\n    var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;\n    return value.replace(re, function(match) {\n      return decodingMap[match];\n    });\n  }\n\n  function parseHTML(html, options) {\n    var stack = [];\n    var expectHTML = options.expectHTML;\n    var isUnaryTag$$1 = options.isUnaryTag || no;\n    var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;\n    var index = 0;\n    var last, lastTag;\n    while (html) {\n      last = html;\n      // Make sure we're not in a plaintext content element like script/style\n      if (!lastTag || !isPlainTextElement(lastTag)) {\n        var textEnd = html.indexOf(\"<\");\n        if (textEnd === 0) {\n          // Comment:\n          if (comment.test(html)) {\n            var commentEnd = html.indexOf(\"-->\");\n\n            if (commentEnd >= 0) {\n              if (options.shouldKeepComment) {\n                options.comment(html.substring(4, commentEnd));\n              }\n              advance(commentEnd + 3);\n              continue;\n            }\n          }\n\n          // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment\n          if (conditionalComment.test(html)) {\n            var conditionalEnd = html.indexOf(\"]>\");\n\n            if (conditionalEnd >= 0) {\n              advance(conditionalEnd + 2);\n              continue;\n            }\n          }\n\n          // Doctype:\n          var doctypeMatch = html.match(doctype);\n          if (doctypeMatch) {\n            advance(doctypeMatch[0].length);\n            continue;\n          }\n\n          // End tag:\n          var endTagMatch = html.match(endTag);\n          if (endTagMatch) {\n            var curIndex = index;\n            advance(endTagMatch[0].length);\n            parseEndTag(endTagMatch[1], curIndex, index);\n            continue;\n          }\n\n          // Start tag:\n          var startTagMatch = parseStartTag();\n          if (startTagMatch) {\n            handleStartTag(startTagMatch);\n            if (shouldIgnoreFirstNewline(lastTag, html)) {\n              advance(1);\n            }\n            continue;\n          }\n        }\n\n        var text = void 0,\n          rest = void 0,\n          next = void 0;\n        if (textEnd >= 0) {\n          rest = html.slice(textEnd);\n          while (\n            !endTag.test(rest) &&\n            !startTagOpen.test(rest) &&\n            !comment.test(rest) &&\n            !conditionalComment.test(rest)\n          ) {\n            // < in plain text, be forgiving and treat it as text\n            next = rest.indexOf(\"<\", 1);\n            if (next < 0) {\n              break;\n            }\n            textEnd += next;\n            rest = html.slice(textEnd);\n          }\n          text = html.substring(0, textEnd);\n          advance(textEnd);\n        }\n\n        if (textEnd < 0) {\n          text = html;\n          html = \"\";\n        }\n\n        if (options.chars && text) {\n          options.chars(text);\n        }\n      } else {\n        var endTagLength = 0;\n        var stackedTag = lastTag.toLowerCase();\n        var reStackedTag =\n          reCache[stackedTag] || (reCache[stackedTag] = new RegExp(\"([\\\\s\\\\S]*?)(</\" + stackedTag + \"[^>]*>)\", \"i\"));\n        var rest$1 = html.replace(reStackedTag, function(all, text, endTag) {\n          endTagLength = endTag.length;\n          if (!isPlainTextElement(stackedTag) && stackedTag !== \"noscript\") {\n            text = text\n              .replace(/<!\\--([\\s\\S]*?)-->/g, \"$1\") // #7298\n              .replace(/<!\\[CDATA\\[([\\s\\S]*?)]]>/g, \"$1\");\n          }\n          if (shouldIgnoreFirstNewline(stackedTag, text)) {\n            text = text.slice(1);\n          }\n          if (options.chars) {\n            options.chars(text);\n          }\n          return \"\";\n        });\n        index += html.length - rest$1.length;\n        html = rest$1;\n        parseEndTag(stackedTag, index - endTagLength, index);\n      }\n\n      if (html === last) {\n        options.chars && options.chars(html);\n        if (\"development\" !== \"production\" && !stack.length && options.warn) {\n          options.warn('Mal-formatted tag at end of template: \"' + html + '\"');\n        }\n        break;\n      }\n    }\n\n    // Clean up any remaining tags\n    parseEndTag();\n\n    function advance(n) {\n      index += n;\n      html = html.substring(n);\n    }\n\n    function parseStartTag() {\n      var start = html.match(startTagOpen);\n      if (start) {\n        var match = {\n          tagName: start[1],\n          attrs: [],\n          start: index\n        };\n        advance(start[0].length);\n        var end, attr;\n        while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {\n          advance(attr[0].length);\n          match.attrs.push(attr);\n        }\n        if (end) {\n          match.unarySlash = end[1];\n          advance(end[0].length);\n          match.end = index;\n          return match;\n        }\n      }\n    }\n\n    function handleStartTag(match) {\n      var tagName = match.tagName;\n      var unarySlash = match.unarySlash;\n\n      if (expectHTML) {\n        if (lastTag === \"p\" && isNonPhrasingTag(tagName)) {\n          parseEndTag(lastTag);\n        }\n        if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {\n          parseEndTag(tagName);\n        }\n      }\n\n      var unary = isUnaryTag$$1(tagName) || !!unarySlash;\n\n      var l = match.attrs.length;\n      var attrs = new Array(l);\n      for (var i = 0; i < l; i++) {\n        var args = match.attrs[i];\n        // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778\n        if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('\"\"') === -1) {\n          if (args[3] === \"\") {\n            delete args[3];\n          }\n          if (args[4] === \"\") {\n            delete args[4];\n          }\n          if (args[5] === \"\") {\n            delete args[5];\n          }\n        }\n        var value = args[3] || args[4] || args[5] || \"\";\n        var shouldDecodeNewlines =\n          tagName === \"a\" && args[1] === \"href\" ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines;\n        attrs[i] = {\n          name: args[1],\n          value: decodeAttr(value, shouldDecodeNewlines)\n        };\n      }\n\n      if (!unary) {\n        stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs });\n        lastTag = tagName;\n      }\n\n      if (options.start) {\n        options.start(tagName, attrs, unary, match.start, match.end);\n      }\n    }\n\n    function parseEndTag(tagName, start, end) {\n      var pos, lowerCasedTagName;\n      if (start == null) {\n        start = index;\n      }\n      if (end == null) {\n        end = index;\n      }\n\n      if (tagName) {\n        lowerCasedTagName = tagName.toLowerCase();\n      }\n\n      // Find the closest opened tag of the same type\n      if (tagName) {\n        for (pos = stack.length - 1; pos >= 0; pos--) {\n          if (stack[pos].lowerCasedTag === lowerCasedTagName) {\n            break;\n          }\n        }\n      } else {\n        // If no tag name is provided, clean shop\n        pos = 0;\n      }\n\n      if (pos >= 0) {\n        // Close all the open elements, up the stack\n        for (var i = stack.length - 1; i >= pos; i--) {\n          if (\"development\" !== \"production\" && (i > pos || !tagName) && options.warn) {\n            options.warn(\"tag <\" + stack[i].tag + \"> has no matching end tag.\");\n          }\n          if (options.end) {\n            options.end(stack[i].tag, start, end);\n          }\n        }\n\n        // Remove the open elements from the stack\n        stack.length = pos;\n        lastTag = pos && stack[pos - 1].tag;\n      } else if (lowerCasedTagName === \"br\") {\n        if (options.start) {\n          options.start(tagName, [], true, start, end);\n        }\n      } else if (lowerCasedTagName === \"p\") {\n        if (options.start) {\n          options.start(tagName, [], false, start, end);\n        }\n        if (options.end) {\n          options.end(tagName, start, end);\n        }\n      }\n    }\n  }\n\n  /*  */\n\n  var onRE = /^@|^v-on:/;\n  var dirRE = /^v-|^@|^:/;\n  var forAliasRE = /([^]*?)\\s+(?:in|of)\\s+([^]*)/;\n  var forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\n  var stripParensRE = /^\\(|\\)$/g;\n\n  var argRE = /:(.*)$/;\n  var bindRE = /^:|^v-bind:/;\n  var modifierRE = /\\.[^.]+/g;\n\n  var decodeHTMLCached = cached(he.decode);\n\n  // configurable state\n  var warn$2;\n  var delimiters;\n  var transforms;\n  var preTransforms;\n  var postTransforms;\n  var platformIsPreTag;\n  var platformMustUseProp;\n  var platformGetTagNamespace;\n\n  function createASTElement(tag, attrs, parent) {\n    return {\n      type: 1,\n      tag: tag,\n      attrsList: attrs,\n      attrsMap: makeAttrsMap(attrs),\n      parent: parent,\n      children: []\n    };\n  }\n\n  /**\n   * Convert HTML string to AST.\n   */\n  function parse(template, options) {\n    warn$2 = options.warn || baseWarn;\n\n    platformIsPreTag = options.isPreTag || no;\n    platformMustUseProp = options.mustUseProp || no;\n    platformGetTagNamespace = options.getTagNamespace || no;\n\n    transforms = pluckModuleFunction(options.modules, \"transformNode\");\n    preTransforms = pluckModuleFunction(options.modules, \"preTransformNode\");\n    postTransforms = pluckModuleFunction(options.modules, \"postTransformNode\");\n\n    delimiters = options.delimiters;\n\n    var stack = [];\n    var preserveWhitespace = options.preserveWhitespace !== false;\n    var root;\n    var currentParent;\n    var inVPre = false;\n    var inPre = false;\n    var warned = false;\n\n    function warnOnce(msg) {\n      if (!warned) {\n        warned = true;\n        warn$2(msg);\n      }\n    }\n\n    function closeElement(element) {\n      // check pre state\n      if (element.pre) {\n        inVPre = false;\n      }\n      if (platformIsPreTag(element.tag)) {\n        inPre = false;\n      }\n      // apply post-transforms\n      for (var i = 0; i < postTransforms.length; i++) {\n        postTransforms[i](element, options);\n      }\n    }\n\n    parseHTML(template, {\n      warn: warn$2,\n      expectHTML: options.expectHTML,\n      isUnaryTag: options.isUnaryTag,\n      canBeLeftOpenTag: options.canBeLeftOpenTag,\n      shouldDecodeNewlines: options.shouldDecodeNewlines,\n      shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,\n      shouldKeepComment: options.comments,\n      start: function start(tag, attrs, unary) {\n        // check namespace.\n        // inherit parent ns if there is one\n        var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);\n\n        // handle IE svg bug\n        /* istanbul ignore if */\n        if (isIE && ns === \"svg\") {\n          attrs = guardIESVGBug(attrs);\n        }\n\n        var element = createASTElement(tag, attrs, currentParent);\n        if (ns) {\n          element.ns = ns;\n        }\n\n        if (isForbiddenTag(element) && !isServerRendering()) {\n          element.forbidden = true;\n          \"development\" !== \"production\" &&\n            warn$2(\n              \"Templates should only be responsible for mapping the state to the \" +\n                \"UI. Avoid placing tags with side-effects in your templates, such as \" +\n                \"<\" +\n                tag +\n                \">\" +\n                \", as they will not be parsed.\"\n            );\n        }\n\n        // apply pre-transforms\n        for (var i = 0; i < preTransforms.length; i++) {\n          element = preTransforms[i](element, options) || element;\n        }\n\n        if (!inVPre) {\n          processPre(element);\n          if (element.pre) {\n            inVPre = true;\n          }\n        }\n        if (platformIsPreTag(element.tag)) {\n          inPre = true;\n        }\n        if (inVPre) {\n          processRawAttrs(element);\n        } else if (!element.processed) {\n          // structural directives\n          processFor(element);\n          processIf(element);\n          processOnce(element);\n          // element-scope stuff\n          processElement(element, options);\n        }\n\n        function checkRootConstraints(el) {\n          {\n            if (el.tag === \"slot\" || el.tag === \"template\") {\n              warnOnce(\n                \"Cannot use <\" + el.tag + \"> as component root element because it may \" + \"contain multiple nodes.\"\n              );\n            }\n            if (el.attrsMap.hasOwnProperty(\"v-for\")) {\n              warnOnce(\n                \"Cannot use v-for on stateful component root element because \" + \"it renders multiple elements.\"\n              );\n            }\n          }\n        }\n\n        // tree management\n        if (!root) {\n          root = element;\n          checkRootConstraints(root);\n        } else if (!stack.length) {\n          // allow root elements with v-if, v-else-if and v-else\n          if (root.if && (element.elseif || element.else)) {\n            checkRootConstraints(element);\n            addIfCondition(root, {\n              exp: element.elseif,\n              block: element\n            });\n          } else {\n            warnOnce(\n              \"Component template should contain exactly one root element. \" +\n                \"If you are using v-if on multiple elements, \" +\n                \"use v-else-if to chain them instead.\"\n            );\n          }\n        }\n        if (currentParent && !element.forbidden) {\n          if (element.elseif || element.else) {\n            processIfConditions(element, currentParent);\n          } else if (element.slotScope) {\n            // scoped slot\n            currentParent.plain = false;\n            var name = element.slotTarget || '\"default\"';\n            (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;\n          } else {\n            currentParent.children.push(element);\n            element.parent = currentParent;\n          }\n        }\n        if (!unary) {\n          currentParent = element;\n          stack.push(element);\n        } else {\n          closeElement(element);\n        }\n      },\n\n      end: function end() {\n        // remove trailing whitespace\n        var element = stack[stack.length - 1];\n        var lastNode = element.children[element.children.length - 1];\n        if (lastNode && lastNode.type === 3 && lastNode.text === \" \" && !inPre) {\n          element.children.pop();\n        }\n        // pop stack\n        stack.length -= 1;\n        currentParent = stack[stack.length - 1];\n        closeElement(element);\n      },\n\n      chars: function chars(text) {\n        if (!currentParent) {\n          {\n            if (text === template) {\n              warnOnce(\"Component template requires a root element, rather than just text.\");\n            } else if ((text = text.trim())) {\n              warnOnce('text \"' + text + '\" outside root element will be ignored.');\n            }\n          }\n          return;\n        }\n        // IE textarea placeholder bug\n        /* istanbul ignore if */\n        if (isIE && currentParent.tag === \"textarea\" && currentParent.attrsMap.placeholder === text) {\n          return;\n        }\n        var children = currentParent.children;\n        text =\n          inPre || text.trim()\n            ? isTextTag(currentParent)\n              ? text\n              : decodeHTMLCached(text)\n            : // only preserve whitespace if its not right after a starting tag\n              preserveWhitespace && children.length\n              ? \" \"\n              : \"\";\n        if (text) {\n          var res;\n          if (!inVPre && text !== \" \" && (res = parseText(text, delimiters))) {\n            children.push({\n              type: 2,\n              expression: res.expression,\n              tokens: res.tokens,\n              text: text\n            });\n          } else if (text !== \" \" || !children.length || children[children.length - 1].text !== \" \") {\n            children.push({\n              type: 3,\n              text: text\n            });\n          }\n        }\n      },\n      comment: function comment(text) {\n        currentParent.children.push({\n          type: 3,\n          text: text,\n          isComment: true\n        });\n      }\n    });\n    return root;\n  }\n\n  function processPre(el) {\n    if (getAndRemoveAttr(el, \"v-pre\") != null) {\n      el.pre = true;\n    }\n  }\n\n  function processRawAttrs(el) {\n    var l = el.attrsList.length;\n    if (l) {\n      var attrs = (el.attrs = new Array(l));\n      for (var i = 0; i < l; i++) {\n        attrs[i] = {\n          name: el.attrsList[i].name,\n          value: JSON.stringify(el.attrsList[i].value)\n        };\n      }\n    } else if (!el.pre) {\n      // non root node in pre blocks with no attributes\n      el.plain = true;\n    }\n  }\n\n  function processElement(element, options) {\n    processKey(element);\n\n    // determine whether this is a plain element after\n    // removing structural attributes\n    element.plain = !element.key && !element.attrsList.length;\n\n    processRef(element);\n    processSlot(element);\n    processComponent(element);\n    for (var i = 0; i < transforms.length; i++) {\n      element = transforms[i](element, options) || element;\n    }\n    processAttrs(element);\n  }\n\n  function processKey(el) {\n    var exp = getBindingAttr(el, \"key\");\n    if (exp) {\n      if (\"development\" !== \"production\" && el.tag === \"template\") {\n        warn$2(\"<template> cannot be keyed. Place the key on real elements instead.\");\n      }\n      el.key = exp;\n    }\n  }\n\n  function processRef(el) {\n    var ref = getBindingAttr(el, \"ref\");\n    if (ref) {\n      el.ref = ref;\n      el.refInFor = checkInFor(el);\n    }\n  }\n\n  function processFor(el) {\n    var exp;\n    if ((exp = getAndRemoveAttr(el, \"v-for\"))) {\n      var res = parseFor(exp);\n      if (res) {\n        extend(el, res);\n      } else {\n        warn$2(\"Invalid v-for expression: \" + exp);\n      }\n    }\n  }\n\n  function parseFor(exp) {\n    var inMatch = exp.match(forAliasRE);\n    if (!inMatch) {\n      return;\n    }\n    var res = {};\n    res.for = inMatch[2].trim();\n    var alias = inMatch[1].trim().replace(stripParensRE, \"\");\n    var iteratorMatch = alias.match(forIteratorRE);\n    if (iteratorMatch) {\n      res.alias = alias.replace(forIteratorRE, \"\");\n      res.iterator1 = iteratorMatch[1].trim();\n      if (iteratorMatch[2]) {\n        res.iterator2 = iteratorMatch[2].trim();\n      }\n    } else {\n      res.alias = alias;\n    }\n    return res;\n  }\n\n  function processIf(el) {\n    var exp = getAndRemoveAttr(el, \"v-if\");\n    if (exp) {\n      el.if = exp;\n      addIfCondition(el, {\n        exp: exp,\n        block: el\n      });\n    } else {\n      if (getAndRemoveAttr(el, \"v-else\") != null) {\n        el.else = true;\n      }\n      var elseif = getAndRemoveAttr(el, \"v-else-if\");\n      if (elseif) {\n        el.elseif = elseif;\n      }\n    }\n  }\n\n  function processIfConditions(el, parent) {\n    var prev = findPrevElement(parent.children);\n    if (prev && prev.if) {\n      addIfCondition(prev, {\n        exp: el.elseif,\n        block: el\n      });\n    } else {\n      warn$2(\n        \"v-\" +\n          (el.elseif ? 'else-if=\"' + el.elseif + '\"' : \"else\") +\n          \" \" +\n          \"used on element <\" +\n          el.tag +\n          \"> without corresponding v-if.\"\n      );\n    }\n  }\n\n  function findPrevElement(children) {\n    var i = children.length;\n    while (i--) {\n      if (children[i].type === 1) {\n        return children[i];\n      } else {\n        if (\"development\" !== \"production\" && children[i].text !== \" \") {\n          warn$2('text \"' + children[i].text.trim() + '\" between v-if and v-else(-if) ' + \"will be ignored.\");\n        }\n        children.pop();\n      }\n    }\n  }\n\n  function addIfCondition(el, condition) {\n    if (!el.ifConditions) {\n      el.ifConditions = [];\n    }\n    el.ifConditions.push(condition);\n  }\n\n  function processOnce(el) {\n    var once$$1 = getAndRemoveAttr(el, \"v-once\");\n    if (once$$1 != null) {\n      el.once = true;\n    }\n  }\n\n  function processSlot(el) {\n    if (el.tag === \"slot\") {\n      el.slotName = getBindingAttr(el, \"name\");\n      if (\"development\" !== \"production\" && el.key) {\n        warn$2(\n          \"`key` does not work on <slot> because slots are abstract outlets \" +\n            \"and can possibly expand into multiple elements. \" +\n            \"Use the key on a wrapping element instead.\"\n        );\n      }\n    } else {\n      var slotScope;\n      if (el.tag === \"template\") {\n        slotScope = getAndRemoveAttr(el, \"scope\");\n        /* istanbul ignore if */\n        if (\"development\" !== \"production\" && slotScope) {\n          warn$2(\n            'the \"scope\" attribute for scoped slots have been deprecated and ' +\n              'replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute ' +\n              \"can also be used on plain elements in addition to <template> to \" +\n              \"denote scoped slots.\",\n            true\n          );\n        }\n        el.slotScope = slotScope || getAndRemoveAttr(el, \"slot-scope\");\n      } else if ((slotScope = getAndRemoveAttr(el, \"slot-scope\"))) {\n        /* istanbul ignore if */\n        if (\"development\" !== \"production\" && el.attrsMap[\"v-for\"]) {\n          warn$2(\n            \"Ambiguous combined usage of slot-scope and v-for on <\" +\n              el.tag +\n              \"> \" +\n              \"(v-for takes higher priority). Use a wrapper <template> for the \" +\n              \"scoped slot to make it clearer.\",\n            true\n          );\n        }\n        el.slotScope = slotScope;\n      }\n      var slotTarget = getBindingAttr(el, \"slot\");\n      if (slotTarget) {\n        el.slotTarget = slotTarget === '\"\"' ? '\"default\"' : slotTarget;\n        // preserve slot as an attribute for native shadow DOM compat\n        // only for non-scoped slots.\n        if (el.tag !== \"template\" && !el.slotScope) {\n          addAttr(el, \"slot\", slotTarget);\n        }\n      }\n    }\n  }\n\n  function processComponent(el) {\n    var binding;\n    if ((binding = getBindingAttr(el, \"is\"))) {\n      el.component = binding;\n    }\n    if (getAndRemoveAttr(el, \"inline-template\") != null) {\n      el.inlineTemplate = true;\n    }\n  }\n\n  function processAttrs(el) {\n    var list = el.attrsList;\n    var i, l, name, rawName, value, modifiers, isProp;\n    for (i = 0, l = list.length; i < l; i++) {\n      name = rawName = list[i].name;\n      value = list[i].value;\n      if (dirRE.test(name)) {\n        // mark element as dynamic\n        el.hasBindings = true;\n        // modifiers\n        modifiers = parseModifiers(name);\n        if (modifiers) {\n          name = name.replace(modifierRE, \"\");\n        }\n        if (bindRE.test(name)) {\n          // v-bind\n          name = name.replace(bindRE, \"\");\n          value = parseFilters(value);\n          isProp = false;\n          if (modifiers) {\n            if (modifiers.prop) {\n              isProp = true;\n              name = camelize(name);\n              if (name === \"innerHtml\") {\n                name = \"innerHTML\";\n              }\n            }\n            if (modifiers.camel) {\n              name = camelize(name);\n            }\n            if (modifiers.sync) {\n              addHandler(el, \"update:\" + camelize(name), genAssignmentCode(value, \"$event\"));\n            }\n          }\n          if (isProp || (!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name))) {\n            addProp(el, name, value);\n          } else {\n            addAttr(el, name, value);\n          }\n        } else if (onRE.test(name)) {\n          // v-on\n          name = name.replace(onRE, \"\");\n          addHandler(el, name, value, modifiers, false, warn$2);\n        } else {\n          // normal directives\n          name = name.replace(dirRE, \"\");\n          // parse arg\n          var argMatch = name.match(argRE);\n          var arg = argMatch && argMatch[1];\n          if (arg) {\n            name = name.slice(0, -(arg.length + 1));\n          }\n          addDirective(el, name, rawName, value, arg, modifiers);\n          if (\"development\" !== \"production\" && name === \"model\") {\n            checkForAliasModel(el, value);\n          }\n        }\n      } else {\n        // literal attribute\n        {\n          var res = parseText(value, delimiters);\n          if (res) {\n            warn$2(\n              name +\n                '=\"' +\n                value +\n                '\": ' +\n                \"Interpolation inside attributes has been removed. \" +\n                \"Use v-bind or the colon shorthand instead. For example, \" +\n                'instead of <div id=\"{{ val }}\">, use <div :id=\"val\">.'\n            );\n          }\n        }\n        addAttr(el, name, JSON.stringify(value));\n        // #6887 firefox doesn't update muted state if set via attribute\n        // even immediately after element creation\n        if (!el.component && name === \"muted\" && platformMustUseProp(el.tag, el.attrsMap.type, name)) {\n          addProp(el, name, \"true\");\n        }\n      }\n    }\n  }\n\n  function checkInFor(el) {\n    var parent = el;\n    while (parent) {\n      if (parent.for !== undefined) {\n        return true;\n      }\n      parent = parent.parent;\n    }\n    return false;\n  }\n\n  function parseModifiers(name) {\n    var match = name.match(modifierRE);\n    if (match) {\n      var ret = {};\n      match.forEach(function(m) {\n        ret[m.slice(1)] = true;\n      });\n      return ret;\n    }\n  }\n\n  function makeAttrsMap(attrs) {\n    var map = {};\n    for (var i = 0, l = attrs.length; i < l; i++) {\n      if (\"development\" !== \"production\" && map[attrs[i].name] && !isIE && !isEdge) {\n        warn$2(\"duplicate attribute: \" + attrs[i].name);\n      }\n      map[attrs[i].name] = attrs[i].value;\n    }\n    return map;\n  }\n\n  // for script (e.g. type=\"x/template\") or style, do not decode content\n  function isTextTag(el) {\n    return el.tag === \"script\" || el.tag === \"style\";\n  }\n\n  function isForbiddenTag(el) {\n    return el.tag === \"style\" || (el.tag === \"script\" && (!el.attrsMap.type || el.attrsMap.type === \"text/javascript\"));\n  }\n\n  var ieNSBug = /^xmlns:NS\\d+/;\n  var ieNSPrefix = /^NS\\d+:/;\n\n  /* istanbul ignore next */\n  function guardIESVGBug(attrs) {\n    var res = [];\n    for (var i = 0; i < attrs.length; i++) {\n      var attr = attrs[i];\n      if (!ieNSBug.test(attr.name)) {\n        attr.name = attr.name.replace(ieNSPrefix, \"\");\n        res.push(attr);\n      }\n    }\n    return res;\n  }\n\n  function checkForAliasModel(el, value) {\n    var _el = el;\n    while (_el) {\n      if (_el.for && _el.alias === value) {\n        warn$2(\n          \"<\" +\n            el.tag +\n            ' v-model=\"' +\n            value +\n            '\">: ' +\n            \"You are binding v-model directly to a v-for iteration alias. \" +\n            \"This will not be able to modify the v-for source array because \" +\n            \"writing to the alias is like modifying a function local variable. \" +\n            \"Consider using an array of objects and use v-model on an object property instead.\"\n        );\n      }\n      _el = _el.parent;\n    }\n  }\n\n  /*  */\n\n  /**\n   * Expand input[v-model] with dyanmic type bindings into v-if-else chains\n   * Turn this:\n   *   <input v-model=\"data[type]\" :type=\"type\">\n   * into this:\n   *   <input v-if=\"type === 'checkbox'\" type=\"checkbox\" v-model=\"data[type]\">\n   *   <input v-else-if=\"type === 'radio'\" type=\"radio\" v-model=\"data[type]\">\n   *   <input v-else :type=\"type\" v-model=\"data[type]\">\n   */\n\n  function preTransformNode(el, options) {\n    if (el.tag === \"input\") {\n      var map = el.attrsMap;\n      if (!map[\"v-model\"]) {\n        return;\n      }\n\n      var typeBinding;\n      if (map[\":type\"] || map[\"v-bind:type\"]) {\n        typeBinding = getBindingAttr(el, \"type\");\n      }\n      if (!map.type && !typeBinding && map[\"v-bind\"]) {\n        typeBinding = \"(\" + map[\"v-bind\"] + \").type\";\n      }\n\n      if (typeBinding) {\n        var ifCondition = getAndRemoveAttr(el, \"v-if\", true);\n        var ifConditionExtra = ifCondition ? \"&&(\" + ifCondition + \")\" : \"\";\n        var hasElse = getAndRemoveAttr(el, \"v-else\", true) != null;\n        var elseIfCondition = getAndRemoveAttr(el, \"v-else-if\", true);\n        // 1. checkbox\n        var branch0 = cloneASTElement(el);\n        // process for on the main node\n        processFor(branch0);\n        addRawAttr(branch0, \"type\", \"checkbox\");\n        processElement(branch0, options);\n        branch0.processed = true; // prevent it from double-processed\n        branch0.if = \"(\" + typeBinding + \")==='checkbox'\" + ifConditionExtra;\n        addIfCondition(branch0, {\n          exp: branch0.if,\n          block: branch0\n        });\n        // 2. add radio else-if condition\n        var branch1 = cloneASTElement(el);\n        getAndRemoveAttr(branch1, \"v-for\", true);\n        addRawAttr(branch1, \"type\", \"radio\");\n        processElement(branch1, options);\n        addIfCondition(branch0, {\n          exp: \"(\" + typeBinding + \")==='radio'\" + ifConditionExtra,\n          block: branch1\n        });\n        // 3. other\n        var branch2 = cloneASTElement(el);\n        getAndRemoveAttr(branch2, \"v-for\", true);\n        addRawAttr(branch2, \":type\", typeBinding);\n        processElement(branch2, options);\n        addIfCondition(branch0, {\n          exp: ifCondition,\n          block: branch2\n        });\n\n        if (hasElse) {\n          branch0.else = true;\n        } else if (elseIfCondition) {\n          branch0.elseif = elseIfCondition;\n        }\n\n        return branch0;\n      }\n    }\n  }\n\n  function cloneASTElement(el) {\n    return createASTElement(el.tag, el.attrsList.slice(), el.parent);\n  }\n\n  var model$2 = {\n    preTransformNode: preTransformNode\n  };\n\n  var modules$1 = [klass$1, style$1, model$2];\n\n  /*  */\n\n  function text(el, dir) {\n    if (dir.value) {\n      addProp(el, \"textContent\", \"_s(\" + dir.value + \")\");\n    }\n  }\n\n  /*  */\n\n  function html(el, dir) {\n    if (dir.value) {\n      addProp(el, \"innerHTML\", \"_s(\" + dir.value + \")\");\n    }\n  }\n\n  var directives$1 = {\n    model: model,\n    text: text,\n    html: html\n  };\n\n  /*  */\n\n  var baseOptions = {\n    expectHTML: true,\n    modules: modules$1,\n    directives: directives$1,\n    isPreTag: isPreTag,\n    isUnaryTag: isUnaryTag,\n    mustUseProp: mustUseProp,\n    canBeLeftOpenTag: canBeLeftOpenTag,\n    isReservedTag: isReservedTag,\n    getTagNamespace: getTagNamespace,\n    staticKeys: genStaticKeys(modules$1)\n  };\n\n  /*  */\n\n  var isStaticKey;\n  var isPlatformReservedTag;\n\n  var genStaticKeysCached = cached(genStaticKeys$1);\n\n  /**\n   * Goal of the optimizer: walk the generated template AST tree\n   * and detect sub-trees that are purely static, i.e. parts of\n   * the DOM that never needs to change.\n   *\n   * Once we detect these sub-trees, we can:\n   *\n   * 1. Hoist them into constants, so that we no longer need to\n   *    create fresh nodes for them on each re-render;\n   * 2. Completely skip them in the patching process.\n   */\n  function optimize(root, options) {\n    if (!root) {\n      return;\n    }\n    isStaticKey = genStaticKeysCached(options.staticKeys || \"\");\n    isPlatformReservedTag = options.isReservedTag || no;\n    // first pass: mark all non-static nodes.\n    markStatic$1(root);\n    // second pass: mark static roots.\n    markStaticRoots(root, false);\n  }\n\n  function genStaticKeys$1(keys) {\n    return makeMap(\"type,tag,attrsList,attrsMap,plain,parent,children,attrs\" + (keys ? \",\" + keys : \"\"));\n  }\n\n  function markStatic$1(node) {\n    node.static = isStatic(node);\n    if (node.type === 1) {\n      // do not make component slot content static. this avoids\n      // 1. components not able to mutate slot nodes\n      // 2. static slot content fails for hot-reloading\n      if (!isPlatformReservedTag(node.tag) && node.tag !== \"slot\" && node.attrsMap[\"inline-template\"] == null) {\n        return;\n      }\n      for (var i = 0, l = node.children.length; i < l; i++) {\n        var child = node.children[i];\n        markStatic$1(child);\n        if (!child.static) {\n          node.static = false;\n        }\n      }\n      if (node.ifConditions) {\n        for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {\n          var block = node.ifConditions[i$1].block;\n          markStatic$1(block);\n          if (!block.static) {\n            node.static = false;\n          }\n        }\n      }\n    }\n  }\n\n  function markStaticRoots(node, isInFor) {\n    if (node.type === 1) {\n      if (node.static || node.once) {\n        node.staticInFor = isInFor;\n      }\n      // For a node to qualify as a static root, it should have children that\n      // are not just static text. Otherwise the cost of hoisting out will\n      // outweigh the benefits and it's better off to just always render it fresh.\n      if (node.static && node.children.length && !(node.children.length === 1 && node.children[0].type === 3)) {\n        node.staticRoot = true;\n        return;\n      } else {\n        node.staticRoot = false;\n      }\n      if (node.children) {\n        for (var i = 0, l = node.children.length; i < l; i++) {\n          markStaticRoots(node.children[i], isInFor || !!node.for);\n        }\n      }\n      if (node.ifConditions) {\n        for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {\n          markStaticRoots(node.ifConditions[i$1].block, isInFor);\n        }\n      }\n    }\n  }\n\n  function isStatic(node) {\n    if (node.type === 2) {\n      // expression\n      return false;\n    }\n    if (node.type === 3) {\n      // text\n      return true;\n    }\n    return !!(\n      node.pre ||\n      (!node.hasBindings && // no dynamic bindings\n      !node.if &&\n      !node.for && // not v-if or v-for or v-else\n      !isBuiltInTag(node.tag) && // not a built-in\n      isPlatformReservedTag(node.tag) && // not a component\n        !isDirectChildOfTemplateFor(node) &&\n        Object.keys(node).every(isStaticKey))\n    );\n  }\n\n  function isDirectChildOfTemplateFor(node) {\n    while (node.parent) {\n      node = node.parent;\n      if (node.tag !== \"template\") {\n        return false;\n      }\n      if (node.for) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /*  */\n\n  var fnExpRE = /^([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/;\n  var simplePathRE = /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*|\\['[^']*?']|\\[\"[^\"]*?\"]|\\[\\d+]|\\[[A-Za-z_$][\\w$]*])*$/;\n\n  // KeyboardEvent.keyCode aliases\n  var keyCodes = {\n    esc: 27,\n    tab: 9,\n    enter: 13,\n    space: 32,\n    up: 38,\n    left: 37,\n    right: 39,\n    down: 40,\n    delete: [8, 46]\n  };\n\n  // KeyboardEvent.key aliases\n  var keyNames = {\n    esc: \"Escape\",\n    tab: \"Tab\",\n    enter: \"Enter\",\n    space: \" \",\n    // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.\n    up: [\"Up\", \"ArrowUp\"],\n    left: [\"Left\", \"ArrowLeft\"],\n    right: [\"Right\", \"ArrowRight\"],\n    down: [\"Down\", \"ArrowDown\"],\n    delete: [\"Backspace\", \"Delete\"]\n  };\n\n  // #4868: modifiers that prevent the execution of the listener\n  // need to explicitly return null so that we can determine whether to remove\n  // the listener for .once\n  var genGuard = function(condition) {\n    return \"if(\" + condition + \")return null;\";\n  };\n\n  var modifierCode = {\n    stop: \"$event.stopPropagation();\",\n    prevent: \"$event.preventDefault();\",\n    self: genGuard(\"$event.target !== $event.currentTarget\"),\n    ctrl: genGuard(\"!$event.ctrlKey\"),\n    shift: genGuard(\"!$event.shiftKey\"),\n    alt: genGuard(\"!$event.altKey\"),\n    meta: genGuard(\"!$event.metaKey\"),\n    left: genGuard(\"'button' in $event && $event.button !== 0\"),\n    middle: genGuard(\"'button' in $event && $event.button !== 1\"),\n    right: genGuard(\"'button' in $event && $event.button !== 2\")\n  };\n\n  function genHandlers(events, isNative, warn) {\n    var res = isNative ? \"nativeOn:{\" : \"on:{\";\n    for (var name in events) {\n      res += '\"' + name + '\":' + genHandler(name, events[name]) + \",\";\n    }\n    return res.slice(0, -1) + \"}\";\n  }\n\n  function genHandler(name, handler) {\n    if (!handler) {\n      return \"function(){}\";\n    }\n\n    if (Array.isArray(handler)) {\n      return (\n        \"[\" +\n        handler\n          .map(function(handler) {\n            return genHandler(name, handler);\n          })\n          .join(\",\") +\n        \"]\"\n      );\n    }\n\n    var isMethodPath = simplePathRE.test(handler.value);\n    var isFunctionExpression = fnExpRE.test(handler.value);\n\n    if (!handler.modifiers) {\n      if (isMethodPath || isFunctionExpression) {\n        return handler.value;\n      }\n      /* istanbul ignore if */\n      return \"function($event){\" + handler.value + \"}\"; // inline statement\n    } else {\n      var code = \"\";\n      var genModifierCode = \"\";\n      var keys = [];\n      for (var key in handler.modifiers) {\n        if (modifierCode[key]) {\n          genModifierCode += modifierCode[key];\n          // left/right\n          if (keyCodes[key]) {\n            keys.push(key);\n          }\n        } else if (key === \"exact\") {\n          var modifiers = handler.modifiers;\n          genModifierCode += genGuard(\n            [\"ctrl\", \"shift\", \"alt\", \"meta\"]\n              .filter(function(keyModifier) {\n                return !modifiers[keyModifier];\n              })\n              .map(function(keyModifier) {\n                return \"$event.\" + keyModifier + \"Key\";\n              })\n              .join(\"||\")\n          );\n        } else {\n          keys.push(key);\n        }\n      }\n      if (keys.length) {\n        code += genKeyFilter(keys);\n      }\n      // Make sure modifiers like prevent and stop get executed after key filtering\n      if (genModifierCode) {\n        code += genModifierCode;\n      }\n      var handlerCode = isMethodPath\n        ? \"return \" + handler.value + \"($event)\"\n        : isFunctionExpression\n          ? \"return (\" + handler.value + \")($event)\"\n          : handler.value;\n      /* istanbul ignore if */\n      return \"function($event){\" + code + handlerCode + \"}\";\n    }\n  }\n\n  function genKeyFilter(keys) {\n    return \"if(!('button' in $event)&&\" + keys.map(genFilterCode).join(\"&&\") + \")return null;\";\n  }\n\n  function genFilterCode(key) {\n    var keyVal = parseInt(key, 10);\n    if (keyVal) {\n      return \"$event.keyCode!==\" + keyVal;\n    }\n    var keyCode = keyCodes[key];\n    var keyName = keyNames[key];\n    return (\n      \"_k($event.keyCode,\" +\n      JSON.stringify(key) +\n      \",\" +\n      JSON.stringify(keyCode) +\n      \",\" +\n      \"$event.key,\" +\n      \"\" +\n      JSON.stringify(keyName) +\n      \")\"\n    );\n  }\n\n  /*  */\n\n  function on(el, dir) {\n    if (\"development\" !== \"production\" && dir.modifiers) {\n      warn(\"v-on without argument does not support modifiers.\");\n    }\n    el.wrapListeners = function(code) {\n      return \"_g(\" + code + \",\" + dir.value + \")\";\n    };\n  }\n\n  /*  */\n\n  function bind$1(el, dir) {\n    el.wrapData = function(code) {\n      return (\n        \"_b(\" +\n        code +\n        \",'\" +\n        el.tag +\n        \"',\" +\n        dir.value +\n        \",\" +\n        (dir.modifiers && dir.modifiers.prop ? \"true\" : \"false\") +\n        (dir.modifiers && dir.modifiers.sync ? \",true\" : \"\") +\n        \")\"\n      );\n    };\n  }\n\n  /*  */\n\n  var baseDirectives = {\n    on: on,\n    bind: bind$1,\n    cloak: noop\n  };\n\n  /*  */\n\n  var CodegenState = function CodegenState(options) {\n    this.options = options;\n    this.warn = options.warn || baseWarn;\n    this.transforms = pluckModuleFunction(options.modules, \"transformCode\");\n    this.dataGenFns = pluckModuleFunction(options.modules, \"genData\");\n    this.directives = extend(extend({}, baseDirectives), options.directives);\n    var isReservedTag = options.isReservedTag || no;\n    this.maybeComponent = function(el) {\n      return !isReservedTag(el.tag);\n    };\n    this.onceId = 0;\n    this.staticRenderFns = [];\n  };\n\n  function generate(ast, options) {\n    var state = new CodegenState(options);\n    var code = ast ? genElement(ast, state) : '_c(\"div\")';\n    return {\n      render: \"with(this){return \" + code + \"}\",\n      staticRenderFns: state.staticRenderFns\n    };\n  }\n\n  function genElement(el, state) {\n    if (el.staticRoot && !el.staticProcessed) {\n      return genStatic(el, state);\n    } else if (el.once && !el.onceProcessed) {\n      return genOnce(el, state);\n    } else if (el.for && !el.forProcessed) {\n      return genFor(el, state);\n    } else if (el.if && !el.ifProcessed) {\n      return genIf(el, state);\n    } else if (el.tag === \"template\" && !el.slotTarget) {\n      return genChildren(el, state) || \"void 0\";\n    } else if (el.tag === \"slot\") {\n      return genSlot(el, state);\n    } else {\n      // component or element\n      var code;\n      if (el.component) {\n        code = genComponent(el.component, el, state);\n      } else {\n        var data = el.plain ? undefined : genData$2(el, state);\n\n        var children = el.inlineTemplate ? null : genChildren(el, state, true);\n        code = \"_c('\" + el.tag + \"'\" + (data ? \",\" + data : \"\") + (children ? \",\" + children : \"\") + \")\";\n      }\n      // module transforms\n      for (var i = 0; i < state.transforms.length; i++) {\n        code = state.transforms[i](el, code);\n      }\n      return code;\n    }\n  }\n\n  // hoist static sub-trees out\n  function genStatic(el, state) {\n    el.staticProcessed = true;\n    state.staticRenderFns.push(\"with(this){return \" + genElement(el, state) + \"}\");\n    return \"_m(\" + (state.staticRenderFns.length - 1) + (el.staticInFor ? \",true\" : \"\") + \")\";\n  }\n\n  // v-once\n  function genOnce(el, state) {\n    el.onceProcessed = true;\n    if (el.if && !el.ifProcessed) {\n      return genIf(el, state);\n    } else if (el.staticInFor) {\n      var key = \"\";\n      var parent = el.parent;\n      while (parent) {\n        if (parent.for) {\n          key = parent.key;\n          break;\n        }\n        parent = parent.parent;\n      }\n      if (!key) {\n        \"development\" !== \"production\" && state.warn(\"v-once can only be used inside v-for that is keyed. \");\n        return genElement(el, state);\n      }\n      return \"_o(\" + genElement(el, state) + \",\" + state.onceId++ + \",\" + key + \")\";\n    } else {\n      return genStatic(el, state);\n    }\n  }\n\n  function genIf(el, state, altGen, altEmpty) {\n    el.ifProcessed = true; // avoid recursion\n    return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty);\n  }\n\n  function genIfConditions(conditions, state, altGen, altEmpty) {\n    if (!conditions.length) {\n      return altEmpty || \"_e()\";\n    }\n\n    var condition = conditions.shift();\n    if (condition.exp) {\n      return (\n        \"(\" +\n        condition.exp +\n        \")?\" +\n        genTernaryExp(condition.block) +\n        \":\" +\n        genIfConditions(conditions, state, altGen, altEmpty)\n      );\n    } else {\n      return \"\" + genTernaryExp(condition.block);\n    }\n\n    // v-if with v-once should generate code like (a)?_m(0):_m(1)\n    function genTernaryExp(el) {\n      return altGen ? altGen(el, state) : el.once ? genOnce(el, state) : genElement(el, state);\n    }\n  }\n\n  function genFor(el, state, altGen, altHelper) {\n    var exp = el.for;\n    var alias = el.alias;\n    var iterator1 = el.iterator1 ? \",\" + el.iterator1 : \"\";\n    var iterator2 = el.iterator2 ? \",\" + el.iterator2 : \"\";\n\n    if (\n      \"development\" !== \"production\" &&\n      state.maybeComponent(el) &&\n      el.tag !== \"slot\" &&\n      el.tag !== \"template\" &&\n      !el.key\n    ) {\n      state.warn(\n        \"<\" +\n          el.tag +\n          ' v-for=\"' +\n          alias +\n          \" in \" +\n          exp +\n          '\">: component lists rendered with ' +\n          \"v-for should have explicit keys. \" +\n          \"See https://vuejs.org/guide/list.html#key for more info.\",\n        true /* tip */\n      );\n    }\n\n    el.forProcessed = true; // avoid recursion\n    return (\n      (altHelper || \"_l\") +\n      \"((\" +\n      exp +\n      \"),\" +\n      \"function(\" +\n      alias +\n      iterator1 +\n      iterator2 +\n      \"){\" +\n      \"return \" +\n      (altGen || genElement)(el, state) +\n      \"})\"\n    );\n  }\n\n  function genData$2(el, state) {\n    var data = \"{\";\n\n    // directives first.\n    // directives may mutate the el's other properties before they are generated.\n    var dirs = genDirectives(el, state);\n    if (dirs) {\n      data += dirs + \",\";\n    }\n\n    // key\n    if (el.key) {\n      data += \"key:\" + el.key + \",\";\n    }\n    // ref\n    if (el.ref) {\n      data += \"ref:\" + el.ref + \",\";\n    }\n    if (el.refInFor) {\n      data += \"refInFor:true,\";\n    }\n    // pre\n    if (el.pre) {\n      data += \"pre:true,\";\n    }\n    // record original tag name for components using \"is\" attribute\n    if (el.component) {\n      data += 'tag:\"' + el.tag + '\",';\n    }\n    // module data generation functions\n    for (var i = 0; i < state.dataGenFns.length; i++) {\n      data += state.dataGenFns[i](el);\n    }\n    // attributes\n    if (el.attrs) {\n      data += \"attrs:{\" + genProps(el.attrs) + \"},\";\n    }\n    // DOM props\n    if (el.props) {\n      data += \"domProps:{\" + genProps(el.props) + \"},\";\n    }\n    // event handlers\n    if (el.events) {\n      data += genHandlers(el.events, false, state.warn) + \",\";\n    }\n    if (el.nativeEvents) {\n      data += genHandlers(el.nativeEvents, true, state.warn) + \",\";\n    }\n    // slot target\n    // only for non-scoped slots\n    if (el.slotTarget && !el.slotScope) {\n      data += \"slot:\" + el.slotTarget + \",\";\n    }\n    // scoped slots\n    if (el.scopedSlots) {\n      data += genScopedSlots(el.scopedSlots, state) + \",\";\n    }\n    // component v-model\n    if (el.model) {\n      data +=\n        \"model:{value:\" +\n        el.model.value +\n        \",callback:\" +\n        el.model.callback +\n        \",expression:\" +\n        el.model.expression +\n        \"},\";\n    }\n    // inline-template\n    if (el.inlineTemplate) {\n      var inlineTemplate = genInlineTemplate(el, state);\n      if (inlineTemplate) {\n        data += inlineTemplate + \",\";\n      }\n    }\n    data = data.replace(/,$/, \"\") + \"}\";\n    // v-bind data wrap\n    if (el.wrapData) {\n      data = el.wrapData(data);\n    }\n    // v-on data wrap\n    if (el.wrapListeners) {\n      data = el.wrapListeners(data);\n    }\n    return data;\n  }\n\n  function genDirectives(el, state) {\n    var dirs = el.directives;\n    if (!dirs) {\n      return;\n    }\n    var res = \"directives:[\";\n    var hasRuntime = false;\n    var i, l, dir, needRuntime;\n    for (i = 0, l = dirs.length; i < l; i++) {\n      dir = dirs[i];\n      needRuntime = true;\n      var gen = state.directives[dir.name];\n      if (gen) {\n        // compile-time directive that manipulates AST.\n        // returns true if it also needs a runtime counterpart.\n        needRuntime = !!gen(el, dir, state.warn);\n      }\n      if (needRuntime) {\n        hasRuntime = true;\n        res +=\n          '{name:\"' +\n          dir.name +\n          '\",rawName:\"' +\n          dir.rawName +\n          '\"' +\n          (dir.value ? \",value:(\" + dir.value + \"),expression:\" + JSON.stringify(dir.value) : \"\") +\n          (dir.arg ? ',arg:\"' + dir.arg + '\"' : \"\") +\n          (dir.modifiers ? \",modifiers:\" + JSON.stringify(dir.modifiers) : \"\") +\n          \"},\";\n      }\n    }\n    if (hasRuntime) {\n      return res.slice(0, -1) + \"]\";\n    }\n  }\n\n  function genInlineTemplate(el, state) {\n    var ast = el.children[0];\n    if (\"development\" !== \"production\" && (el.children.length !== 1 || ast.type !== 1)) {\n      state.warn(\"Inline-template components must have exactly one child element.\");\n    }\n    if (ast.type === 1) {\n      var inlineRenderFns = generate(ast, state.options);\n      return (\n        \"inlineTemplate:{render:function(){\" +\n        inlineRenderFns.render +\n        \"},staticRenderFns:[\" +\n        inlineRenderFns.staticRenderFns\n          .map(function(code) {\n            return \"function(){\" + code + \"}\";\n          })\n          .join(\",\") +\n        \"]}\"\n      );\n    }\n  }\n\n  function genScopedSlots(slots, state) {\n    return (\n      \"scopedSlots:_u([\" +\n      Object.keys(slots)\n        .map(function(key) {\n          return genScopedSlot(key, slots[key], state);\n        })\n        .join(\",\") +\n      \"])\"\n    );\n  }\n\n  function genScopedSlot(key, el, state) {\n    if (el.for && !el.forProcessed) {\n      return genForScopedSlot(key, el, state);\n    }\n    var fn =\n      \"function(\" +\n      String(el.slotScope) +\n      \"){\" +\n      \"return \" +\n      (el.tag === \"template\"\n        ? el.if\n          ? el.if + \"?\" + (genChildren(el, state) || \"undefined\") + \":undefined\"\n          : genChildren(el, state) || \"undefined\"\n        : genElement(el, state)) +\n      \"}\";\n    return \"{key:\" + key + \",fn:\" + fn + \"}\";\n  }\n\n  function genForScopedSlot(key, el, state) {\n    var exp = el.for;\n    var alias = el.alias;\n    var iterator1 = el.iterator1 ? \",\" + el.iterator1 : \"\";\n    var iterator2 = el.iterator2 ? \",\" + el.iterator2 : \"\";\n    el.forProcessed = true; // avoid recursion\n    return (\n      \"_l((\" +\n      exp +\n      \"),\" +\n      \"function(\" +\n      alias +\n      iterator1 +\n      iterator2 +\n      \"){\" +\n      \"return \" +\n      genScopedSlot(key, el, state) +\n      \"})\"\n    );\n  }\n\n  function genChildren(el, state, checkSkip, altGenElement, altGenNode) {\n    var children = el.children;\n    if (children.length) {\n      var el$1 = children[0];\n      // optimize single v-for\n      if (children.length === 1 && el$1.for && el$1.tag !== \"template\" && el$1.tag !== \"slot\") {\n        return (altGenElement || genElement)(el$1, state);\n      }\n      var normalizationType = checkSkip ? getNormalizationType(children, state.maybeComponent) : 0;\n      var gen = altGenNode || genNode;\n      return (\n        \"[\" +\n        children\n          .map(function(c) {\n            return gen(c, state);\n          })\n          .join(\",\") +\n        \"]\" +\n        (normalizationType ? \",\" + normalizationType : \"\")\n      );\n    }\n  }\n\n  // determine the normalization needed for the children array.\n  // 0: no normalization needed\n  // 1: simple normalization needed (possible 1-level deep nested array)\n  // 2: full normalization needed\n  function getNormalizationType(children, maybeComponent) {\n    var res = 0;\n    for (var i = 0; i < children.length; i++) {\n      var el = children[i];\n      if (el.type !== 1) {\n        continue;\n      }\n      if (\n        needsNormalization(el) ||\n        (el.ifConditions &&\n          el.ifConditions.some(function(c) {\n            return needsNormalization(c.block);\n          }))\n      ) {\n        res = 2;\n        break;\n      }\n      if (\n        maybeComponent(el) ||\n        (el.ifConditions &&\n          el.ifConditions.some(function(c) {\n            return maybeComponent(c.block);\n          }))\n      ) {\n        res = 1;\n      }\n    }\n    return res;\n  }\n\n  function needsNormalization(el) {\n    return el.for !== undefined || el.tag === \"template\" || el.tag === \"slot\";\n  }\n\n  function genNode(node, state) {\n    if (node.type === 1) {\n      return genElement(node, state);\n    }\n    if (node.type === 3 && node.isComment) {\n      return genComment(node);\n    } else {\n      return genText(node);\n    }\n  }\n\n  function genText(text) {\n    return (\n      \"_v(\" +\n      (text.type === 2\n        ? text.expression // no need for () because already wrapped in _s()\n        : transformSpecialNewlines(JSON.stringify(text.text))) +\n      \")\"\n    );\n  }\n\n  function genComment(comment) {\n    return \"_e(\" + JSON.stringify(comment.text) + \")\";\n  }\n\n  function genSlot(el, state) {\n    var slotName = el.slotName || '\"default\"';\n    var children = genChildren(el, state);\n    var res = \"_t(\" + slotName + (children ? \",\" + children : \"\");\n    var attrs =\n      el.attrs &&\n      \"{\" +\n        el.attrs\n          .map(function(a) {\n            return camelize(a.name) + \":\" + a.value;\n          })\n          .join(\",\") +\n        \"}\";\n    var bind$$1 = el.attrsMap[\"v-bind\"];\n    if ((attrs || bind$$1) && !children) {\n      res += \",null\";\n    }\n    if (attrs) {\n      res += \",\" + attrs;\n    }\n    if (bind$$1) {\n      res += (attrs ? \"\" : \",null\") + \",\" + bind$$1;\n    }\n    return res + \")\";\n  }\n\n  // componentName is el.component, take it as argument to shun flow's pessimistic refinement\n  function genComponent(componentName, el, state) {\n    var children = el.inlineTemplate ? null : genChildren(el, state, true);\n    return \"_c(\" + componentName + \",\" + genData$2(el, state) + (children ? \",\" + children : \"\") + \")\";\n  }\n\n  function genProps(props) {\n    var res = \"\";\n    for (var i = 0; i < props.length; i++) {\n      var prop = props[i];\n      /* istanbul ignore if */\n      {\n        res += '\"' + prop.name + '\":' + transformSpecialNewlines(prop.value) + \",\";\n      }\n    }\n    return res.slice(0, -1);\n  }\n\n  // #3895, #4268\n  function transformSpecialNewlines(text) {\n    return text.replace(/\\u2028/g, \"\\\\u2028\").replace(/\\u2029/g, \"\\\\u2029\");\n  }\n\n  /*  */\n\n  // these keywords should not appear inside expressions, but operators like\n  // typeof, instanceof and in are allowed\n  var prohibitedKeywordRE = new RegExp(\n    \"\\\\b\" +\n      (\n        \"do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,\" +\n        \"super,throw,while,yield,delete,export,import,return,switch,default,\" +\n        \"extends,finally,continue,debugger,function,arguments\"\n      )\n        .split(\",\")\n        .join(\"\\\\b|\\\\b\") +\n      \"\\\\b\"\n  );\n\n  // these unary operators should not be used as property/method names\n  var unaryOperatorsRE = new RegExp(\n    \"\\\\b\" + \"delete,typeof,void\".split(\",\").join(\"\\\\s*\\\\([^\\\\)]*\\\\)|\\\\b\") + \"\\\\s*\\\\([^\\\\)]*\\\\)\"\n  );\n\n  // strip strings in expressions\n  var stripStringRE = /'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*\\$\\{|\\}(?:[^`\\\\]|\\\\.)*`|`(?:[^`\\\\]|\\\\.)*`/g;\n\n  // detect problematic expressions in a template\n  function detectErrors(ast) {\n    var errors = [];\n    if (ast) {\n      checkNode(ast, errors);\n    }\n    return errors;\n  }\n\n  function checkNode(node, errors) {\n    if (node.type === 1) {\n      for (var name in node.attrsMap) {\n        if (dirRE.test(name)) {\n          var value = node.attrsMap[name];\n          if (value) {\n            if (name === \"v-for\") {\n              checkFor(node, 'v-for=\"' + value + '\"', errors);\n            } else if (onRE.test(name)) {\n              checkEvent(value, name + '=\"' + value + '\"', errors);\n            } else {\n              checkExpression(value, name + '=\"' + value + '\"', errors);\n            }\n          }\n        }\n      }\n      if (node.children) {\n        for (var i = 0; i < node.children.length; i++) {\n          checkNode(node.children[i], errors);\n        }\n      }\n    } else if (node.type === 2) {\n      checkExpression(node.expression, node.text, errors);\n    }\n  }\n\n  function checkEvent(exp, text, errors) {\n    var stipped = exp.replace(stripStringRE, \"\");\n    var keywordMatch = stipped.match(unaryOperatorsRE);\n    if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== \"$\") {\n      errors.push(\n        \"avoid using JavaScript unary operator as property name: \" +\n          '\"' +\n          keywordMatch[0] +\n          '\" in expression ' +\n          text.trim()\n      );\n    }\n    checkExpression(exp, text, errors);\n  }\n\n  function checkFor(node, text, errors) {\n    checkExpression(node.for || \"\", text, errors);\n    checkIdentifier(node.alias, \"v-for alias\", text, errors);\n    checkIdentifier(node.iterator1, \"v-for iterator\", text, errors);\n    checkIdentifier(node.iterator2, \"v-for iterator\", text, errors);\n  }\n\n  function checkIdentifier(ident, type, text, errors) {\n    if (typeof ident === \"string\") {\n      try {\n        new Function(\"var \" + ident + \"=_\");\n      } catch (e) {\n        errors.push(\"invalid \" + type + ' \"' + ident + '\" in expression: ' + text.trim());\n      }\n    }\n  }\n\n  function checkExpression(exp, text, errors) {\n    try {\n      new Function(\"return \" + exp);\n    } catch (e) {\n      var keywordMatch = exp.replace(stripStringRE, \"\").match(prohibitedKeywordRE);\n      if (keywordMatch) {\n        errors.push(\n          \"avoid using JavaScript keyword as property name: \" +\n            '\"' +\n            keywordMatch[0] +\n            '\"\\n  Raw expression: ' +\n            text.trim()\n        );\n      } else {\n        errors.push(\n          \"invalid expression: \" +\n            e.message +\n            \" in\\n\\n\" +\n            \"    \" +\n            exp +\n            \"\\n\\n\" +\n            \"  Raw expression: \" +\n            text.trim() +\n            \"\\n\"\n        );\n      }\n    }\n  }\n\n  /*  */\n\n  function createFunction(code, errors) {\n    try {\n      return new Function(code);\n    } catch (err) {\n      errors.push({ err: err, code: code });\n      return noop;\n    }\n  }\n\n  function createCompileToFunctionFn(compile) {\n    var cache = Object.create(null);\n\n    return function compileToFunctions(template, options, vm) {\n      options = extend({}, options);\n      var warn$$1 = options.warn || warn;\n      delete options.warn;\n\n      /* istanbul ignore if */\n      {\n        // detect possible CSP restriction\n        try {\n          new Function(\"return 1\");\n        } catch (e) {\n          if (e.toString().match(/unsafe-eval|CSP/)) {\n            warn$$1(\n              \"It seems you are using the standalone build of Vue.js in an \" +\n                \"environment with Content Security Policy that prohibits unsafe-eval. \" +\n                \"The template compiler cannot work in this environment. Consider \" +\n                \"relaxing the policy to allow unsafe-eval or pre-compiling your \" +\n                \"templates into render functions.\"\n            );\n          }\n        }\n      }\n\n      // check cache\n      var key = options.delimiters ? String(options.delimiters) + template : template;\n      if (cache[key]) {\n        return cache[key];\n      }\n\n      // compile\n      var compiled = compile(template, options);\n\n      // check compilation errors/tips\n      {\n        if (compiled.errors && compiled.errors.length) {\n          warn$$1(\n            \"Error compiling template:\\n\\n\" +\n              template +\n              \"\\n\\n\" +\n              compiled.errors\n                .map(function(e) {\n                  return \"- \" + e;\n                })\n                .join(\"\\n\") +\n              \"\\n\",\n            vm\n          );\n        }\n        if (compiled.tips && compiled.tips.length) {\n          compiled.tips.forEach(function(msg) {\n            return tip(msg, vm);\n          });\n        }\n      }\n\n      // turn code into functions\n      var res = {};\n      var fnGenErrors = [];\n      res.render = createFunction(compiled.render, fnGenErrors);\n      res.staticRenderFns = compiled.staticRenderFns.map(function(code) {\n        return createFunction(code, fnGenErrors);\n      });\n\n      // check function generation errors.\n      // this should only happen if there is a bug in the compiler itself.\n      // mostly for codegen development use\n      /* istanbul ignore if */\n      {\n        if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {\n          warn$$1(\n            \"Failed to generate render function:\\n\\n\" +\n              fnGenErrors\n                .map(function(ref) {\n                  var err = ref.err;\n                  var code = ref.code;\n\n                  return err.toString() + \" in\\n\\n\" + code + \"\\n\";\n                })\n                .join(\"\\n\"),\n            vm\n          );\n        }\n      }\n\n      return (cache[key] = res);\n    };\n  }\n\n  /*  */\n\n  function createCompilerCreator(baseCompile) {\n    return function createCompiler(baseOptions) {\n      function compile(template, options) {\n        var finalOptions = Object.create(baseOptions);\n        var errors = [];\n        var tips = [];\n        finalOptions.warn = function(msg, tip) {\n          (tip ? tips : errors).push(msg);\n        };\n\n        if (options) {\n          // merge custom modules\n          if (options.modules) {\n            finalOptions.modules = (baseOptions.modules || []).concat(options.modules);\n          }\n          // merge custom directives\n          if (options.directives) {\n            finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives);\n          }\n          // copy other options\n          for (var key in options) {\n            if (key !== \"modules\" && key !== \"directives\") {\n              finalOptions[key] = options[key];\n            }\n          }\n        }\n\n        var compiled = baseCompile(template, finalOptions);\n        {\n          errors.push.apply(errors, detectErrors(compiled.ast));\n        }\n        compiled.errors = errors;\n        compiled.tips = tips;\n        return compiled;\n      }\n\n      return {\n        compile: compile,\n        compileToFunctions: createCompileToFunctionFn(compile)\n      };\n    };\n  }\n\n  /*  */\n\n  // `createCompilerCreator` allows creating compilers that use alternative\n  // parser/optimizer/codegen, e.g the SSR optimizing compiler.\n  // Here we just export a default compiler using the default parts.\n  var createCompiler = createCompilerCreator(function baseCompile(template, options) {\n    var ast = parse(template.trim(), options);\n    if (options.optimize !== false) {\n      optimize(ast, options);\n    }\n    var code = generate(ast, options);\n    return {\n      ast: ast,\n      render: code.render,\n      staticRenderFns: code.staticRenderFns\n    };\n  });\n\n  /*  */\n\n  var ref$1 = createCompiler(baseOptions);\n  var compileToFunctions = ref$1.compileToFunctions;\n\n  /*  */\n\n  // check whether current browser encodes a char inside attribute values\n  var div;\n  function getShouldDecode(href) {\n    div = div || document.createElement(\"div\");\n    div.innerHTML = href ? '<a href=\"\\n\"/>' : '<div a=\"\\n\"/>';\n    return div.innerHTML.indexOf(\"&#10;\") > 0;\n  }\n\n  // #3663: IE encodes newlines inside attribute values while other browsers don't\n  var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;\n  // #6828: chrome encodes content in a[href]\n  var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;\n\n  /*  */\n\n  var idToTemplate = cached(function(id) {\n    var el = query(id);\n    return el && el.innerHTML;\n  });\n\n  var mount = Vue.prototype.$mount;\n  Vue.prototype.$mount = function(el, hydrating) {\n    el = el && query(el);\n\n    /* istanbul ignore if */\n    if (el === document.body || el === document.documentElement) {\n      \"development\" !== \"production\" &&\n        warn(\"Do not mount Vue to <html> or <body> - mount to normal elements instead.\");\n      return this;\n    }\n\n    var options = this.$options;\n    // resolve template/el and convert to render function\n    if (!options.render) {\n      var template = options.template;\n      if (template) {\n        if (typeof template === \"string\") {\n          if (template.charAt(0) === \"#\") {\n            template = idToTemplate(template);\n            /* istanbul ignore if */\n            if (\"development\" !== \"production\" && !template) {\n              warn(\"Template element not found or is empty: \" + options.template, this);\n            }\n          }\n        } else if (template.nodeType) {\n          template = template.innerHTML;\n        } else {\n          {\n            warn(\"invalid template option:\" + template, this);\n          }\n          return this;\n        }\n      } else if (el) {\n        template = getOuterHTML(el);\n      }\n      if (template) {\n        /* istanbul ignore if */\n        if (\"development\" !== \"production\" && config.performance && mark) {\n          mark(\"compile\");\n        }\n\n        var ref = compileToFunctions(\n          template,\n          {\n            shouldDecodeNewlines: shouldDecodeNewlines,\n            shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,\n            delimiters: options.delimiters,\n            comments: options.comments\n          },\n          this\n        );\n        var render = ref.render;\n        var staticRenderFns = ref.staticRenderFns;\n        options.render = render;\n        options.staticRenderFns = staticRenderFns;\n\n        /* istanbul ignore if */\n        if (\"development\" !== \"production\" && config.performance && mark) {\n          mark(\"compile end\");\n          measure(\"vue \" + this._name + \" compile\", \"compile\", \"compile end\");\n        }\n      }\n    }\n    return mount.call(this, el, hydrating);\n  };\n\n  /**\n   * Get outerHTML of elements, taking care\n   * of SVG elements in IE as well.\n   */\n  function getOuterHTML(el) {\n    if (el.outerHTML) {\n      return el.outerHTML;\n    } else {\n      var container = document.createElement(\"div\");\n      container.appendChild(el.cloneNode(true));\n      return container.innerHTML;\n    }\n  }\n\n  Vue.compile = compileToFunctions;\n\n  return Vue;\n});\n"
  },
  {
    "path": "readme.md",
    "content": "# web-console\r\n\r\n![Github release](https://img.shields.io/npm/v/@whinc/web-console.svg)\r\n![总下载量](https://img.shields.io/npm/dt/@whinc/web-console.svg)\r\n![月下载量](https://img.shields.io/npm/dm/@whinc/web-console.svg)\r\n![周下载量](https://img.shields.io/npm/dw/@whinc/web-console.svg)\r\n![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@whinc/web-console.svg)\r\n![LINCENSE](https://img.shields.io/github/license/mashape/apistatus.svg)\r\n\r\nweb-console 是一款基于 H5 开发的移动端 Web 调试工具。其高度还原了 Chrome DevTools 的功能和交互，支持 webpack 打包和`<script>`方式引入。\r\n\r\n在线演示：<https://whinc.github.io/web-console/>\r\n\r\nCDN 地址：<https://unpkg.com/@whinc/web-console>\r\n\r\n![snapshot](./docs/snapshot.png)\r\n\r\n[更多运行截图点击这里](https://github.com/whinc/web-console/blob/master/docs/snapshot.md)\r\n\r\n## 功能特性\r\n\r\nweb-console 特性列表完成情况（可能会变动）：\r\n\r\n- Element 面板\r\n  - 支持 DOM 树展示\r\n  - 支持查看 DOM 节点的继承样式\r\n  - 支持查看 DOM 节点的计算样式\r\n  - 支持查看 DOM 节点的盒模型\r\n- Console 面板\r\n  - 支持 console 对象的 log/warn/info/error/debug 方法\r\n  - 支持 log 等日志方法的多参数输出\r\n  - 支持 log 等日志方法的参数格式化输出，已支持`%s, %i, %d, %f, %d, %o, %O, %c`\r\n  - 支持日志过滤\r\n- Network 面板\r\n  - 支持`XMLHttpRequest`请求和响应的展示\r\n  - 支持`fetch`请求和响应的展示\r\n  - 支持响应数据的预览\r\n- Application 面板\r\n  - 支持 cookie、localStorage 和 sessionStorage 的增删改查\r\n  - 支持 cookie、localStorage 和 sessionStorage 按关键字过滤\r\n- Settings 面板\r\n  - 设置各面板默认行为\r\n  - 关于信息\r\n- 插件\r\n  - 支持自定义插件\r\n  - 支持插件生命周期\r\n  - 支持插件偏好设置\r\n  - 支持使用内置组件\r\n\r\n## 如何使用?\r\n\r\n### 模块化方式导入\r\n\r\n安装 npm 包\r\n\r\n```\r\nnpm install @whinc/web-console\r\n```\r\n\r\n导入 web-console 并初始化\r\n\r\n```js\r\nimport WebConsole from \"@whinc/web-console\";\r\nnew WebConsole();\r\n```\r\n\r\n或者，仅在开发模式下导入\r\n\r\n```js\r\nif (process.env.NODE_ENV === \"development\") {\r\n  import(\"@whinc/web-console\").then(WebConsole => {\r\n    new WebConsole(config);\r\n  });\r\n}\r\n```\r\n\r\n### `<script>`标签导入\r\n\r\n在 html 文件中引入 web-console（依赖 Vue 2.x）\r\n\r\n```html\r\n<script src=\"https://unpkg.com/vue\"></script>\r\n<script src=\"https://unpkg.com/@whinc/web-console\"></script>\r\n```\r\n\r\n通过下面代码初始化\r\n\r\n```js\r\nnew WebConsole(config);\r\n```\r\n\r\n## API\r\n\r\n`WebConsole`构造参数如下：\r\n\r\n| 字段 | 类型 | 必填 | 备注 |\r\n| --------- | ------------ | ----- | ------------------------------------ |\r\n| panelVisible | bool | false | 是否自动弹窗主面板 |\r\n| activeTab | string | 'console' | 默认激活的 Tab 面板，支持'element', 'console', 'network', 'application', 以及插件的 id |\r\n| entryStyle | string | 'button' | 入口样式，支持两种'button'和'icon' |\r\n\r\n> 后续补充更多的配置参数和 API 接口\r\n\r\n## 插件开发\r\n\r\nweb-console 提供一些开箱即用的功能，如果这些无法满足你的需求，你还可以通过 web-console 提供的插件机制，添加第三方编写的插件来扩展功能。\r\n\r\n可参考下面资源：\r\n\r\n- [插件开发文档](./docs/plugin.md)\r\n- [插件项目模板](https://github.com/whinc/web-console-plugin)\r\n\r\n## 更新日志\r\n\r\n[CHANGELOG](CHANGELOG.md)\r\n\r\n## 相似项目\r\n\r\n**Web**\r\n- [vConsole](https://github.com/Tencent/vConsole) A lightweight, extendable front-end developer tool for mobile web page.\r\n- [eruda](https://github.com/liriliri/eruda) Console for mobile browsers\r\n\r\n**Native**\r\n- [wt-console](https://github.com/WeBankFinTech/wt-console) A lightweight, extendable react-native developer and tester tool\r\n\r\n## License\r\n\r\nMIT\r\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div class=\"web-console\">\n    <!-- 悬浮按钮 -->\n    <template v-if=\"!panelVisible\">\n      <img\n        v-if=\"entryStyle === 'icon'\"\n        class=\"entry icon\"\n        :style=\"{right: right + 'px', bottom: bottom + 'px'}\"\n        src=\"@/assets/icons/chrome_logo.png\"\n        @click=\"showPanel\"\n        @touchstart=\"onTouchStart\"\n        @touchmove=\"onTouchMove\"\n        @touchend=\"onTouchEnd\"\n      />\n      <button\n        v-else\n        class=\"entry button\"\n        :style=\"{right: right + 'px', bottom: bottom + 'px'}\"\n        @click=\"showPanel\"\n        @touchstart=\"onTouchStart\"\n        @touchmove=\"onTouchMove\"\n        @touchend=\"onTouchEnd\"\n      >\n        web-console\n      </button>\n    </template>\n\n    <!-- 工具面板 -->\n    <mt-popup position=\"bottom\" v-model=\"panelVisible\">\n      <div class=\"panel\" :style=\"{'padding': `0 ${scrollbarWidth / 2}px`}\">\n        <!-- Tabbar -->\n        <VTabBar v-model=\"activeTab\">\n          <!-- 内置面板 -->\n          <VTabBarItem v-for=\"id in PanelType\" :id=\"id\" :key=\"id\">{{PanelType.text(id)}}</VTabBarItem>\n          <!-- 插件 -->\n          <VTabBarItem v-for=\"plugin in plugins\" :key=\"plugin.id\" :id=\"plugin.id\">{{plugin.name}}</VTabBarItem> \n          <template slot=\"icons\">\n            <VIcon name=\"setting\" @click=\"onClickSetting\" style=\"width: 2em; padding: 0.4em\" />\n          </template>\n        </VTabBar>\n        <!-- 内置面板 -->\n        <ElementPanel v-show=\"activeTab === PanelType.ELEMENT\" />\n        <ConsolePanel v-show=\"activeTab === PanelType.CONSOLE\" />\n        <NetworkPanel v-show=\"activeTab === PanelType.NETWORK\" />\n        <ApplicationPanel v-show=\"activeTab === PanelType.APPLICATION\" />\n        <!-- 插件 -->\n        <div class=\"plugin-panel\" v-for=\"plugin in plugins\" :key=\"plugin.id\" v-show=\"activeTab === plugin.id\">\n          <component :id=\"plugin.id\" :is=\"plugin.component\" />\n        </div>\n        <!-- 设置面板 -->\n        <SettingsPanel v-model=\"isSettingPanelVisible\" />\n      </div>\n    </mt-popup>\n  </div>\n</template>\n\n<script>\nimport { Popup } from \"mint-ui\";\nimport { VTabBar, VTabBarItem, VIcon, VFootBar } from \"@/components\";\nimport { ApplicationPanel, ConsolePanel, SettingsPanel, NetworkPanel, ElementPanel } from \"@/panels\";\nimport { eventBus, Logger } from \"@/utils\";\nimport { pluginManager, pluginEvents } from \"@/plugins\";\nimport { PanelType } from \"@/constants\";\n\nconst logger = new Logger(\"[App]\");\n\nlogger.log(\"pluginManager:\", pluginManager);\n\nexport default {\n  name: \"app\",\n  components: {\n    ElementPanel,\n    ConsolePanel,\n    NetworkPanel,\n    ApplicationPanel,\n    SettingsPanel,\n    VIcon,\n    VFootBar,\n    VTabBar,\n    VTabBarItem,\n    [Popup.name]: Popup\n  },\n  props: {\n    initPanelVisible: Boolean,\n    initActiveTab: String,\n    // 入口样式，支持 'icon' 和 'button'\n    initEntryStyle: String\n  },\n  data() {\n    return {\n      entryStyle: \"icon\",\n      panelVisible: false,\n      isSettingPanelVisible: false,\n      activeTab: \"console\",\n      right: 20,\n      bottom: 20,\n      // 页面右侧滚动条宽度，通过判断滚动条宽度，在页面右侧填充适当空间，修复 PC 端滚动条覆盖面板右侧边缘的问题\n      scrollbarWidth: 0,\n      plugins: []\n    };\n  },\n  computed: {\n    PanelType() {\n      return PanelType;\n    }\n  },\n  watch: {\n    activeTab(newVal, oldVal) {\n      // 触发 Tab 变化事件\n      pluginManager.emit(pluginEvents.WEB_CONSOLE_TAB_CHANGED, newVal, oldVal);\n    },\n    panelVisible(value) {\n      // 通知子元素弹窗可见性变化\n      this.$nextTick(() => {\n        eventBus.emit(eventBus.POPUP_VISIBILITY_CHANGE, value);\n      });\n\n      // 面板显示隐藏时，触发插件生命周期方法\n      pluginManager.emit(value ? pluginEvents.WEB_CONSOLE_SHOW : pluginEvents.WEB_CONSOLE_HIDE);\n\n      value ? this.scaleManager.preventScale() : this.scaleManager.recoverScale();\n    }\n  },\n  beforeMount() {\n    // 非 reactivity 数据\n    this.scaleManager = createScaleManager();\n    this.isTouched = false;\n\n    // mounted 之前加载插件，确保 mounted 执行时插件已挂载\n    this.installPlugins();\n\n    // 监听请求隐藏主面板事件\n    eventBus.on(eventBus.REQUEST_WEB_CONSOLE_HIDE, () => this.hidePanel());\n    // 监听设置加载完毕事件，触发插件生命周期方法\n    eventBus.on(eventBus.SETTINGS_LOADED, settings =>\n      pluginManager.emit(pluginEvents.WEB_CONSOLE_SETTINGS_LOADED, settings)\n    );\n    // 监听设置变化事件，触发插件生命周期方法\n    eventBus.on(eventBus.SETTINGS_CHANGE, settings =>\n      pluginManager.emit(pluginEvents.WEB_CONSOLE_SETTINGS_CHANGED, settings)\n    );\n  },\n  mounted() {\n    // 设置初始值\n    this.panelVisible = this.initPanelVisible;\n    this.initPanelVisible ? this.scaleManager.preventScale() : this.scaleManager.recoverScale();\n    this.activeTab = this.initActiveTab;\n    this.entryStyle = this.initEntryStyle;\n\n    // 获取滚动条宽度\n    this.scrollbarWidth = getScrollbarWidth();\n    window.addEventListener(\"resize\", () => {\n      this.scrollbarWidth = getScrollbarWidth();\n    });\n\n    // 触发插件 onWebConsoleReady 事件\n    // 设置加载是在 beforeMount 中完成的，当主面板 mounted 时，设置项已经可用并且主面板也已经渲染完成，可以安全的告诉插件一切就绪\n    pluginManager.emit(pluginEvents.WEB_CONSOLE_READY);\n  },\n  methods: {\n    showPanel() {\n      this.panelVisible = true;\n    },\n    hidePanel() {\n      this.panelVisible = false;\n    },\n    onTouchStart(e) {\n      this.isTouched = true;\n      const touch = e.changedTouches[0];\n      this.prevClientX = touch.clientX;\n      this.prevClientY = touch.clientY;\n    },\n    onTouchMove(e) {\n      if (!this.isTouched) return;\n      const touch = e.changedTouches[0];\n      this.right -= touch.clientX - this.prevClientX;\n      this.bottom -= touch.clientY - this.prevClientY;\n      // 防止滑出边界\n      this.right = Math.min(Math.max(this.right, 0), document.documentElement.clientWidth - e.target.clientWidth - 1);\n      this.bottom = Math.min(Math.max(this.bottom, 0), document.documentElement.clientHeight - e.target.clientHeight);\n      this.prevClientX = touch.clientX;\n      this.prevClientY = touch.clientY;\n\n      // 避免滚动底层元素\n      e.preventDefault();\n    },\n    onTouchEnd() {\n      this.isTouched = false;\n    },\n    onClickSetting() {\n      this.isSettingPanelVisible = true;\n    },\n    installPlugins() {\n      this.plugins = pluginManager.getPlugins();\n    }\n  }\n};\n\n// 管理页面缩放行为\nfunction createScaleManager() {\n  let onTouchStart = function(e) {\n    if (e.touches.length > 1) {\n      e.preventDefault();\n    }\n  };\n  let lastTouchEnd = 0;\n  let onTouchEnd = function(event) {\n    var now = Date.now();\n    if (now - lastTouchEnd <= 300) {\n      event.preventDefault();\n    }\n    lastTouchEnd = now;\n  };\n  // 由于 IOS 10 以后通过 <meta> 标签无法禁止 safari 页面缩放，解决办法是通过 JS 判断缩放和双击事件来阻止页面缩放行为\n  return {\n    preventScale: () => {\n      logger.log(\"preventScale\");\n      document.documentElement.addEventListener(\"touchstart\", onTouchStart, true);\n      document.documentElement.addEventListener(\"touchend\", onTouchEnd, true);\n    },\n    recoverScale: () => {\n      logger.log(\"recoverScale\");\n      document.documentElement.removeEventListener(\"touchstart\", onTouchStart, true);\n      document.documentElement.removeEventListener(\"touchend\", onTouchEnd, true);\n    }\n  };\n}\n\n// 获取页面滚动条宽度\n// 参考：[通过 JS 判断页面是否有滚动条的简单方法](https://www.cnblogs.com/nzbin/p/8117535.html)\nfunction getScrollbarWidth() {\n  var scrollDiv = document.createElement(\"div\");\n  scrollDiv.style.cssText = \"width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;\";\n  document.body.appendChild(scrollDiv);\n  var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n  document.body.removeChild(scrollDiv);\n\n  return scrollbarWidth;\n}\n</script>\n\n<style scoped lang=\"scss\">\n@import \"./styles/variables\";\n.entry {\n  position: fixed;\n  right: 20px;\n  bottom: 20px;\n  z-index: 99999;\n  &.icon {\n    border-radius: 16px;\n    box-shadow: 0 0 32px rgba(0, 0, 0, 0.4);\n  }\n  &.button {\n    font-size: 1em;\n    font-weight: bold;\n    padding: 0.6em 1em;\n    border-radius: 0.3em;\n    background-color: #26a2ff;\n    color: white;\n    outline: none;\n    border: none;\n    box-shadow: 0 0 0.61538462em rgba(0, 0, 0, 0.4);\n  }\n}\n\n.panel {\n  position: relative;\n  width: 100vw;\n  background-color: white;\n  display: flex;\n  flex-direction: column;\n  box-sizing: border-box;\n}\n\n.plugin-panel {\n  height: $panel-height;\n}\n</style>\n"
  },
  {
    "path": "src/components/VFootBar.vue",
    "content": "<template>\n  <div class=\"foot-bar\">\n    <template v-for=\"(btn, index) in buttons\">\n      <button :key=\"index + btn.text\"\n        class=\"button\"\n        :class=\"{'disable': btn.disable == true}\"\n        @click=\"onClick(btn)\">\n        {{btn.text}}\n      </button>\n      <!-- 最后一个不显示分隔符 -->\n      <div v-if=\"index < buttons.length - 1\" :key=\"index\" class=\"separator\" />\n    </template>\n  </div>  \n</template>\n\n<script>\nimport { isFunction } from \"@/utils\";\nexport default {\n  props: {\n    /**\n     * 按钮\n     * {\n     *  text: String, // 按钮文案\n     *  disable: Boolean, // 禁用\n     *  click: Function, // 点击事件\n     * }\n     */\n    buttons: {\n      type: Array,\n      default() {\n        return [];\n      }\n    }\n  },\n  mounted() {},\n  methods: {\n    onClick(btn) {\n      if (isFunction(btn.click)) {\n        btn.click.call(null);\n      }\n    }\n  }\n};\n</script>\n\n\n<style lang=\"scss\" scoped>\n@import \"../styles/variables\";\n.foot-bar {\n  border-top: 1px solid #d9d9d9;\n  height: $footbar-height;\n  display: flex;\n  flex-direction: row;\n  .button {\n    outline: none;\n    padding: 0;\n    border: none;\n    width: 100%;\n    background-color: #fff;\n    &:active {\n      background-color: rgba(0, 0, 0, 0.15);\n    }\n    &.disable {\n      color: #cdcdcd;\n      &:active {\n        background-color: #fff;\n      }\n    }\n  }\n  .separator {\n    width: 1px;\n    height: 80%;\n    margin-top: auto;\n    margin-bottom: auto;\n    border-left: 1px solid #d9d9d9;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/VHighlightView.vue",
    "content": "<template>\n  <div class=\"v-highlight-view\">\n    <code class=\"source-code hljs\" :class=\"language\" v-html=\"codeHtml\"></code>\n  </div>\n</template>\n\n<script>\nimport hljs from \"highlight.js/lib/highlight\";\nimport javascript from \"highlight.js/lib/languages/javascript\";\nimport css from \"highlight.js/lib/languages/css\";\nimport htmlbars from \"highlight.js/lib/languages/htmlbars\";\nimport xml from \"highlight.js/lib/languages/xml\";\nimport json from \"highlight.js/lib/languages/json\";\nhljs.registerLanguage(\"javascript\", javascript);\nhljs.registerLanguage(\"css\", css);\nhljs.registerLanguage(\"htmlbars\", htmlbars);\nhljs.registerLanguage(\"xml\", xml);\nhljs.registerLanguage(\"json\", json);\n\nexport default {\n  name: \"VHightlightView\",\n  props: {\n    // 要高亮处理的代码字符串\n    code: {\n      type: String,\n      default: \"\"\n    },\n    // 代码语言\n    language: {\n      type: String,\n      default: \"\"\n    }\n  },\n  data() {\n    return {\n      codeHtml: \"\"\n    };\n  },\n  mounted() {\n    const language = this.language;\n    const code = this.code;\n\n    // 根据指定的语言生成高亮，如未指定则自动检测语言并生成高亮\n    let result;\n    if (language) {\n      result = hljs.highlight(language, code, true);\n    } else {\n      result = hljs.highlightAuto(code);\n    }\n    this.codeHtml = result.value;\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.v-highlight-view {\n  white-space: pre;\n  display: block;\n  padding: 5px;\n}\n</style>\n\n<style lang=\"scss\">\n/* hightlight.js style start */\n\n// hightlight.js 生成的标签中不带 hash 值，故需要设为全局样式\n\n/* Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org> */\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  background: #0f0f0f;\n  color: #444;\n\n  &-subst {\n    color: #444;\n  }\n\n  &-comment {\n    color: #888888;\n  }\n\n  &-type,\n  &-string,\n  &-number,\n  &-selector-id,\n  &-selector-class,\n  &-quote,\n  &-template-tag,\n  &-deletion {\n    color: #880000;\n  }\n\n  &-title,\n  &-section {\n    color: #880000;\n    font-weight: bold;\n  }\n\n  &-regexp,\n  &-symbol,\n  &-variables,\n  &-template-variables,\n  &-link,\n  &-selector-attr,\n  &-selector-pseudo {\n    color: #bc6060;\n  }\n\n  &-literal {\n    color: #78a960;\n  }\n\n  &-built_in,\n  &-bullet,\n  &-code,\n  &-addition {\n    color: #397300;\n  }\n\n  /* Meta color: hue: 200 */\n\n  &-meta {\n    color: #1f7199;\n  }\n\n  &-meta-string {\n    color: #4d99bf;\n  }\n\n  /* Misc effects */\n\n  &-emphasis {\n    font-style: italic;\n  }\n\n  &-strong {\n    font-weight: bold;\n  }\n}\n\n/* 覆盖默认样式向 Chrome DevTools 样式看齐 */\n\n$color-normal: black;\n\n.hljs {\n  background: #fff;\n  color: $color-normal;\n\n  &.html {\n    .hljs-name,\n    .hljs-tag {\n      color: rgb(136, 18, 128);\n    }\n\n    .hljs-comment {\n      color: rgb(35, 110, 37);\n    }\n\n    .hljs-attr {\n      color: rgb(153, 69, 0);\n    }\n    .hljs-string {\n      color: rgb(26, 26, 166);\n    }\n  }\n\n  &.json {\n    .hljs-string,\n    .hljs-attr {\n      color: hsl(1, 80%, 43%);\n    }\n    .hljs-number {\n      color: hsl(248, 100%, 41%);\n    }\n    .hljs-literal {\n      color: hsl(310, 86%, 36%);\n    }\n  }\n\n  &.javascript {\n    .hljs-keyword {\n      color: hsl(310, 86%, 36%);\n    }\n    .hljs-params,\n    .hljs-title {\n      color: hsl(240, 73%, 38%);\n      font-weight: normal;\n    }\n    .hljs-built_in {\n      color: $color-normal;\n    }\n    .hljs-literal {\n      color: hsl(310, 86%, 36%);\n    }\n    .hljs-number {\n      color: hsl(240, 73%, 38%);\n    }\n    .hljs-string {\n      color: hsl(1, 80%, 43%);\n    }\n  }\n\n  &.css {\n    .hljs-attribute,\n    .hljs-keyword {\n      color: rgb(200, 0, 0);\n    }\n    .hljs-built_in,\n    .hljs-string {\n      color: rgb(7, 144, 154);\n    }\n    .hljs-selector-class {\n      color: $color-normal;\n    }\n  }\n}\n/* hightlight.js style end */\n</style>\n"
  },
  {
    "path": "src/components/VIcon.vue",
    "content": "<template>\n  <span class=\"icon\" :class=\"'icon--' + name\" @click=\"!disable && $emit('click')\">\n    <img v-if=\"name === 'setting'\" src=\"@/assets/icons/setting.png\"/>\n    <template v-else-if=\"name === 'close'\">\n      <img v-if=\"disable\" src=\"@/assets/icons/close-disable.png\"/>\n      <img v-else src=\"@/assets/icons/close.png\"/>\n    </template>\n    <img v-else-if=\"name === 'refresh'\" src=\"@/assets/icons/refresh.png\"/>\n    <img v-else-if=\"name === 'ban'\" src=\"@/assets/icons/ban.png\"/>\n    <template v-else-if=\"name === 'edit'\">\n      <img v-if=\"disable\" src=\"@/assets/icons/edit-disable.png\"/>\n      <img v-else src=\"@/assets/icons/edit.png\"/>\n    </template>\n    <template v-else-if=\"name === 'save'\">\n      <img v-if=\"disable\" src=\"@/assets/icons/save-disable.png\"/>\n      <img v-else src=\"@/assets/icons/save.png\"/>\n    </template>\n    <template v-else-if=\"name === 'add'\">\n      <img v-if=\"disable\" src=\"@/assets/icons/add-disable.png\"/>\n      <img v-else src=\"@/assets/icons/add.png\"/>\n    </template>\n    <img v-else-if=\"name === 'cancel'\" src=\"@/assets/icons/close.png\"/>\n    <img v-else-if=\"name === 'expand'\" src=\"@/assets/icons/expand.png\"/>\n    <img v-else-if=\"name === 'collapse'\" src=\"@/assets/icons/collapse.png\"/>\n  </span>\n</template>\n\n<script>\n/**\n * 内置图标\n *\n * 使用示例:\n * <VIcon\n *  name=\"setting\"\n *  @click=\"onClick\"\n * />\n */\nexport default {\n  props: {\n    // 图标名称（必须是支持的图标名）\n    // 目前支持：[\"setting\", \"close\", \"refresh\", \"ban\", \"edit\", \"save\", \"add\", \"cancel\", \"expand\", \"collapse\"]\n    name: {\n      type: String\n      // validator(type) {\n      //   return [\"setting\", \"close\", \"refresh\", \"ban\"].indexOf(type) !== -1;\n      // }\n    },\n    disable: {\n      type: Boolean,\n      default: false\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.icon {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  img {\n    width: 100%;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/VJSONViewer/JSONTextBlock.vue",
    "content": "<template>\n  <div class=\"json-text-block\">\n    <!-- name/value -->\n    <!-- @click.stop 避免事件冒泡到上一级 TextBlock -->\n    <div class=\"prop\" @click.stop=\"isFold = !isFold\">\n      <!-- 折叠展开符仅当缩进量大于0，或者传入值是对象且属性数量大于0时可见-->\n      <div v-if=\"deepth > 0 || properties.length > 0\" class=\"indent\" :style=\"indentStyle\">\n        <div class=\"triangles\" :class=\"arrowClass\"></div>\n      </div>\n      <!-- 属性名 -->\n      <template v-if=\"hasName\">\n        <span class=\"key public\">{{name}}</span>\n        <span class=\"space\">: </span>\n      </template>\n      <!-- 属性值 -->\n      <JSONTextInlineBlock\n        :name=\"name\"\n        :value=\"descriptor.value\"\n      />\n    </div>\n    <!-- 子节点 -->\n    <div v-for=\"(item, index) in properties\" :key=\"String(item.name) + index\" v-if=\"!isFold\">\n      <JSONTextBlock\n        :name=\"item.name\"\n        :descriptor=\"item.descriptor\"\n        :deepth=\"deepth + 1\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\n/**\n * 以树状结构展示传入的 JSON 对象\n *\n * 例如，传入值为 obj\n * obj = {\n *  a: 1,\n *  b: 2\n * }\n * UI 展示成：\n * {\n *  a: 1,\n *  b: 2\n * }\n *\n * 使用示例：\n * // 展示值\n * <text-block :descriptor=\"{value: 1}\" />\n * // 展示键和值\n * <text-block :name=\"age\" :descriptor=\"{value: 1}\" />\n */\nimport { isObject, flatMap, Logger, isFunction } from \"@/utils\";\nimport JSONTextInlineBlock from \"./JSONTextInlineBlock\";\n\nconst logger = new Logger(\"[JSONTextBlock]\");\nexport default {\n  name: \"JSONTextBlock\",\n  components: {\n    JSONTextInlineBlock\n  },\n  props: {\n    // 属性描述符，结构同 Object.defineProperty() 的第三个参数\n    descriptor: {\n      type: Object,\n      required: true,\n      validator: descriptor => {\n        try {\n          Object.defineProperty({}, \"key\", descriptor);\n        } catch (error) {\n          logger.error(error.message, \"descriptor:\", descriptor);\n        }\n        // ConsolePanel 内部组件不能再次抛出错误，否则会造成循环错误\n        return true;\n      }\n    },\n    // 属性名（可选）\n    name: [String, Symbol],\n    // 嵌套深度\n    // obj = {a: 1, b: {c: 2}}\n    // obj 深度为 0\n    // obj.b 深度为 1\n    // obj.b.c 深度为 2\n    deepth: {\n      type: Number,\n      default: 0\n    },\n    // 初始状态下展开\n    initialUnfold: {\n      type: Boolean,\n      default: false\n    }\n  },\n  data() {\n    return {\n      // 是否折叠属性树\n      isFold: true\n    };\n  },\n  computed: {\n    isRoot() {\n      return !this.name;\n    },\n    hasName() {\n      return !!this.name;\n    },\n    // 当前对象的所有属性相关信息\n    properties() {\n      // 获取对象值\n      // 如果是值属性，取 value 值；如果是 getter 访问器，取计算值\n      let value = this.descriptor.value;\n\n      let obj;\n      if (isObject(value)) {\n        obj = value;\n      } else {\n        return [];\n      }\n\n      // 获取属性名及其描述符\n      const ownKeys = Object.keys(obj);\n      const properties = flatMap(ownKeys, name => {\n        const descriptor = Object.getOwnPropertyDescriptor(obj, name);\n        if (!descriptor) {\n          return [];\n        }\n\n        // value and writable 与 get/set 访问器不会同时存在\n        if (isFunction(descriptor.get) && isFunction(descriptor.set)) {\n          // 同时存在 getter 和 setter\n          return [\n            { name, descriptor },\n            {\n              // Symbol 类型需要进行转换\n              name: `get ${String(name)}`,\n              descriptor: {\n                value: descriptor.get,\n                enumerable: false\n              }\n            },\n            {\n              name: `set ${String(name)}`,\n              descriptor: {\n                value: descriptor.set,\n                enumerable: false\n              }\n            }\n          ];\n        } else if (isFunction(descriptor.get) && !isFunction(descriptor.set)) {\n          // 只存在 getter\n          return [\n            { name, descriptor },\n            {\n              name: `get ${String(name)}`,\n              descriptor: {\n                value: descriptor.get,\n                enumerable: false\n              }\n            }\n          ];\n        } else if (!isFunction(descriptor.get) && isFunction(descriptor.set)) {\n          // 只存在 setter\n          return [\n            {\n              name: `set ${String(name)}`,\n              descriptor: {\n                value: descriptor.set,\n                enumerable: false\n              }\n            }\n          ];\n        } else {\n          // 不存在 getter 或 setter，即 value\n          return [{ name, descriptor }];\n        }\n      });\n\n      // 属性排序\n      return properties.sort(propCompareFn);\n    },\n    indentStyle() {\n      return {\n        width: this.deepth > 0 ? `${this.deepth}em` : \"auto\"\n      };\n    },\n    // 折叠/展开箭头样式（使用CSS绘制）\n    arrowClass() {\n      return this.properties.length > 0 ? (this.isFold ? \"fold\" : \"unfold\") : \"\";\n    }\n  },\n  mounted() {\n    this.isFold = !this.initialUnfold;\n  },\n  methods: {}\n};\n\n/**\n * 属性排序比较函数\n */\nfunction propCompareFn(propA, propB) {\n  const a = String(propA.name);\n  const b = String(propB.name);\n  return a < b ? -1 : a > b ? 1 : 0;\n}\n</script>\n\n<style scoped lang=\"scss\">\n.json-text-block {\n  display: flex;\n  flex-direction: column;\n  line-height: 1.4em;\n  .indent {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: flex-end;\n    padding-right: 3px;\n    flex-shrink: 0;\n  }\n  .triangles {\n    width: 0;\n    height: 0;\n    $border-width: 5px;\n    /* 等边三角形，tan(30) 约为 0.5773502691896257 */\n    &.fold {\n      border-left: $border-width solid #727272;\n      border-top: $border-width * 0.8 solid transparent;\n      border-bottom: $border-width * 0.8 solid transparent;\n    }\n    &.unfold {\n      border-top: $border-width solid #727272;\n      border-left: $border-width * 0.8 solid transparent;\n      border-right: $border-width * 0.8 solid transparent;\n    }\n  }\n  .content {\n    display: flex;\n    flex-direction: column;\n  }\n  .prop {\n    display: flex;\n    flex-direction: row;\n    .key {\n      flex-shrink: 0;\n      &.public {\n        color: rgb(136, 19, 145);\n      }\n    }\n  }\n  .space {\n    white-space: pre;\n    flex-shrink: 0;\n  }\n\n  .italic {\n    font-style: italic;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/VJSONViewer/JSONTextInlineBlock.vue",
    "content": "<template>\n  <!-- 数组类型 -->\n  <span v-if=\"isArray\" class=\"json-text-inline-block\">\n    <span>[</span>\n    <span v-for=\"(v, i) in value\" :key=\"i\">\n      <JSONTextInlineBlock\n        :name=\"String(i)\"\n        :value=\"v\"\n        :deepth=\"deepth + 1\"\n      />\n      <span v-if=\"i !== value.length - 1\">,&nbsp;</span>\n    </span>\n    <span>]</span>\n  </span>\n  <!-- 对象 -->\n  <span v-else-if=\"isObject\" class=\"json-text-inline-block\">\n    <!-- 展示对象详情 -->\n    <span>{</span>\n    <!-- 第1层属性要展示且最多展示 5 个，超过 5 个后使用省略号替代 -->\n    <template v-if=\"deepth === 0\">\n      <span v-for=\"(propName, index) in displayPropertyNames\" :key=\"index\" v-if=\"index < maxDisplayPropertyCount\">\n        <span>{{propName}}: </span>\n        <JSONTextInlineBlock\n          :name=\"propName\"\n          :value=\"value[propName]\"\n          :deepth=\"deepth + 1\"\n        />\n        <span v-if=\"index !== Math.min(maxDisplayPropertyCount - 1, displayPropertyNames.length - 1)\">, </span>\n      </span>\n      <span v-if=\"displayPropertyNames.length >= 5\">, ...</span>\n    </template>\n    <!-- 超过1层的属性用省略号替代展示 -->\n    <span v-else>...</span>\n    <span>}</span>\n  </span>\n  <!-- 字符串类型 -->\n  <span v-else-if=\"isString\" class=\"json-text-inline-block\">\n    <template v-if=\"name\">\n      <span class=\"string-quote\">\"</span>\n      <span :class=\"{'string': isRoot}\">{{value}}</span>\n      <span class=\"string-quote\">\"</span>\n    </template>\n    <template v-else>\n      <span>{{value}}</span>\n    </template>\n  </span>\n  <!-- 其他类型 -->\n  <span v-else class=\"json-text-inline-block\" :class=\"isRoot ? valueClass : ''\">{{formattedValue}}</span>\n</template>\n\n<script>\n/**\n * 以一行内展示传入的 JSON 对象\n */\nimport { isNull, isBoolean, isString, isNumber, isObject, isSymbol, isUndefined, isArray } from \"@/utils\";\n\nexport default {\n  name: \"JSONTextInlineBlock\",\n  props: {\n    // 属性名\n    // 属性名为空时，表示内联块作为根元素展示\n    // 属性名有值时，表示内敛块作为对象的属性值展示\n    name: [String, Symbol],\n    // 属性值\n    value: {},\n    // 嵌套深度\n    // let obj = {a: {b: 2}}\n    // obj 的 deepth 为 0\n    // obj.a 的 deepth 为 1\n    // obj.a.b 的 deepth 为 2 以此类推\n    deepth: {\n      type: Number,\n      default: 0\n    }\n  },\n  computed: {\n    isRoot() {\n      return this.deepth === 0;\n    },\n    isArray() {\n      return isArray(this.value);\n    },\n    isString() {\n      return isString(this.value);\n    },\n    isObject() {\n      return isObject(this.value);\n    },\n    maxDisplayPropertyCount() {\n      return 5;\n    },\n    displayPropertyNames() {\n      if (isObject(this.value)) {\n        return Object.keys(this.value);\n      } else {\n        return [];\n      }\n    },\n    formattedValue() {\n      const value = this.value;\n      if (isNull(value) || isUndefined(value)) {\n        return String(value);\n      }\n      return value;\n    },\n    valueClass() {\n      const value = this.value;\n      return {\n        null: isNull(value),\n        undefined: isUndefined(value),\n        boolean: isBoolean(value),\n        number: isNumber(value),\n        // 字符串仅当放到对象或数组中（即有key）时，需要高亮并带双引号显式\n        string: isString(value),\n        symbol: isSymbol(value)\n      };\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.json-text-inline-block {\n  word-break: break-all;\n\n  .normal {\n    color: #565656;\n  }\n  .null,\n  .undefined {\n    color: rgb(128, 128, 128);\n  }\n  .boolean,\n  .function {\n    color: rgb(13, 34, 170);\n  }\n  .number {\n    color: #1c00cf;\n  }\n  .string,\n  .symbol {\n    color: rgb(196, 26, 22);\n  }\n  .string-quote {\n    color: #222;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/VJSONViewer/VJSONViewer.vue",
    "content": "<template>\n  <div class=\"source-code v-json-viewer\">\n    <JSONTextBlock :descriptor=\"descriptor\" initialUnfold />\n  </div>  \n</template>\n\n<script>\nimport JSONTextBlock from \"./JSONTextBlock.vue\";\nimport { Logger } from \"@/utils\";\n\nconst logger = new Logger(\"[VJSONViewer]\");\nexport default {\n  components: {\n    JSONTextBlock\n  },\n  props: {\n    // 任意 JS 类型值\n    value: {\n      required: true\n    }\n  },\n  computed: {\n    descriptor() {\n      let v = \"(error)\";\n      try {\n        v = JSON.parse(JSON.stringify(this.value));\n      } catch (err) {\n        logger.error(err);\n      }\n      return { value: v };\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.v-json-viewer {\n  padding: 5px;\n}\n</style>\n"
  },
  {
    "path": "src/components/VJSONViewer/index.js",
    "content": "export { default } from \"./VJSONViewer.vue\";\n"
  },
  {
    "path": "src/components/VTabBar.vue",
    "content": "<template>\n  <div class=\"v-tab-bar\" :class=\"{'show-bottom-border': showBottomBorder}\">\n    <div class=\"item-container g-hide-scrollbar\">\n      <slot></slot>\n    </div>\n    <div class=\"icon-container\">\n      <slot name=\"icons\"></slot>\n    </div>\n  </div>\n</template>\n\n<script>\n/**\n * Tab 栏\n *\n * 使用示例：\n * <VTabBar v-model=\"activedTab\">\n *  <VTabBarItem id=\"tab1\">Tab1</VTabBarItem>\n *  <VTabBarItem id=\"tab2\">Tab2</VTabBarItem>\n * </VTabBar>\n */\nexport default {\n  name: \"v-tab-bar\",\n  props: {\n    // TabItem 等宽显示\n    equalWidth: {\n      type: Boolean,\n      default: false\n    },\n    // TabBar 显示下边框\n    showBottomBorder: {\n      type: Boolean,\n      default: true\n    },\n    // 实现 v-model 指令\n    value: String\n  },\n  methods: {\n    updateSelectedItem(id) {\n      this.$emit(\"input\", id);\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../styles/variables\";\n.v-tab-bar {\n  flex: 0 0 auto;\n  width: 100%;\n  height: $tabbar-height;\n  background-color: $tabbar-bg-color;\n  display: flex;\n  &.show-bottom-border {\n    border-bottom: 1px solid $tabbar-border-color;\n  }\n  .item-container {\n    height: 100%;\n    display: flex;\n    flex: 1 1 auto;\n    overflow-x: auto;\n  }\n  .icon-container {\n    height: 100%;\n    display: flex;\n    flex: 0 0 none;\n    &:active {\n      background-color: #eaeaea;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/VTabBarItem.vue",
    "content": "<template>\n  <div class=\"v-tab-bar-item\" :class=\"{'equal-width': $parent.equalWidth}\">\n    <div class=\"tab-item\" :class=\"{selected: isSelected}\" @click=\"$parent.updateSelectedItem(id)\">\n      <slot></slot>\n    </div>\n    <div v-if=\"isSelected\" class=\"tab-slider\"></div>\n  </div>\n</template>\n\n<script>\n/**\n * Tab 栏子项\n */\nexport default {\n  name: \"v-tab-bar-item\",\n  props: {\n    id: {\n      type: String,\n      required: true\n    }\n  },\n  computed: {\n    isSelected() {\n      return this.$parent.value === this.id;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n$toolbar-hover-bg-color: #eaeaea;\n$toolbar-fg-color: #5a5a5a;\n$toolbar-selected-fg-color: #333;\n\n.v-tab-bar-item {\n  position: relative;\n  display: flex;\n  height: 100%;\n  .tab-item {\n    width: 100%;\n    color: $toolbar-fg-color;\n    border-left: 2px solid transparent;\n    border-right: 2px solid transparent;\n    padding: 2px 0.8em;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    &:active {\n      background-color: $toolbar-hover-bg-color;\n    }\n    &.selected {\n      color: $toolbar-selected-fg-color;\n    }\n  }\n  .tab-slider {\n    position: absolute;\n    left: 0px;\n    right: 0px;\n    bottom: 0px;\n    height: 2px;\n    background-color: #03a9f4;\n  }\n}\n\n.equal-width {\n  flex: 1;\n}\n</style>\n"
  },
  {
    "path": "src/components/index.js",
    "content": "export { default as VTabBar } from \"./VTabBar\";\nexport { default as VTabBarItem } from \"./VTabBarItem\";\nexport { default as VFootBar } from \"./VFootBar\";\nexport { default as VJSONViewer } from \"./VJSONViewer\";\nexport { default as VIcon } from \"./VIcon\";\nexport { default as VHighlightView } from \"./VHighlightView\";\n"
  },
  {
    "path": "src/constants/PanelType.js",
    "content": "export default Object.freeze(\n  Object.setPrototypeOf(\n    {\n      ELEMENT: \"element\",\n      CONSOLE: \"console\",\n      APPLICATION: \"application\",\n      NETWORK: \"network\"\n    },\n    {\n      text(type) {\n        switch (type) {\n          case this.ELEMENT:\n            return \"Element\";\n          case this.CONSOLE:\n            return \"Console\";\n          case this.APPLICATION:\n            return \"Application\";\n          case this.NETWORK:\n            return \"Network\";\n          default:\n            return type;\n        }\n      }\n    }\n  )\n);\n"
  },
  {
    "path": "src/constants/index.js",
    "content": "export { default as PanelType } from \"./PanelType\";\n"
  },
  {
    "path": "src/main.js",
    "content": "import Vue from \"vue\";\nimport App from \"./App.vue\";\nimport \"./polyfill\";\nimport { consoleHooks, filters, isFunction } from \"@/utils\";\nimport { pluginManager, Plugin } from \"@/plugins\";\nimport { PanelType } from \"@/constants\";\nimport InfiniteScroll from \"vue-infinite-scroll\";\nimport \"./styles/_global.scss\";\n\n// register filters\nObject.keys(filters).forEach(name => {\n  Vue.filter(name, filters[name]);\n});\n\n// Fix: 避免重复注册插件（从 mint-ui 库引入和从 vue-infinite-scroll 引入是同一个插件的两个不同实例）\nconst installedPlugins = Vue._installedPlugins || [];\nconst foundIndex = installedPlugins.findIndex(plugin => isFunction(plugin.bind) && isFunction(plugin.unbind));\nif (foundIndex === -1) {\n  Vue.use(InfiniteScroll);\n} else {\n  console.warn('\"vue-infinite-scroll\" has been registered');\n}\n\n// const logger = new Logger(\"[main.js]\");\n\n// 放在可滚动容器上，在滚动触顶或触底时，可以阻止背景层滚动\nVue.directive(\"prevent-bkg-scroll\", {\n  bind(el) {\n    let preventMove = false;\n    el.addEventListener(\"touchstart\", function() {\n      // logger.log('touchstart scrollTop: %s, clientHeight: %s, scrollHeight: %s', el.scrollTop, el.clientHeight, el.scrollHeight)\n      if (el.scrollTop <= 0) {\n        // 滚动到顶部时将其设置为 1，避免触顶后不能向下滚动(IOS 上 scrollTop 会出现负数)\n        el.scrollTop = 1;\n        // 如果内容不足一屏，则禁用 touchmove\n        if (el.scrollTop === 0) {\n          preventMove = true;\n        }\n      } else if (el.scrollTop + el.clientHeight >= el.scrollHeight) {\n        // 滚动到底部时，将滚动距离设置为最大可滚动距离 - 1，避免触底后不能向上滚动\n        el.scrollTop = el.scrollHeight - el.clientHeight - 1;\n        // 如果内容不足一屏\n        if (el.scrollTop + el.clientHeight === el.scrollHeight) {\n          preventMove = true;\n        }\n      }\n    });\n    el.addEventListener(\"touchmove\", function(e) {\n      if (preventMove) {\n        e.preventDefault();\n      }\n    });\n    el.addEventListener(\"touchend\", function() {\n      preventMove = false;\n    });\n  }\n});\n\nVue.config.productionTip = false;\n\n// WebConsole 只能创建一个实例\nlet _webConsole = null;\n\nclass WebConsole {\n  // 注册插件\n  static use(pluginCreator, options) {\n    if (_webConsole) {\n      console.warn(\"Plugin must be registered before creating WebConsole\");\n      return;\n    }\n\n    if (!isFunction(pluginCreator)) {\n      console.warn(\"Invalid plugin:\", pluginCreator);\n      return;\n    }\n    const plugin = pluginCreator.call(null, WebConsole, options);\n    pluginManager.addPlugin(plugin);\n  }\n\n  constructor(options) {\n    if (_webConsole) {\n      console.warn(\"WebConsole has been initialized, return previous instance: %O\", _webConsole);\n      return _webConsole;\n    }\n\n    _webConsole = this;\n    this._isLoaded = false;\n    this._load(this._processOptions(options));\n  }\n\n  _processOptions(options) {\n    const defaultOptions = {\n      panelVisible: false,\n      activeTab: \"console\",\n      entryStyle: \"button\"\n    };\n\n    const supportTab = Object.keys(PanelType).map(key => PanelType[key]);\n\n    let activeTab = options.activeTab;\n    if (supportTab.indexOf(activeTab) === -1 && !pluginManager.hasPlugin(activeTab)) {\n      activeTab = defaultOptions.activeTab;\n    }\n\n    return {\n      panelVisible: options.panelVisible || defaultOptions.panelVisible,\n      entryStyle: options.entryStyle || defaultOptions.entryStyle,\n      activeTab: activeTab\n    };\n  }\n\n  _load(options = {}) {\n    if (!this._isLoaded && (document.readyState === \"interactive\" || document.readyState === \"complete\")) {\n      this._isLoaded = true;\n      const root = document.createElement(\"div\");\n      (document.documentElement || document.body).appendChild(root);\n\n      const vm = new Vue({\n        el: root,\n        render: h =>\n          h(App, {\n            props: {\n              initPanelVisible: options.panelVisible,\n              initActiveTab: options.activeTab,\n              initEntryStyle: options.entryStyle\n            }\n          })\n      });\n    } else {\n      document.addEventListener(\"readystatechange\", this._load.bind(this, ...arguments));\n    }\n  }\n}\n\nWebConsole.Plugin = Plugin;\n\nif (!window.WebConsole) {\n  window.WebConsole = WebConsole;\n  consoleHooks.install();\n} else {\n  console.error(\"Inject WebConsole into window failed\");\n}\nexport default WebConsole;\n"
  },
  {
    "path": "src/panels/application/ApplicationPanel.vue",
    "content": "<template>\n  <div class=\"application-panel\">\n    <div class=\"head\">\n      <VTabBar class=\"head__tabbar\" v-model=\"activeTab\" :equalWidth=\"true\">\n        <VTabBarItem v-for=\"{type, desc} in storageTypes\" :key=\"type\"\n          :id=\"type\">\n          {{desc}}\n        </VTabBarItem>\n        <VIcon slot=\"icons\" :name=\"isToolbarExpanded ? 'collapse' : 'expand'\" class=\"head__icon\" @click=\"isToolbarExpanded = !isToolbarExpanded\" />\n      </VTabBar>\n      <!-- <div class=\"head__separator\" /> -->\n    </div>\n    <TabStorage v-for=\"{type} in storageTypes\" :key=\"type\"\n      v-show=\"activeTab === type\"\n      ref=\"tabStorage\"\n      class=\"body\"\n      :class=\"type\"\n      :storageType=\"type\"\n      :isToolbarExpanded=\"isToolbarExpanded\"\n    />\n    <VFootBar class=\"foot\" :buttons=\"buttons\" />\n  </div>\n</template>\n\n<script>\nimport { VTabBar, VTabBarItem, VFootBar, VIcon } from \"@/components\";\nimport { eventBus } from \"@/utils\";\nimport TabStorage from \"./TabStorage\";\n\nexport default {\n  name: \"ApplicationPanel\",\n  components: {\n    VTabBar,\n    VTabBarItem,\n    TabStorage,\n    VIcon,\n    VFootBar\n  },\n  data() {\n    return {\n      activeTab: \"localStorage\",\n      // 工具栏是否处于展开态\n      isToolbarExpanded: false\n    };\n  },\n  computed: {\n    buttons() {\n      return [\n        {\n          text: \"Refresh\",\n          click: () => {\n            // 刷新当前激活的 Stroage 数据\n            const tabStorage = this.getActiveTabStorage();\n            if (tabStorage) {\n              tabStorage.onRefresh();\n            }\n          }\n        },\n        {\n          text: \"Clear\",\n          click: () => {\n            // 清除当前激活的 Stroage 数据\n            const tabStorage = this.getActiveTabStorage();\n            if (tabStorage) {\n              tabStorage.onClearAll();\n            }\n          }\n        },\n        {\n          text: \"Hide\",\n          click: () => {\n            eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE);\n          }\n        }\n      ];\n    },\n    storageTypes() {\n      return [\n        { type: \"localStorage\", desc: \"Local Storage\" },\n        { type: \"sessionStorage\", desc: \"Session Storage\" },\n        { type: \"cookieStorage\", desc: \"Cookie\" }\n      ];\n    }\n  },\n  created() {\n    // 监听\"偏好设置\"变化\n    const bindOnSettingsChanged = this.onSettingsChanged.bind(this);\n    eventBus.on(eventBus.SETTINGS_LOADED, bindOnSettingsChanged);\n    eventBus.on(eventBus.SETTINGS_CHANGE, bindOnSettingsChanged);\n  },\n  methods: {\n    onSettingsChanged(settings) {\n      this.isToolbarExpanded = settings.showApplicationToolbar;\n    },\n    // 获取当前激活的 TabStroage 组件实例\n    getActiveTabStorage() {\n      const tabStorageList = this.$refs.tabStorage;\n      if (Array.isArray(tabStorageList)) {\n        const foundIndex = tabStorageList.findIndex(v => v.storageType === this.activeTab);\n        if (foundIndex !== -1) {\n          return tabStorageList[foundIndex];\n        }\n      }\n      return null;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.application-panel {\n  height: $panel-height;\n  display: flex;\n  flex-direction: column;\n  .head {\n    flex: 0 0 auto;\n    display: flex;\n    &__tabbar {\n      flex: 1 1 auto;\n    }\n    &__icon {\n      padding: 0.6em;\n      width: 2.4em;\n    }\n  }\n  .body {\n    flex: 1 1 auto;\n  }\n  .foot {\n    flex: 0 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/application/TabStorage.vue",
    "content": "<template>\n  <div class=\"tab-storage\">\n    <div v-show=\"isToolbarExpanded\" class=\"tab-storage__head toolbar\">\n      <VIcon name=\"refresh\" class=\"toolbar__button\" @click=\"onRefresh\" />\n      <VIcon name=\"ban\" class=\"toolbar__button\" @click=\"onClearAll\" />\n      <VIcon name=\"close\" :disable=\"!select\" class=\"toolbar__button\" @click=\"onClearSelected\" />\n\n      <VIcon v-if=\"isEditting\" name=\"save\" class=\"toolbar__button\" @click=\"onClickSave\" />\n      <VIcon v-else name=\"edit\" :disable=\"!select\" class=\"toolbar__button\" @click=\"onClickEdit\" />\n      <VIcon name=\"add\" :disable=\"isEditting\" class=\"toolbar__button\" @click=\"onClickAdd\" />\n      <input class=\"toolbar__input\" type=\"text\" placeholder=\"Filter\" v-model=\"filter\" />\n    </div>\n    <div class=\"tag-storage__body table\">\n      <div class=\"table__head\">\n        <div class=\"table__row table__row--head\">\n          <div class=\"table__cell table__cell--head\">Key({{storageLength}})<!--[{{kvList.length}}]--></div>\n          <div class=\"table__cell table__cell--head\">Value</div>\n        </div>\n      </div>\n      <div class=\"table__body\"\n        v-prevent-bkg-scroll\n        v-infinite-scroll=\"loadMore\"\n        infinite-scroll-immediate-check=\"false\"\n        infinite-scroll-distance=\"120\"\n      >\n        <div class=\"table__row\"\n          v-for=\"({value, key}) in kvList\"\n          :key=\"key\"\n          :class=\"{'table__row--selected': select === key}\"\n          :ref=\"key\"\n          @click=\"onClickRow(key)\"\n          >\n          <template v-if=\"select === key && isEditting\">\n            <div class=\"table__cell table__cell--edit\">\n              <input v-model=\"editingKey\" ref=\"activeInput\" />\n            </div>\n            <div class=\"table__cell table__cell--edit\">\n              <input v-model=\"editingValue\" />\n            </div>\n          </template>\n          <template v-else>\n            <div class=\"table__cell g-hide-scrollbar\">{{key}}</div>\n            <div class=\"table__cell g-hide-scrollbar\">{{value}}</div>\n          </template>\n        </div>\n      </div>\n    </div>\n  </div>  \n</template>\n\n<script>\nimport { VIcon } from \"@/components\";\nimport { Logger } from \"@/utils\";\nimport XStorage from \"./XStorage\";\n\nconst logger = new Logger(\"[TabStorage]\");\n\nexport default {\n  components: {\n    VIcon\n  },\n  props: {\n    // 存储类型\n    storageType: {\n      type: String,\n      required: true,\n      validator(val) {\n        return [\"localStorage\", \"sessionStorage\", \"cookieStorage\"].indexOf(val) !== -1;\n      }\n    },\n    // 工具栏是否处于展开态\n    isToolbarExpanded: {\n      type: Boolean,\n      default: true\n    }\n  },\n  data() {\n    return {\n      // 过滤关键字\n      filter: \"\",\n      // 当前选项的名称\n      select: \"\",\n      // 是否处于编辑状态\n      isEditting: false,\n      // 当前编辑键值对\n      editingKey: \"\",\n      editingValue: \"\",\n      storageLength: 0,\n      /**\n       * storage 的 key/value 列表\n       * @type {Array<{key: String, value: String}>}\n       */\n      kvList: []\n    };\n  },\n  computed: {},\n  watch: {\n    filter(val) {\n      this._xStorage.setFilter(val);\n      this.onRefresh();\n    }\n  },\n  mounted() {\n    this._xStorage = new XStorage(this.storageType);\n    this.onRefresh();\n  },\n  methods: {\n    onRefresh() {\n      logger.log(\"onRefresh\");\n      // 最后一条数据的下一条的索引\n      this.select = \"\";\n      this.endEdit();\n      this.kvList = [];\n\n      this._xStorage.refresh();\n      // 刷新后重新获取数据项数量\n      this.storageLength = this._xStorage.length;\n      this.kvList.push(...this._xStorage.loadMoreItems());\n    },\n    loadMore() {\n      // no more data\n      if (!this._xStorage.hasMore()) return;\n\n      this.kvList.push(...this._xStorage.loadMoreItems());\n    },\n    /**\n     * 进入编辑状态\n     * @param {String} key 初始值\n     * @param {String} value 初始值\n     */\n    startEdit(key, value) {\n      this.select = key;\n      this.isEditting = true;\n      this.editingKey = key;\n      this.editingValue = value;\n\n      // 自动聚焦编辑输入框\n      this.$nextTick(() => {\n        const arr = this.$refs.activeInput;\n        if (arr && arr.length > 0) {\n          const el = arr[0];\n          el.focus({ preventScroll: false });\n        }\n      });\n    },\n    /**\n     * 结束编辑状态\n     * @returns 最后一次编辑的 key/value\n     */\n    endEdit() {\n      const data = {\n        key: this.editingKey,\n        value: this.editingValue\n      };\n      this.isEditting = false;\n      this.editingKey = \"\";\n      this.editingValue = \"\";\n      return data;\n    },\n    // 移除数据源以及视图数据中指定项\n    removeItem(key) {\n      this._xStorage.removeItem(key);\n      const foundIndex = this.kvList.findIndex(item => item.key === key);\n      if (foundIndex !== -1) {\n        this.kvList.splice(foundIndex, 1);\n      }\n      this.storageLength = this._xStorage.length;\n    },\n    getItem(key) {\n      return this._xStorage.getItem(key);\n    },\n    // 更新数据源以及视图数据中指定项\n    setItem(key, value) {\n      this._xStorage.setItem(key, value);\n      const item = this.kvList.find(item => item.key === key);\n      // 如果存在则更新，否则需要刷新数据源以保证正确的展示顺序和分页加载顺序\n      if (item) {\n        item.value = value;\n      } else {\n        this.onRefresh();\n      }\n    },\n    // 清除数据源以及视图数据\n    clear() {\n      this._xStorage.clear();\n      this.onRefresh();\n    },\n    onClearAll() {\n      const msg = `Are you sure clear all ${this.storageType === \"cookieStorage\" ? \"cookie\" : this.storageType}?`;\n      window.confirm(msg) && this.clear();\n    },\n    onClearSelected() {\n      const key = this.select;\n      if (!key) return;\n\n      const selectIndex = this.kvList.findIndex(item => item.key === key);\n      this.removeItem(key);\n      // 选中下一项\n      this.select = this.kvList[selectIndex] ? this.kvList[selectIndex].key : \"\";\n      this.endEdit();\n    },\n    onClickEdit() {\n      if (!this.select) return;\n\n      const key = this.select;\n      const value = this.getItem(key);\n      this.startEdit(key, value);\n    },\n    onClickRow(key) {\n      if (!this.isEditting) {\n        this.select = key;\n      }\n    },\n    onClickSave() {\n      if (!this.isEditting) return;\n\n      const oldKey = this.select;\n      const oldValue = this._xStorage.getItem(oldKey);\n      const { key, value } = this.endEdit();\n\n      /* storage：移除旧值，更新新值 */\n      if (key === \"\") {\n        this.removeItem(oldKey);\n        this.removeItem(key);\n      } else if (key && oldKey === \"\") {\n        this.removeItem(oldKey);\n        this.setItem(key, value);\n      } else {\n        // key and oldKey are not empty\n        if (key === oldKey) {\n          if (value !== oldValue) {\n            this.setItem(key, value);\n          }\n        } else {\n          this.removeItem(oldKey);\n          this.setItem(key, value);\n        }\n      }\n\n      // if (name) {\n      //   keyValueMap[name] = value;\n      //   // 将新增项滚动到可见区域\n      //   if (this.$refs[name]) {\n      //     const el = this.$refs[name][0];\n      //     if (el) el.scrollIntoView();\n      //   }\n      // }\n    },\n    onClickAdd() {\n      const item = {\n        key: \"\",\n        value: \"\"\n      };\n      this.kvList.unshift(item);\n      this.startEdit(item.key, item.value);\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/mixins\";\n.tab-storage {\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  &__head {\n    flex: 0 0 2.4em;\n  }\n  &__body {\n    flex: 1 1 auto;\n  }\n}\n\n.scroll-y {\n  overflow-y: auto;\n}\n\n.toolbar {\n  display: flex;\n  align-items: center;\n  background-color: rgb(243, 243, 243);\n  border-bottom: 1px solid rgb(205, 205, 205);\n  // Fix: 子元素 <input> 设置高度百分比无效的问题\n  height: 0px;\n  &__button {\n    padding: 0.55em;\n    width: 2.4em;\n    &:active {\n      background-color: #eaeaea;\n    }\n  }\n  &__input {\n    @include input;\n    flex: 1 1 auto;\n    height: 80%;\n    margin: 0 4px;\n    padding: 0 4px;\n  }\n}\n\n.table {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  &__head {\n    flex: 0 0 30px;\n  }\n  &__body {\n    flex: 1 1 auto;\n    overflow-y: auto;\n  }\n  &__row {\n    height: 30px;\n    display: flex;\n    &--head {\n      border-bottom: 1px solid #aaa;\n    }\n    &:nth-child(even) {\n      background-color: rgb(242, 247, 253);\n    }\n    // 覆盖选中行前景色和背景色\n    &--selected {\n      @include descendant-attr(\"color\", white);\n      @include descendant-attr(\"background-color\", #2196f3);\n    }\n  }\n  &__cell {\n    height: 100%;\n    flex: 1 1 auto;\n    display: flex;\n    align-items: center;\n    padding: 0 4px;\n\n    // 超出时可滚动\n    border-right: 1px solid #aaa;\n    white-space: nowrap;\n    overflow-x: auto;\n    &:first-child {\n      flex: 0 0 50%;\n      max-width: 50%;\n    }\n    &--head {\n    }\n    &--edit {\n      @include descendant-attr(\"color\", black);\n      @include descendant-attr(\"background-color\", white);\n      padding: 0;\n      box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.1);\n      input {\n        outline: none;\n        height: 100%;\n        width: 100%;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/application/XStorage.js",
    "content": "import { Logger, cloneDeep } from \"@/utils\";\nimport { cookie } from \"cookie_js\";\n\nconst logger = new Logger(\"XStorage\");\n\nclass CookieStorage {\n  constructor() {\n    this._keys = [];\n    this.refresh();\n  }\n\n  refresh() {\n    const cookies = cookie.all();\n    this._keys = Object.keys(cookies);\n  }\n\n  get length() {\n    return this._keys.length;\n  }\n\n  key(n) {\n    return this._keys[n];\n  }\n\n  getItem(key) {\n    return cookie.get(key);\n  }\n\n  setItem(key, value) {\n    cookie.set(key, value);\n    this.refresh();\n  }\n\n  removeItem(key) {\n    cookie.remove(key);\n    this.refresh();\n  }\n\n  clear() {\n    cookie.empty();\n    this.refresh();\n  }\n}\n\n/**\n * 统一 localStorage/sessionStorage/cookie 三种存储器接口\n *\n *                    XStorage\n *                       |\n *                       | <- XStorage.refresh()\n *                       |\n *                    Filtered Data Source\n *                       |\n *                       | <- XStorage._refreshFilteredDataSource()\n *                       |\n *                    Data Source\n *                       |\n *                       | <- XStorage._refreshDataSource()\n *                       |\n * CookieStorage | SessionStorage | LocalStorage\n *    |\n *    | <- CookieStorage.refresh()\n *    |\n * cookie\n */\nclass XStorage {\n  // 分页大小\n  static get PAGE_SIZE() {\n    return 20;\n  }\n\n  constructor(type) {\n    if (type === \"localStorage\" || type === \"sessionStorage\") {\n      this._storage = window[type];\n    } else if (type === \"cookieStorage\") {\n      this._storage = new CookieStorage();\n    } else {\n      logger.error(\"unsupport storage type:\", type);\n    }\n\n    // data source\n    this._ds = [];\n    // filterd data source\n    this._fds = [];\n    // filter keyword\n    this._filter = \"\";\n    // 下次加载的开始位置\n    this._cursor = 0;\n\n    this.refresh();\n  }\n\n  setFilter(filter) {\n    const isChange = filter !== this._filter;\n\n    if (typeof filter === \"string\" && filter) {\n      this._filter = filter;\n    } else {\n      this._filter = \"\";\n    }\n\n    if (isChange) {\n      this._refreshFilteredDataSource();\n    }\n  }\n\n  refresh() {\n    // 在 XStorage 和 cookie 之间增加了一层 CookieStorage\n    // 当 cookie 发生变化后需要手动刷新 CookieStorage\n    if (this._storage instanceof CookieStorage) {\n      this._storage.refresh();\n    }\n\n    this._cursor = 0;\n    this._refreshDataSource();\n  }\n\n  // 从指定位置开始，加载一页数据\n  loadItems(startIndex, pageSize = XStorage.PAGE_SIZE) {\n    return cloneDeep(this._fds.slice(startIndex, startIndex + pageSize));\n  }\n\n  // 从上一次加载结束位置开始，加载一页数据\n  loadMoreItems() {\n    logger.log(\"loadMoreItems\");\n    const items = this.loadItems(this._cursor);\n    this._cursor += items.length;\n    return items;\n  }\n\n  hasMore() {\n    return this._cursor < this._fds.length;\n  }\n\n  get length() {\n    return this._fds.length;\n  }\n\n  getItem(key) {\n    return this._storage.getItem(key);\n  }\n\n  setItem(key, value) {\n    this._storage.setItem(key, value);\n    /**\n     * 更新存在两种情况:\n     * 1) 更新已存在项，这种情况不需要刷新数据源，只要更新数据源中指定项即可\n     * 2) 新增项，这种情况必须刷新数据源，保证读出的数据源列表保持自然顺序\n     *\n     * 针对第 2 中情况进行示例说明，假设分页大小为 2 并且 localStorage 是倒序读取（storage 读取顺序是浏览器决定的，这里假设这种读取方式）\n     * data source      UI data     action\n     * [A, B, C, D]     [D, C]      修改 D -> E\n     * {E, A, B, C}     [E, C]      加载下一页\n     * {E, A, B, C}     [E, C, A, E]\n     * 由于修改后导致读取顺序变化，分页加载的数据不再准确\n     */\n    const item = this._ds.find(item => item.key === key);\n    if (item) {\n      // 更新已存在项\n      item.value = value;\n      const item2 = this._fds.find(item => item.key === key);\n      if (item2) {\n        item2.value = value;\n      }\n    } else {\n      // 新增项\n      this._refreshDataSource();\n    }\n  }\n\n  removeItem(key) {\n    this._storage.removeItem(key);\n\n    /**\n     * 为了提高性能并保留已读取出来的数据，不直接刷新数据源，而是从数据源中删除指定项\n     * 删除不会影响已读取数据源中数据的排列顺序\n     */\n    const foundIndex = this._ds.findIndex(item => item.key === key);\n    if (foundIndex !== -1) {\n      this._ds.splice(foundIndex, 1);\n      const foundIndex2 = this._fds.findIndex(item => item.key === key);\n      if (foundIndex2 !== -1) {\n        this._fds.splice(foundIndex2, 1);\n      }\n      // 当前游标前移 1 格\n      this._cursor -= 1;\n    }\n    // logger.log('removeItem', this)\n  }\n\n  clear() {\n    this._storage.clear();\n    this._ds = [];\n    this._fds = [];\n    this._cursor = 0;\n  }\n\n  // 更新数据源\n  _refreshDataSource() {\n    logger.log(\"_refreshDataSource\", this);\n    let key, value;\n    const storage = this._storage;\n    const length = this._storage.length;\n    this._ds = [];\n    for (let j = 0; j < length; ++j) {\n      key = storage.key(j);\n      value = storage.getItem(key);\n      this._ds.push({ key, value });\n    }\n    this._refreshFilteredDataSource();\n  }\n\n  // 更新过滤数据源\n  _refreshFilteredDataSource() {\n    logger.log(\"_refreshFilteredDataSource\", this);\n    const filter = this._filter;\n    const ds = this._ds;\n    this._fds = [];\n    if (filter) {\n      // 过滤出 key 或 value 中包含关键字的项\n      this._fds = ds.filter(({ key, value }) => key.indexOf(filter) >= 0 || value.indexOf(filter) >= 0);\n    } else {\n      this._fds = ds;\n    }\n  }\n}\n\nwindow.CookieStorage = CookieStorage;\nwindow.XStorage = XStorage;\nexport default XStorage;\n"
  },
  {
    "path": "src/panels/console/ConsolePanel.vue",
    "content": "<template>\n  <div class=\"console-panel\">\n    <div class=\"head\">\n      <VTabBar v-model=\"activeType\" :equalWidth=\"true\">\n        <VTabBarItem id=\"all\">All</VTabBarItem>\n        <VTabBarItem id=\"log\">Log</VTabBarItem>\n        <VTabBarItem id=\"info\">Info</VTabBarItem>\n        <VTabBarItem id=\"warn\">Warn</VTabBarItem>\n        <VTabBarItem id=\"error\">Error</VTabBarItem>\n        <VIcon slot=\"icons\" :name=\"isToolbarExpanded ? 'collapse' : 'expand'\" class=\"head__icon\" @click=\"isToolbarExpanded = !isToolbarExpanded\" />\n      </VTabBar>\n      <div v-if=\"isToolbarExpanded\" class=\"toolbar\">\n        <input class=\"toolbar__input\" type=\"text\" placeholder=\"Filter\" v-model=\"filter\" />\n      </div>\n    </div>\n\n    <div ref=\"container\" @scroll=\"onScroll\" class=\"body\" v-prevent-bkg-scroll>\n      <!-- 不同面板的切换比较频繁，v-show 比 v-if 更适合该场景，可以复用 Message 组件 -->\n      <Message\n        v-for=\"msg in msgList\"\n        :key=\"msg.id\"\n        v-show=\"msg.type === activeType || activeType === 'all'\"\n        :message=\"msg\"\n        :showTimestamps=\"showTimestamps\"\n        :filter=\"filter\"\n      />\n    </div>\n    <VFootBar class=\"foot\" :buttons=\"footBarButtons\" />\n  </div>\n</template>\n\n<script>\nimport { VTabBar, VTabBarItem, VFootBar, VIcon } from \"@/components\";\nimport { uuid, createStack, eventBus, TaskScheduler, Logger, consoleHooks, formatFileName } from \"@/utils\";\nimport Message from \"./Message\";\nimport { isFunction } from \"util\";\n\nconst logger = new Logger(\"[ConsolePanel]\");\nconst taskScheduler = new TaskScheduler();\n\nexport default {\n  name: \"ConsolePanel\",\n  components: {\n    Message,\n    VFootBar,\n    VTabBar,\n    VTabBarItem,\n    VIcon\n  },\n  data() {\n    return {\n      activeType: \"all\",\n      /**\n       * [{\n       *  // 日志类型，取值 all/log/info/error/warn/debug\n       *  type: String,\n       *  // log 的参数数组\n       *  logArgs: Array\n       * }]\n       */\n      msgList: [],\n      /**\n       * 是否滚动触底\n       * 为 true 时，新增消息时滚动条将自动定位到底部\n       * 为 false 时，新增消息时滚动条保持位置不变\n       */\n      isBottom: true,\n      // 显示时间戳\n      showTimestamps: false,\n      // 最大消息数量（超出后移除旧消息）\n      maxMsgCount: Infinity,\n      // 是否展开工具栏\n      isToolbarExpanded: false,\n      // 过滤器\n      filter: \"\"\n    };\n  },\n  computed: {\n    /* eslint-disable */\n    footBarButtons() {\n      return [\n        {\n          text: \"Clear\",\n          click: () => {\n            this.msgList = [];\n          }\n        },\n        {\n          text: \"Hide\",\n          click: () => {\n            eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE);\n          }\n        }\n      ];\n    }\n    /* eslint-enable */\n  },\n  watch: {\n    msgList() {\n      // 等列表渲染完成后(触发 onScroll 更新 isBottom)，再根据最新的 isBottom 决定是否需要滚动到底部\n      this.$nextTick(() => {\n        const el = this.$refs.container;\n        // logger.log('msgList changed, isBottom:', this.isBottom)\n        if (this.isBottom && el && isFunction(HTMLElement.prototype.scrollTo)) {\n          // Element 的 scrollTo 还处于工作草案，需判断兼容性\n          // 在合适的时机滚动至底部，避免阻塞交互\n          el.scrollTo(0, el.scrollHeight - el.clientHeight);\n        }\n      });\n    }\n  },\n  created() {\n    // 搜集 web-console 初始化之前用户输出的日志消息，放入消息队列中\n    this.collectUnhandledMessage();\n\n    // 拦截 Console 对象的日志打印方法，将消息放入消息队列中，并启动日志输出\n    this.interceptConsole();\n\n    // 处理全局未捕获的异常\n    this.handleUncaughtException();\n\n    // 弹窗不可见时，新增数据不会滚动，当弹窗变为可见时，需要执行一次滚动至底部来修正滚动位置\n    // TODO: 不是监听弹窗可见，而是监听当前面板可见\n    eventBus.on(eventBus.POPUP_VISIBILITY_CHANGE, () => {\n      const el = this.$refs.container;\n      if (this.isBottom && el && isFunction(HTMLElement.prototype.scrollTo)) {\n        // Element 的 scrollTo 还处于工作草案，需判断兼容性\n        // 待新增的消息渲染完成后，滚动至底部\n        el.scrollTo(0, el.scrollHeight - el.clientHeight);\n      }\n    });\n\n    // 监听设置变化事件\n    const bindOnSettingsChanged = this.onSettingsChanged.bind(this);\n    eventBus.on(eventBus.SETTINGS_LOADED, bindOnSettingsChanged);\n    eventBus.on(eventBus.SETTINGS_CHANGE, bindOnSettingsChanged);\n  },\n  errorCaptured(error) {\n    // 在浏览器控制台输出错误原因\n    logger.error(error);\n    return false;\n  },\n  methods: {\n    onScroll(event) {\n      const el = event.target;\n      /**\n       * 这里使用 el.scrollHeight - 1 作为右侧判断条件，是因为 v-prevent-bkg-scroll 指令内部\n       * 在触底时会强制将滚动内容上移 1px 以避免触底后滚动弹窗底部背景层\n       */\n      this.isBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;\n      // logger.log('scrollTop + clientHeight: %s, scrollHeight: %s, isBottom: %s', el.scrollTop + el.clientHeight, el.scrollHeight, this.isBottom)\n    },\n    onSettingsChanged(settings) {\n      this.showTimestamps = !!settings.showTimestamps;\n      this.maxMsgCount = settings.maxMsgCount;\n    },\n    appendMessage(type, ...args) {\n      // 冻结消息对象，避免传递过程中 Vue 添加额外字段\n      const msg = Object.freeze({\n        id: uuid(),\n        type: type,\n        // 时间戳取生成消息的时间\n        timestamps: Date.now(),\n        logArgs: args\n      });\n\n      // 1. 性能优化：短时间内批量打印日志时，将打印操作放入队列，之后按顺序依次打印\n      // 2. 修复异常：渲染模板中抛出异常时，打印错误消息，走到这里会再次出发渲染，导致死循环，\n      //             采用任务队列后，相邻两次打印不在同一个执行堆栈中，可以避免这种情况\n      taskScheduler.addAndStart(() => {\n        // 移除超出的消息\n        while (this.msgList.length >= this.maxMsgCount) this.msgList.shift();\n\n        this.msgList.push(msg);\n      });\n    },\n    collectUnhandledMessage() {\n      // 停止搜集日志，交给 ConsolePanel 进行搜集\n      consoleHooks.uninstall();\n      // 将创建之前搜集到的日志按打印顺序追加到日志列表前面\n      // 如果超出最大消息数量，则截取最新的那部分\n      const msgList = consoleHooks.getMsgList();\n      if (msgList.length > this.maxMsgCount) {\n        this.msgList.unshift(...msgList.slice(msgList.length - this.maxMsgCount));\n        logger.warn(\n          \"当前消息数量(%d)超出最大消息数量(%d)，截断后消息数量(%d)\",\n          msgList.length,\n          this.maxMsgCount,\n          this.msgList.length\n        );\n      } else {\n        this.msgList.unshift(...msgList);\n      }\n    },\n    // 拦截 Console 日志方法\n    interceptConsole() {\n      const vm = this;\n      const originConsole = {};\n      const names = [\"log\", \"info\", \"error\", \"warn\", \"debug\"];\n      names.forEach(name => {\n        originConsole[name] = window.console[name];\n\n        window.console[name] = function(...args) {\n          // 如果参数中有 Error 对象，为其生成堆栈信息\n          args.forEach(value => {\n            if (value instanceof Error) {\n              createStack(value, window.console[name]);\n            }\n          });\n\n          vm.appendMessage(name, ...args);\n\n          originConsole[name].apply(this, args);\n        };\n      });\n    },\n    // 处理全局未捕获的异常\n    handleUncaughtException() {\n      const vm = this;\n      // 未捕获的错误\n      window.addEventListener(\"error\", function uncaughtExceptionHandler(event) {\n        let error = event.error;\n        // createStack(error, uncaughtExceptionHandler);\n\n        // if (!error.stack) {\n        //   error.stack = event.message + \"\\nat \" + event.filename + \":\" + event.lineno + \":\" + event.colno;\n        // }\n        error.stack = formatFileName(error.stack);\n        vm.appendMessage(\"error\", error);\n      });\n\n      // 未处理的 rejected Promise\n      window.addEventListener(\"unhandledrejection\", function unhandledRejectionHandler(event) {\n        const error = new Error(\"(in promise) \" + event.reason);\n        createStack(error, unhandledRejectionHandler);\n        vm.appendMessage(\"error\", error);\n      });\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n@import \"../../styles/mixins\";\n\n.console-panel {\n  height: $panel-height;\n  display: flex;\n  flex-direction: column;\n  .head {\n    &__icon {\n      padding: 0.6em;\n      width: 2.4em;\n    }\n    .toolbar {\n      display: flex;\n      flex-direction: row;\n      align-items: center;\n      background-color: $toolbar-bg-color;\n      border-bottom: 1px solid $toolbar-border-color;\n      height: 2.4em;\n      &__input {\n        @include input(80%);\n        margin: 0 4px;\n        padding: 0 4px;\n        flex: 1;\n      }\n    }\n  }\n  .body {\n    flex-grow: 1;\n    overflow-y: auto;\n    overflow-x: hidden;\n    -webkit-overflow-scrolling: touch;\n  }\n  .foot {\n    flex: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/console/Message.vue",
    "content": "<template>\r\n  <div v-if=\"isMatched\" class=\"message source-code g-hide-scrollbar\" :class=\"[message.type]\">\r\n    <div v-if=\"showTimestamps\" class=\"timestamps\">{{formattedTime}}</div>\r\n\r\n    <template v-if=\"!isErrorCaptured\">\r\n      <span v-for=\"(argInfo, index) in argInfoList\" :key=\"index\" class=\"block-wrapper\">\r\n        <text-block\r\n          :descriptor=\"{value: argInfo.displayValue}\"\r\n          :showRootValueDetail=\"argInfo.showValueDetail\"\r\n          :key=\"index + '1'\"\r\n          :style=\"argInfo.style\"\r\n        />\r\n      </span>\r\n    </template>\r\n    <div v-else class=\"error\">\r\n      Message 组件内部错误\r\n    </div>\r\n  </div>\r\n</template>\r\n\r\n<script>\r\nimport { isString, isObject, isArray, Logger, isFunction, flatMap } from \"@/utils\";\r\nimport TextBlock from \"./TextBlock\";\r\n\r\nconst logger = new Logger(\"[Message]\");\r\nexport default {\r\n  components: {\r\n    TextBlock\r\n  },\r\n  props: {\r\n    /**\r\n     * 日志消息\r\n     * {\r\n     *  // 唯一编号\r\n     *  id: String,\r\n     *  // 日志类型 'log', 'info', 'error', 'warn', 'debug'\r\n     *  type: String,\r\n     *  // 时间戳\r\n     *  timestamps: Number,\r\n     *  // 日志接口参数列表\r\n     *  logArgs: Array\r\n     * }\r\n     */\r\n    message: {\r\n      type: Object,\r\n      required: true\r\n    },\r\n    // 是否显示时间戳\r\n    showTimestamps: {\r\n      type: Boolean,\r\n      default: false\r\n    },\r\n    // 过滤器\r\n    filter: {\r\n      type: String,\r\n      default: \"\"\r\n    }\r\n  },\r\n  data() {\r\n    return {\r\n      isErrorCaptured: false\r\n    };\r\n  },\r\n  computed: {\r\n    space() {\r\n      return \" \";\r\n    },\r\n    formattedTime() {\r\n      let date = new Date(this.message.timestamps);\r\n      const hours = date.getHours();\r\n      const minutes = date.getMinutes();\r\n      const seconds = date.getSeconds();\r\n      const millisconeds = date.getMilliseconds();\r\n      return (\r\n        (hours < 10 ? \"0\" + hours : hours) +\r\n        \":\" +\r\n        (minutes < 10 ? \"0\" + minutes : minutes) +\r\n        \":\" +\r\n        (seconds < 10 ? \"0\" + seconds : seconds) +\r\n        \".\" +\r\n        (millisconeds < 10 ? \"00\" + millisconeds : millisconeds < 100 ? \"0\" + millisconeds : millisconeds)\r\n      );\r\n    },\r\n    argInfoList() {\r\n      // 对 log 参数进行格式化，将占位符替换成对应值\r\n      let argInfoList = parseLogArgs(this.message.logArgs);\r\n      // logger.log('formattedLogArgs', formattedLogArgs)\r\n\r\n      // 将参数信息对象进一步处理，得到一些与 UI 展示相关的信息\r\n      // 返回数据格式为\r\n      // {value, placholder, displayValue, showValueDetail}\r\n      argInfoList = formatLogArgsForDisplay(argInfoList);\r\n\r\n      return argInfoList;\r\n    },\r\n    // 是否匹配过滤器\r\n    isMatched() {\r\n      return !this.filter || this.argInfoList.some(value => match(value.displayValue, this.filter));\r\n    }\r\n  },\r\n  // 捕获 console API 引发的错误，避免陷入循环渲染\r\n  errorCaptured(error) {\r\n    // 在浏览器控制台输出错误原因\r\n    logger.error(error);\r\n    // UI 上展示出错提示\r\n    this.isErrorCaptured = true;\r\n    return false;\r\n  }\r\n};\r\n\r\n/**\r\n * 如果 value 包含 filter 所表示的关键字，则返回 true，否则返回 false\r\n * @param {any} value 内容\r\n * @param {String} filter 过滤关键字\r\n * @param {Number} deepth 匹配深度\r\n * @returns {boolean}\r\n */\r\nfunction match(value, filter, deepth = Infinity) {\r\n  if (deepth < 0) return false;\r\n\r\n  if (isArray(value)) {\r\n    return value.some(el => match(el, filter, deepth - 1));\r\n  } else if (isObject(value)) {\r\n    return Object.keys(value).some(key => match(key, filter, deepth - 1) || match(value[key], filter, deepth - 1));\r\n  } else if (isFunction(value)) {\r\n    return value.name.indexOf(filter) !== -1;\r\n  } else {\r\n    return String(value).indexOf(filter) !== -1;\r\n  }\r\n}\r\n\r\n/**\r\n * 解析日志方法接收到的的参数列表，返回包含占位符描述对象的数组\r\n *\r\n * console.log 方法接收的第一个参数如果是字符串，可以通过特殊占位符来格式化输出结果。\r\n * 例如下面例子，将格式串按占位符进行分割，每个部分都用一个占位符描述对象表示\r\n * format(['a%sc%se', '1', '2', '3', '4'])\r\n * // 返回一个占位符描述对象组成的数组，value 表示对应的值，placeholder 表示对应的占位符\r\n * // 如果属于格式串但不是占位符，则 value 是自身；\r\n * // 如果不属于格式串，则 value 是自身，同时前面插入一个空白符（作为显示时的分隔符)\r\n * [\r\n *  {value: 'a'},\r\n *  {value: '1', placeholder: '%s'},\r\n *  {value: 'c'},\r\n *  {value: '2', placeholder: '%s'},\r\n *  {value: 'e'},\r\n *  {value: '3'},\r\n *  {value: ' '},\r\n *  {value: '4'},\r\n * ]\r\n *\r\n * 算法步骤：\r\n * 对于 console.log 参数数组 ['a%sb%sc', '1', '2']\r\n * 1. 找到第一个参数中的第一个占位符，并以此将第一个参数分割成三部分：占位符之前部分('a'), 占位符('%s'), 占位符之后部分('b%sc')\r\n * 2. 出栈剩余参数替换占位符内容\r\n * 3. 将占位符之后的部分以及剩余参数再次进行第 1 步 的处理，直到没有剩余参数为止\r\n *\r\n * @param {Array} logArgs 参数列表，第一个参数可以包含占位符\r\n * @param {Number} startPos 第一个参数中查找占位符的起始位置\r\n * @returns {Array<Object>} 返回包含占位符描述对象的数组\r\n */\r\nfunction parseLogArgs(logArgs) {\r\n  if (!Array.isArray(logArgs)) {\r\n    return [{ value: logArgs }];\r\n  }\r\n\r\n  if (logArgs.length === 0) {\r\n    return [];\r\n  }\r\n\r\n  const [arg0, ...restArgs] = logArgs;\r\n  // 第一个参数非字符串，或没有剩余参数，不需要继续处理\r\n  if (!isString(arg0) || restArgs.length === 0) {\r\n    // 参数之前添加空白符进行分割\r\n    return flatMap(logArgs, arg => [{ value: arg }, { value: \" \" }]);\r\n  }\r\n\r\n  const matcher = /%%|%s|%o|%O|%i|%d|%f|%c/;\r\n  // 从上次处理结束位置开始匹配\r\n  const result = matcher.exec(arg0);\r\n  if (!result) {\r\n    // 没有占位符，不处理\r\n    // 参数之前添加空白符进行分割\r\n    return flatMap(logArgs, arg => [{ value: arg }, { value: \" \" }]);\r\n  }\r\n  const placeholder = result[0];\r\n  const placeholderIndex = result.index; // 加上偏移量\r\n\r\n  let part1 = null;\r\n  let part2 = null;\r\n  let part3 = null;\r\n  if (placeholderIndex > 0) {\r\n    part1 = { value: arg0.substring(0, placeholderIndex) };\r\n  }\r\n  if (placeholder === \"%%\") {\r\n    part2 = { value: \"%\", placeholder };\r\n  } else {\r\n    part2 = { value: restArgs.shift(), placeholder };\r\n  }\r\n  if (placeholderIndex + 2 <= arg0.length - 1) {\r\n    part3 = { value: arg0.substring(placeholderIndex + 2) };\r\n  }\r\n\r\n  let formattedLogArgs = [];\r\n  if (part1) {\r\n    formattedLogArgs.push(part1);\r\n  }\r\n  if (part2) {\r\n    formattedLogArgs.push(part2);\r\n  }\r\n  if (part3) {\r\n    formattedLogArgs.push(...parseLogArgs([part3.value, ...restArgs]));\r\n  }\r\n\r\n  return formattedLogArgs;\r\n}\r\n\r\n/**\r\n * 格式化日志参数描述对象列表，增加控制显示的相关标志位\r\n *\r\n * 例如同样是 '%s' 占位符，数值和对象的展示是不一样的。再例如 Error 对象和非 Error 对象展示的内容是不同的，\r\n * 前者展示堆栈信息，后者展示对象的属性树。该函数就是为了处理这种有不同展示需求的情况。\r\n *\r\n * {\r\n *  value,            // 原始值\r\n *  placholder,       // 占位符\r\n *  displayValue,     // 展示值\r\n *  showValueDetail,  // 是否展开细节\r\n *  style             // 样式\r\n * }\r\n *\r\n * 例如 console.log('a%sc%s', 'b', {}) 的参数列表解析后如下：\r\n * [\r\n *  {value: 'a'}\r\n *  {value: 'b', placeholder: '%s'}\r\n *  {value: 'c'}\r\n *  {value: {}, placeholder: '%s'}\r\n * ]\r\n * 将上面描述对象数组进一步处理得到（还有一个合并相邻字符串的处理，此处省略）：\r\n * [\r\n *  {value: 'a', displayValue: 'a', placeholder: ''}\r\n *  {value: 'b', displayValue: 'b', placeholder: '%s'}\r\n *  {value: 'c', displayValue: 'c', placeholder: ''}\r\n *  {value: {}, displayValue: 'Object', placeholder: '%s'}\r\n * ]\r\n *\r\n * 观察发现：\r\n * 1）存在占位符时，value 和 displayValue 可能不同，取决于希望如何展示\r\n * 2）没有占位符时，value 和 displayValue 必然相同\r\n *\r\n * @returns {Array<Object>} 返回包含占位符描述对象的数组\r\n */\r\nfunction formatLogArgsForDisplay(argInfoList) {\r\n  // 将参数信息对象进一步处理，得到一些与 UI 展示相关的信息\r\n  // 返回数据格式为\r\n  // {value, placholder, displayValue, showValueDetail}\r\n  const argInfoList2 = argInfoList.map(argInfo => {\r\n    switch (argInfo.placeholder) {\r\n      case \"%%\":\r\n        argInfo.displayValue = \"%\";\r\n        break;\r\n      case \"%s\":\r\n        if (isArray(argInfo.value)) {\r\n          argInfo.displayValue = `Array(${argInfo.value.length})`;\r\n        } else if (isObject(argInfo.value)) {\r\n          argInfo.displayValue = \"Object\";\r\n        } else {\r\n          argInfo.displayValue = String(argInfo.value);\r\n        }\r\n        break;\r\n      case \"%i\": // 同下\r\n      case \"%d\":\r\n        argInfo.displayValue = String(parseInt(argInfo.value));\r\n        break;\r\n      case \"%f\":\r\n        argInfo.displayValue = String(parseFloat(argInfo.value));\r\n        break;\r\n      case \"%o\":\r\n        argInfo.displayValue = argInfo.value;\r\n        argInfo.showValueDetail = true;\r\n        break;\r\n      case \"%O\":\r\n        argInfo.displayValue = argInfo.value;\r\n        argInfo.showValueDetail = false;\r\n        break;\r\n      case \"%c\":\r\n        // %c 占位符对应的样式作用于其后开始的第一个元素直至下一个 %c 占位符或结束位置之前中间的所有元素（%O 和 %o 除外）\r\n        // 举例：console.log('%c A B %c C', 'color: red', 'color: blue')  // A B 是 red， C 是 blue\r\n        argInfo.displayValue = \"\";\r\n        break;\r\n      default:\r\n        argInfo.placeholder = \" \"; // 没有placehodler的使用' '标记\r\n        argInfo.displayValue = argInfo.value;\r\n        break;\r\n    }\r\n\r\n    // Error 对象和普通对象不同，它不展示对象内部属性结构，而是展示堆栈信息\r\n    if (argInfo.displayValue instanceof Error) {\r\n      const err = argInfo.displayValue;\r\n      argInfo.displayValue = (err.stack || err).toString();\r\n    }\r\n\r\n    return argInfo;\r\n  });\r\n  // logger.log('argInfoList2', cloneDeep(argInfoList2))\r\n\r\n  // 合并相邻字符串（%c 占位符除外），减少文本块的数量，避免文本块之间的默认空白符\r\n  // 例如 {displayValue: 'a'} 和 {displayValue: 'b'} 将合并成一个 {displayValue: 'ab'}\r\n  const argInfoList3 = [];\r\n  while (argInfoList2.length > 0) {\r\n    const curArgInfo = argInfoList2.shift();\r\n    if (argInfoList3.length === 0) {\r\n      argInfoList3.push(curArgInfo);\r\n    } else {\r\n      const prevArgInfo = argInfoList3[argInfoList3.length - 1];\r\n      if (\r\n        isString(prevArgInfo.displayValue) &&\r\n        isString(curArgInfo.displayValue) &&\r\n        prevArgInfo.placeholder !== \"%c\" &&\r\n        curArgInfo.placeholder !== \"%c\"\r\n      ) {\r\n        prevArgInfo.displayValue += curArgInfo.displayValue;\r\n        prevArgInfo.placeholder += curArgInfo.placeholder;\r\n      } else {\r\n        argInfoList3.push(curArgInfo);\r\n      }\r\n    }\r\n  }\r\n  // logger.log('argInfoList3', cloneDeep(argInfoList3))\r\n\r\n  // 处理 %c 占位符样式，将 %c 的样式复制到其后元素，直至遇到下一个 %c 或格式串结尾才终止\r\n  let lastStyle = \"\";\r\n  argInfoList3.forEach(argInfo => {\r\n    if (argInfo.placeholder === \"%c\") {\r\n      lastStyle = argInfo.value;\r\n    } else {\r\n      if (lastStyle && !/%O|%o/.test(argInfo.placeholder)) {\r\n        argInfo.style = lastStyle;\r\n      }\r\n    }\r\n  });\r\n  const argInfoList4 = argInfoList3.filter(argInfo => argInfo.placeholder !== \"%c\");\r\n  // logger.log('argInfoList4', cloneDeep(argInfoList4))\r\n\r\n  return argInfoList4;\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@import \"../../styles/mixins\";\r\n.message {\r\n  display: flex;\r\n  flex-direction: row;\r\n  flex-wrap: wrap;\r\n  padding: 8px 4px;\r\n  overflow-y: hidden;\r\n  overflow-x: auto;\r\n  &.log,\r\n  &.debug,\r\n  &.info {\r\n    border-bottom: 1px solid rgb(240, 240, 240);\r\n  }\r\n  &.error {\r\n    @include descendant-attr(\"color\", red);\r\n    border-top: 1px solid hsl(0, 100%, 92%);\r\n    border-bottom: 1px solid hsl(0, 100%, 92%);\r\n    margin-top: -1px;\r\n    background-color: hsl(0, 100%, 97%);\r\n  }\r\n  &.warn {\r\n    @include descendant-attr(\"color\", hsl(39, 100%, 18%));\r\n    border-top: 1px solid hsl(50, 100%, 88%);\r\n    border-bottom: 1px solid hsl(50, 100%, 88%);\r\n    margin-top: -1px;\r\n    background-color: hsl(50, 100%, 95%);\r\n  }\r\n\r\n  .timestamps {\r\n    color: gray;\r\n    margin-right: 5px;\r\n    line-height: 20px;\r\n  }\r\n  .block-wrapper {\r\n    display: inline-flex;\r\n    flex-direction: row;\r\n    align-items: flex-start;\r\n  }\r\n}\r\n\r\n.space {\r\n  white-space: pre;\r\n}\r\n</style>\r\n"
  },
  {
    "path": "src/panels/console/TextBlock.vue",
    "content": "<template>\n  <div class=\"text-block\">\n    <!-- name/value -->\n    <!-- @click.stop 避免事件冒泡到上一级 TextBlock -->\n    <div class=\"prop\" @click.stop=\"isFold = !isFold\">\n      <!-- 折叠展开符仅当缩进量大于0，或者传入值是对象且属性数量大于0时可见-->\n      <div v-if=\"deepth > 0 || properties.length > 0\" class=\"indent\" :style=\"indentStyle\">\n        <div class=\"triangles\" :class=\"arrowClass\"></div>\n      </div>\n      <!-- 属性名 -->\n      <template v-if=\"hasName\">\n        <span class=\"key\" :class=\"[descriptor.enumerable ? 'public' : 'private']\">{{displayName}}</span>\n        <span class=\"space\">: </span>\n      </template>\n      <!-- 属性值 -->\n      <!-- getter 访问器点击时才计算结果，而且只计算一次，且计算结果值始终不展示详情 -->\n      <template v-if=\"isGetAccessor\">\n        <text-inline-block\n          v-if=\"hasComputed\"\n          :name=\"name\"\n          :value=\"computedValue\"\n          :showValueDetail=\"false\"\n        />\n        <span v-else @click.stop=\"onClickGetAccessor\">(...)</span>\n      </template>\n      <!-- value 则直接展示 -->\n      <!-- showValueDetail 说明：-->\n      <!-- a)如果是根元素，则由 showRootValueDetail 决定是否显示详情 -->\n      <!-- b)否则，如果是非'__proto__'属性且处于折叠态时，也显示详情 -->\n      <!-- c)其他情形不展示详情 -->\n      <!-- 如果是根元素, 并且是对象类型, 则展示详情页时以斜体展示 -->\n      <text-inline-block\n        v-else\n        :name=\"name\"\n        :value=\"descriptor.value\"\n        :showValueDetail=\"isRoot ? showRootValueDetail : (name !== '__proto__' && isFold)\"\n        :class=\"textInlineBlockStyle\"\n      />\n    </div>\n    <!-- 子节点 -->\n    <div v-for=\"(item, index) in properties\" :key=\"String(item.name) + index\" v-if=\"!isFold\">\n      <text-block\n        :name=\"item.name\"\n        :descriptor=\"item.descriptor\"\n        :needComputeProps=\"!isFold\"\n        :deepth=\"deepth + 1\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\n/**\n * 高亮显示传入值，如果值是对象，以树状结构展示，可以折叠展开\n *\n * 例如，传入值为 obj\n * obj = {\n *  a: 1,\n *  b: 2\n * }\n * UI 展示成：\n * {\n *  a: 1,\n *  b: 2\n * }\n *\n * 观察 chrome devtools 的输出，有几个特点：\n * 1）...\n *\n * 使用示例：\n * // 展示值\n * <text-block :descriptor=\"{value: 1}\" />\n * // 展示键和值\n * <text-block :name=\"age\" :descriptor=\"{value: 1}\" />\n */\nimport { isString, isObject, flatMap, Logger, isFunction } from \"@/utils\";\nimport TextInlineBlock from \"./TextInlineBlock\";\n\nconst logger = new Logger(\"[TextBlock]\");\nexport default {\n  name: \"text-block\",\n  components: {\n    TextInlineBlock\n  },\n  props: {\n    // 属性描述符，结构同 Object.defineProperty() 的第三个参数\n    descriptor: {\n      type: Object,\n      required: true,\n      validator: descriptor => {\n        try {\n          Object.defineProperty({}, \"key\", descriptor);\n        } catch (error) {\n          logger.error(error.message, \"descriptor:\", descriptor);\n        }\n        // ConsolePanel 内部组件不能再次抛出错误，否则会造成循环错误\n        return true;\n      }\n    },\n    // 属性名（可选）\n    name: [String, Symbol],\n    // 是否需要立即计算对象属性信息\n    needComputeProps: {\n      type: Boolean,\n      default: true\n    },\n    // 嵌套深度\n    // obj = {a: 1, b: {c: 2}}\n    // obj 深度为 0\n    // obj.b 深度为 1\n    // obj.b.c 深度为 2\n    deepth: {\n      type: Number,\n      default: 0\n    },\n    // 根节点是否展示值的详情（仅对根元素有效，对子元素无效）\n    // 为 true 时，对象展示成 {...}，数组展示成 [...]\n    // 位 false 时，对象展示成 Object，数组展示成 Array(n)\n    showRootValueDetail: {\n      type: Boolean,\n      default: true\n    }\n  },\n  data() {\n    return {\n      // 是否折叠属性树\n      isFold: true,\n      // 当前 get 访问器是否已计算（仅当当前 descriptor 含 get 访问器时有效）\n      hasComputed: false,\n      // 当前 get 访问器计算结果（仅当当前 descriptor 含 get 访问器时有效）\n      computedValue: \"(...)\"\n    };\n  },\n  computed: {\n    isRoot() {\n      return !this.name;\n    },\n    hasName() {\n      return !!this.name;\n    },\n    /**\n     * 对象 key 的展示值\n     * 考虑 key 为 Symbol 类型或包含特殊字符（如空白符）时的处理\n     */\n    displayName() {\n      let _name = String(this.name);\n      if (_name.indexOf(\" \") === 0) {\n        // 多个空白符压缩成一个，并加引号区分前后的空白符\n        return '\"' + _name.replace(/^\\s+/, \" \") + '\"';\n      } else {\n        return _name;\n      }\n    },\n    isGetAccessor() {\n      return typeof this.descriptor.get === \"function\";\n    },\n    // 当前对象的所有属性相关信息\n    properties() {\n      const defaultReturns = [];\n\n      // 如不需要计算属性可直接返回以提高性能\n      if (!this.needComputeProps) {\n        return defaultReturns;\n      }\n\n      // 获取对象值\n      // 如果是值属性，取 value 值；如果是 getter 访问器，取计算值\n      let value;\n      if (this.isGetAccessor) {\n        if (this.hasComputed) {\n          value = this.computedValue;\n        } else {\n          return defaultReturns;\n        }\n      } else {\n        value = this.descriptor.value;\n      }\n\n      let obj;\n      if (isObject(value)) {\n        obj = value;\n      } else if (isFunction(value)) {\n        obj = value;\n      } else {\n        return defaultReturns;\n      }\n\n      // 获取属性名及其描述符\n      const ownKeys = [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)];\n      const properties = flatMap(ownKeys, name => {\n        const descriptor = Object.getOwnPropertyDescriptor(obj, name);\n        if (!descriptor) {\n          return [];\n        }\n\n        // value and writable 与 get/set 访问器不会同时存在\n        if (isFunction(descriptor.get) && isFunction(descriptor.set)) {\n          // 同时存在 getter 和 setter\n          return [\n            { name: String(name), descriptor },\n            {\n              // Symbol 类型需要进行转换\n              name: `get ${String(name)}`,\n              descriptor: {\n                value: descriptor.get,\n                enumerable: false\n              }\n            },\n            {\n              name: `set ${String(name)}`,\n              descriptor: {\n                value: descriptor.set,\n                enumerable: false\n              }\n            }\n          ];\n        } else if (isFunction(descriptor.get) && !isFunction(descriptor.set)) {\n          // 只存在 getter\n          return [\n            { name, descriptor },\n            {\n              name: `get ${String(name)}`,\n              descriptor: {\n                value: descriptor.get,\n                enumerable: false\n              }\n            }\n          ];\n        } else if (!isFunction(descriptor.get) && isFunction(descriptor.set)) {\n          // 只存在 setter\n          return [\n            {\n              name: `set ${String(name)}`,\n              descriptor: {\n                value: descriptor.set,\n                enumerable: false\n              }\n            }\n          ];\n        } else {\n          // 不存在 getter 或 setter，那就是数据访问器了\n          return [{ name: String(name), descriptor }];\n        }\n\n        // logger.log(name, ':', descriptor)\n      });\n      // TODO: JSON.stringify 不能用于将循环引用的结构 JSON 化，需要进行特殊处理，暂且过滤掉存在循环引用的属性\n      // 参考 https://stackoverflow.com/questions/4816099/chrome-sendrequest-error-typeerror-converting-circular-structure-to-json#\n      // .filter(name => name !== '__ob__')\n\n      // 增加展示'__proto__'属性\n      if (properties.findIndex(v => v.name === \"__proto__\") === -1) {\n        properties.push({\n          name: \"__proto__\",\n          descriptor: {\n            value: obj.__proto__,\n            enumerable: false,\n            configurable: true\n          }\n        });\n      }\n\n      // Object.prototype 不展示'__proto__'属性，而是展示其 get/set 访问器，避免递归\n      if (obj === Object.prototype) {\n        let index = properties.findIndex(v => v.name === \"__proto__\");\n        if (index !== -1) {\n          properties.splice(index, 1);\n          const descriptor = Object.getOwnPropertyDescriptor(obj, \"__proto__\");\n          properties.push(\n            {\n              name: \"get __proto__\",\n              descriptor: {\n                value: descriptor.get,\n                enumerable: false\n              }\n            },\n            {\n              name: \"set __proto__\",\n              descriptor: {\n                value: descriptor.set,\n                enumerable: false\n              }\n            }\n          );\n        }\n      }\n\n      // 属性排序\n      return properties.sort(propCompareFn);\n    },\n    indentStyle() {\n      return {\n        width: this.deepth > 0 ? `${this.deepth}em` : \"auto\"\n      };\n    },\n    // 折叠/展开箭头样式（使用CSS绘制）\n    arrowClass() {\n      return this.properties.length > 0 ? (this.isFold ? \"fold\" : \"unfold\") : \"\";\n    },\n    textInlineBlockStyle() {\n      // 是根节点，且是对象，且需要显示详情时，以斜体展示\n      const b1 = this.isRoot && this.showRootValueDetail && isObject(this.descriptor.value);\n      // 函数类型，以斜体展示\n      const b2 = isFunction(this.descriptor.value);\n      return {\n        italic: b1 || b2\n        // 只有根节点需要换行展示，非根节点不换行方便阅读\n      };\n    }\n  },\n  errorCaptured(error) {\n    // 在浏览器控制台输出错误原因\n    logger.error(error);\n    return false;\n  },\n  methods: {\n    onClickGetAccessor() {\n      if (!this.hasComputed) {\n        // 最多计算一次\n        this.hasComputed = true;\n        try {\n          // 冻结计算结果，避免 Vue 添加额外属性\n          this.computedValue = Object.freeze(this.descriptor.get());\n        } catch (error) {\n          logger.error(error);\n          this.computedValue = \"(error: \" + error.message + \")\";\n        }\n      }\n    }\n  }\n};\n\n/**\n * 获取属性的展示优先级\n * 先按类别划分优先级高低，相同优先级内部再根据字母顺序排序\n * public 属性\n * A          // 0\n * B\n * a\n * b\n * Symbol()   // 10\n * Symbol(a)\n * _A        // 15\n * _B\n *\n * private 属性\n * A         // 20\n * B\n * a\n * b\n * Symbol()  // 30\n * Symbol(a)\n * _A       // 35\n * _B\n * get A     // 40\n * set A\n * get B\n * get a\n * set a\n * get b\n * __proto__ // 100\n */\nfunction getPropDisplayPriority(prop) {\n  let priority = 0;\n  if (isString(prop.name)) {\n    if (prop.descriptor.enumerable) {\n      if (prop.name.indexOf(\"_\") === 0) {\n        priority = 15;\n      } else {\n        priority = 0;\n      }\n    } else {\n      if (prop.name.indexOf(\"get \") === 0 || prop.name.indexOf(\"set \") === 0) {\n        priority = 40;\n      } else if (prop.name.indexOf(\"_\") === 0) {\n        priority = 35;\n      } else {\n        priority = 20;\n      }\n    }\n  } else {\n    // symbol\n    if (prop.descriptor.enumerable) {\n      priority = 10;\n    } else {\n      priority = 30;\n    }\n  }\n  if (prop.name === \"__proto__\") {\n    priority = 100;\n  }\n  return priority;\n}\n\n/**\n * 属性排序比较函数\n */\nfunction propCompareFn(propA, propB) {\n  let priorityA = getPropDisplayPriority(propA);\n  let priorityB = getPropDisplayPriority(propB);\n  // logger.log(propA.name, propB.name, priorityA, priorityB)\n  if (priorityA === priorityB) {\n    // 优先级相同时按字母的 ASCII 码排序，码值越小越靠上\n    // 默认比较字母的 ASCII\n    let a = String(propA.name);\n    let b = String(propB.name);\n    if (!isNaN(parseFloat(a)) && !isNaN(parseFloat(b))) {\n      // a, b 都是数字时，比较数值大小\n      a = parseFloat(a);\n      b = parseFloat(b);\n    }\n    return a < b ? -1 : a > b ? 1 : 0;\n  } else {\n    // 否则，优先级越低越靠上\n    return priorityA - priorityB;\n  }\n}\n</script>\n\n<style scoped lang=\"scss\">\n.public,\n.private {\n  color: rgb(136, 19, 145);\n}\n.private {\n  opacity: 0.6;\n}\n\n.text-block {\n  display: inline-flex;\n  flex-direction: column;\n  line-height: 1.4em;\n  .indent {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: flex-end;\n    padding-right: 3px;\n    flex-shrink: 0;\n  }\n  .triangles {\n    width: 0;\n    height: 0;\n    $border-width: 5px;\n    /* 等边三角形，tan(30) 约为 0.5773502691896257 */\n    &.fold {\n      border-left: $border-width solid #727272;\n      border-top: $border-width * 0.8 solid transparent;\n      border-bottom: $border-width * 0.8 solid transparent;\n    }\n    &.unfold {\n      border-top: $border-width solid #727272;\n      border-left: $border-width * 0.8 solid transparent;\n      border-right: $border-width * 0.8 solid transparent;\n    }\n  }\n  .content {\n    display: flex;\n    flex-direction: column;\n  }\n  .prop {\n    display: flex;\n    flex-direction: row;\n    .key {\n      flex-shrink: 0;\n    }\n  }\n  .space {\n    white-space: pre;\n    flex-shrink: 0;\n  }\n\n  .italic {\n    font-style: italic;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/console/TextInlineBlock.vue",
    "content": "<template>\n  <!-- 函数 -->\n  <span v-if=\"isFunction\" class=\"text-inline-block function-type\">\n    <template v-if=\"deepth === 0\">\n      <span v-if=\"isArrowFunction\">{{value}}</span>\n      <span v-else>\n        <span class=\"function\">ƒ</span><span> {{value.name}}()</span>\n      </span>\n    </template>\n    <template v-else>\n      <span class=\"function\">ƒ</span>\n    </template>\n  </span>\n  <!-- 数组类型 -->\n  <!-- 两种展示 -->\n  <!-- [1, 2, 3] 数组作为根元素时，即 deepth 为 0 时 -->\n  <!-- Array(3) 数组作为嵌套元素或者由外部控制时，即 deepth 大于 0 或 showValueDetail 为 false，如 {a: 1, b: Array(3)}, [1, Array(3)] -->\n  <span v-else-if=\"isArray\" class=\"text-inline-block array-type\">\n    <template v-if=\"showValueDetail\">\n      <template v-if=\"deepth === 0\">\n        <span style=\"color: gray\">({{value.length}})&nbsp;</span>\n        <span>[</span>\n        <!-- 数组可能包含非数字下标，使用 Object.keys 过滤出有效的 keys 进行处理 -->\n        <span v-for=\"(key, i) in Object.keys(value)\" :key=\"i\" v-if=\"i < MAX_DISPLAY_PROP_COUNT\">\n          <text-inline-block\n            :name=\"String(key)\"\n            :value=\"short(value[key])\"\n            :deepth=\"deepth + 1\"\n          />\n          <!-- 关于 \"displayPropertyNames.length - 2\"的解释：数组的 length 属性在内联块中不显示，所以不计数 -->\n          <span v-if=\"i < Math.min(MAX_DISPLAY_PROP_COUNT - 1, displayPropertyNames.length - 2)\">, </span>\n        </span>\n        <span v-if=\"displayPropertyNames.length >= 5\">, ...</span>\n        <span>]</span>\n      </template>\n      <span v-else>Array({{value.length}})</span>\n    </template>\n    <span v-else>Array({{value.length}})</span>\n  </span>\n  <!-- 对象 -->\n  <!-- 三种展示-->\n  <!-- '{a: 1, b: 2}' 当对象作为根元素时，即 deepth 为 0 -->\n  <!-- '{...}' 当对象作为嵌套元素时，即 deepth 大于 0，如 {a: 1, b: {...}} -->\n  <!-- 'Object' 有外部开关控制，即 showValueDetail 为 true，如 {a: 1, __proto__: Object}  -->\n  <span v-else-if=\"isObject\" class=\"text-inline-block object-type\">\n    <!-- 展示对象详情 -->\n    <template v-if=\"showValueDetail\">\n      <span>{</span>\n      <!-- 第1层属性要展示且最多展示 5 个，超过 5 个后使用省略号替代 -->\n      <template v-if=\"deepth === 0\">\n        <span v-for=\"(propName, index) in displayPropertyNames\" :key=\"index\" v-if=\"index < MAX_DISPLAY_PROP_COUNT\">\n          <span>{{propName}}: </span>\n          <text-inline-block\n            :name=\"propName\"\n            :value=\"short(value[propName])\"\n            :deepth=\"deepth + 1\"\n          />\n          <span v-if=\"index < Math.min(MAX_DISPLAY_PROP_COUNT - 1, displayPropertyNames.length - 1)\">, </span>\n        </span>\n        <span v-if=\"displayPropertyNames.length >= 5\">, ...</span>\n      </template>\n      <!-- 超过1层的属性用省略号替代展示 -->\n      <span v-else>...</span>\n      <span>}</span>\n    </template>\n    <!-- 展示对象类型 -->\n    <span v-else>Object</span>\n  </span>\n  <!-- 字符串类型 -->\n  <span v-else-if=\"isString\" class=\"text-inline-block string-type\">\n    <template v-if=\"name\">\n      <span class=\"string-quote\">\"</span>\n      <span class=\"string keep-format\">{{value}}</span>\n      <span class=\"string-quote\">\"</span>\n    </template>\n    <template v-else>\n      <span class=\"keep-format\">{{value}}</span>\n    </template>\n  </span>\n  <!-- 其他类型 -->\n  <span v-else class=\"text-inline-block\" :class=\"valueClass\">{{formattedValue}}</span>\n</template>\n\n<script>\n/**\n * 高亮展示传入的值，如果是对象，在一行内展开\n *\n * 例如，传入值为 obj\n * obj = {\n *  a: 1,\n *  b: 2\n * }\n * UI 展示成：\n * {a: 1, b: 2}\n *\n * 观察 chrome devtools 的输出，有几个特点：\n * 1）最多展示 5 个字段，再多就展示省略号\n * obj = {\n *  a: 1,\n *  b: 2,\n *  c: 3,\n *  d: 4,\n *  e: 5,\n *  f: 6\n * }\n * UI 展示：\n * {a: 1, b: 2, c: 3, d: 4, e: 5, ...}\n *\n * 2）展示数据属性（包含可枚举和不可枚举），但不展示访问器属性\n * obj = {\n *  // 可枚举\n *  a: 1,\n *  // 不可枚举\n *  b: 1,\n *  // get 访问器\n *  c: (...)\n * }\n * UI 展示：\n * {a: 1, b: 1}\n *\n * 3）函数深度为 0 时展示成 ƒ，深度大于 0 时展示函数的字符串\n * obj = {\n *  a: () => {},\n *  b: function () {},\n *  c: function say () {}\n * }\n * 深度为 0 时展示：\n * {a: ƒ, b: ƒ, c: ƒ}\n * 深度大于 0 时展示：\n * {\n *  a: () => {}\n *  b: ƒ (),\n *  c: ƒ say()\n * }\n */\nimport {\n  isNull,\n  isBoolean,\n  isString,\n  isNumber,\n  isObject,\n  isSymbol,\n  isUndefined,\n  isArray,\n  isFunction,\n  Logger\n} from \"@/utils\";\n\nconst logger = new Logger(\"TextInlineBlock\");\nexport default {\n  name: \"text-inline-block\",\n  props: {\n    // 属性名\n    // 属性名为空时，表示内联块作为根元素，展示传入的 value 值，即 <TextInlineBlock/>\n    // 属性名有值时，表示内联块作为对象的属性值，展示 value 值，即 {key: <TextInlineBlock/>}\n    name: [String, Symbol],\n    // 属性值\n    value: {},\n    // 嵌套深度\n    // let obj = {a: {b: 2}}\n    // obj 的 deepth 为 0\n    // obj.a 的 deepth 为 1\n    // obj.a.b 的 deepth 为 2 以此类推\n    deepth: {\n      type: Number,\n      default: 0\n    },\n    // 是否显示对象详情\n    // 为 true 时，显式对象的属性字段信息，例如 {a: 1}, [1, 2] 等\n    // 为 false 时，则仅对象的类型，例如 Object, Array 等\n    showValueDetail: {\n      type: Boolean,\n      default: true\n    }\n  },\n  computed: {\n    isFunction() {\n      return isFunction(this.value);\n    },\n    isArrowFunction() {\n      return isFunction(this.value) && String(this.value).indexOf(\"() =>\") === 0;\n    },\n    isArray() {\n      return isArray(this.value);\n    },\n    isString() {\n      return isString(this.value);\n    },\n    isObject() {\n      return isObject(this.value);\n    },\n    MAX_DISPLAY_PROP_COUNT() {\n      return 5;\n    },\n    displayPropertyNames() {\n      const obj = this.value;\n      return (\n        Object.getOwnPropertyNames(obj)\n          // 过滤出数据属性\n          .filter(name => {\n            const descriptor = Object.getOwnPropertyDescriptor(obj, name);\n            return \"value\" in descriptor;\n          })\n      );\n    },\n    formattedValue() {\n      const value = this.value;\n      if (isNull(value) || isUndefined(value)) {\n        return String(value);\n      }\n      return value;\n    },\n    valueClass() {\n      const value = this.value;\n      return {\n        null: isNull(value),\n        undefined: isUndefined(value),\n        boolean: isBoolean(value),\n        number: isNumber(value),\n        symbol: isSymbol(value)\n      };\n    }\n  },\n  errorCaptured(error) {\n    // 在浏览器控制台输出错误原因\n    logger.error(error);\n    return false;\n  },\n  methods: {\n    short(value) {\n      const maxLen = 24;\n      if (isString(value) && value.length > maxLen) {\n        return value.slice(0, maxLen / 2) + \"...\" + value.slice(value.length - maxLen / 2);\n      }\n\n      return value;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.text-inline-block {\n  word-break: break-all;\n\n  .normal {\n    color: #565656;\n  }\n  .null,\n  .undefined {\n    color: rgb(128, 128, 128);\n  }\n  .boolean,\n  .function {\n    color: rgb(13, 34, 170);\n  }\n  .number {\n    color: #1c00cf;\n  }\n  .string,\n  .symbol {\n    color: rgb(196, 26, 22);\n  }\n  .string-quote {\n    color: #222;\n  }\n  .keep-format {\n    white-space: pre-wrap;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/BoxModel.vue",
    "content": "<template>\n  <div class=\"box-model\">\n    <div class=\"box\" :class=\"{'box--position': isPositionVisible}\">\n      <template v-if=\"isPositionVisible\">\n        <span class=\"box__label\">position</span>\n        <span class=\"box__label-top\">{{computedStyle.top | format('position')}}</span>\n        <br>\n        <span class=\"box__label-left\">{{computedStyle.left | format('position')}}</span>\n      </template>\n      <div class=\"box box--margin\">\n        <span class=\"box__label\">margin</span>\n        <span class=\"box__label-top\">{{computedStyle.marginTop | format}}</span>\n        <br>\n        <span class=\"box__label-left\">{{computedStyle.marginLeft | format}}</span>\n        <div class=\"box box--border\">\n          <span class=\"box__label\">border</span>\n          <span class=\"box__label-top\">{{computedStyle.borderTopWidth | format}}</span>\n          <br>\n          <span class=\"box__label-left\">{{computedStyle.borderLeftWidth | format}}</span>\n          <div class=\"box box--padding\">\n            <span class=\"box__label\">padding</span>\n            <span class=\"box__label-top\">{{computedStyle.paddingTop | format}}</span>\n            <br>\n            <span class=\"box__label-left\">{{computedStyle.paddingLeft | format}}</span>\n            <div class=\"box box--content\">\n              {{computedStyle.width | format('content')}} x {{computedStyle.height | format('content')}}\n            </div>\n            <span class=\"box__label-right\">{{computedStyle.paddingRight | format}}</span>\n            <br>\n            <span class=\"box__label-bottom\">{{computedStyle.paddingBottom | format}}</span>\n          </div>\n          <span class=\"box__label-right\">{{computedStyle.borderRightWidth | format}}</span>\n          <br>\n          <span class=\"box__label-bottom\">{{computedStyle.borderBottomWidth | format}}</span>\n        </div>\n        <span class=\"box__label-right\">{{computedStyle.marginRight | format}}</span>\n        <br>\n        <span class=\"box__label-bottom\">{{computedStyle.marginBottom | format}}</span>\n      </div>\n      <template v-if=\"isPositionVisible\">\n        <span class=\"box__label-right\">{{computedStyle.right | format('position')}}</span>\n        <br>\n        <span class=\"box__label-bottom\">{{computedStyle.bottom | format('position')}}</span>\n      </template>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    el: {\n      type: Element,\n      required: true\n    }\n  },\n  computed: {\n    computedStyle() {\n      return window.getComputedStyle(this.el);\n    },\n    isPositionVisible() {\n      return this.computedStyle.position !== \"static\";\n    }\n  },\n  filters: {\n    /**\n     * 格式化在盒模型中显式的单位值\n     * @param {String} value\n     * @param {String=} box 有效值 'position', 'margin', 'border', 'padding', content'\n     */\n    format(value, box) {\n      if (value === \"auto\") {\n        if (box === \"position\") return \"-\";\n        else return value;\n      } else if (value === \"0px\") {\n        if (box === \"content\") return parseFloat(value);\n        else return \"-\";\n      } else return parseFloat(value);\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/mixins\";\n@import \"../../styles/variables\";\n.box-model {\n  @include initial();\n  display: inline-block;\n  // 不换行\n  white-space: nowrap;\n  min-height: 190px;\n  font-family: $font-family;\n  font-size: $primary-font-size;\n}\n\n// 矩形盒子样式\n.box {\n  @include initial();\n  display: inline-block;\n  text-align: center;\n  vertical-align: middle;\n  position: relative;\n  margin: 4px;\n  padding: 4px;\n  border: 1px solid transparent;\n  &--position {\n    border: 1px dotted rgb(66%, 66%, 66%);\n  }\n  &--margin {\n    border: 1px dashed black;\n    background-color: rgba(246, 178, 107, 0.66);\n  }\n  &--border {\n    border: 1px solid black;\n    background-color: rgba(255, 229, 153, 0.66);\n  }\n  &--padding {\n    border: 1px dashed grey;\n    background-color: rgba(147, 196, 125, 0.55);\n  }\n  &--content {\n    min-width: 100px;\n    border: 1px solid gray;\n    background-color: rgba(111, 168, 220, 0.66);\n  }\n  &__label {\n    position: absolute;\n    left: 3px;\n    padding: 0 2px;\n  }\n  &__label-top,\n  &__label-bottom,\n  &__label-left,\n  &__label-right {\n    vertical-align: middle;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/ElementPanel.vue",
    "content": "<template>\n  <div class=\"element-panel\">\n    <!-- 使用 v-show 避免销毁组件 -->\n    <div v-show=\"!isStylePanelVisible\" class=\"dom-tree\" ref=\"domTree\" v-prevent-bkg-scroll>\n      <NodeView v-if=\"doctype\" :el=\"doctype\" class=\"source-code\" />\n      <NodeView :el=\"rootEl\" class=\"source-code\" :expandDeepth=\"1\" />\n    </div>\n    <div v-show=\"!isStylePanelVisible && selectedEl\" class=\"dom-path g-hide-scrollbar\" ref=\"domPath\">\n      <NodeLink v-for=\"(path, index) in domPaths\" :key=\"index\"\n        :el=\"path.el\"\n        :selected=\"index === selectedDomPathIndex\"\n        :compacted=\"index !== selectedDomPathIndex && true\"\n        ref=\"nodeLinks\"\n        @click=\"onClickDomPath(index)\"\n      />\n    </div>\n    <!-- 使用 v-if 关闭 inspect panel 时销毁  -->\n    <div v-if=\"isStylePanelVisible\" class=\"inspect-panel\">\n      <VTabBar v-model=\"activedTab\">\n        <VTabBarItem id=\"styles\">Styles</VTabBarItem>\n        <VTabBarItem id=\"computed\">Computed</VTabBarItem>\n      </VTabBar>\n      <TabStyles v-show=\"activedTab === 'styles'\" :el=\"selectedEl\" />\n      <TabComputed v-show=\"activedTab === 'computed'\" :el=\"selectedEl\" />\n    </div>\n    <VFootBar class=\"foot\" :buttons=\"footBarButtons\" />\n  </div>\n</template>\n\n<script>\nimport { VFootBar, VTabBar, VTabBarItem } from \"@/components\";\nimport { eventBus /*Logger*/ } from \"@/utils\";\nimport NodeView from \"./NodeView\";\nimport TabStyles from \"./TabStyles\";\nimport TabComputed from \"./TabComputed\";\nimport NodeLink from \"./NodeLink\";\n\n// const logger = new Logger(\"[ElementPanel]\");\nexport default {\n  name: \"ElementPanel\",\n  components: {\n    NodeView,\n    VFootBar,\n    VTabBar,\n    VTabBarItem,\n    TabStyles,\n    TabComputed,\n    NodeLink\n  },\n  provide() {\n    return {\n      // 更新选中元素及对应组件的引用\n      setSelectedElement: (el, ref) => {\n        this.selectedEl = el;\n        this.selectedRef = ref;\n      },\n      getSelectedElement: () => {\n        return this.selectedEl;\n      }\n    };\n  },\n  data() {\n    return {\n      // rootEl: document.querySelector(\"#element\"),\n      // 根节点，渲染该节点的 DOM 树\n      rootEl: document.documentElement,\n      // 当前选中元素，如果没有值为 null\n      selectedEl: null,\n      /**\n       * 选中元素的 DOM 路径，如 html > body > div#app\n       *\n       * 数组元素数据结构：\n       * {\n       *  // DOM 元素\n       *  el: Node,\n       *  // DOM 元素对应组件的引用\n       *  ref: Node\n       * }\n       */\n      domPaths: [],\n      // 当前选中的 DOM 路径\n      selectedDomPathIndex: -1,\n      // 如果屏幕显示不下 DOM 路径，则以紧凑方式显示 DOM 路径\n      shouldDomPathCompact: false,\n      // 是否显式 inspect 面板\n      isStylePanelVisible: false,\n      // inspect 面板激活的 tab\n      activedTab: \"styles\"\n    };\n  },\n  computed: {\n    doctype() {\n      return document.doctype;\n    },\n    /* eslint-disable */\n    footBarButtons() {\n      const disable = !this.selectedEl || this.selectedEl.nodeType !== Node.ELEMENT_NODE;\n      return [\n        {\n          text: this.isStylePanelVisible ? \"Back\" : \"Inspect\",\n          disable,\n          click: () => {\n            if (disable) return;\n            this.isStylePanelVisible = !this.isStylePanelVisible;\n          }\n        },\n        {\n          text: \"Hide\",\n          click: () => {\n            eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE);\n          }\n        }\n      ];\n    }\n    /* eslint-enable */\n  },\n  watch: {\n    selectedEl(el) {\n      // 选中元素变化时，重置审查面板的默认tab\n      this.activedTab = \"styles\";\n\n      if (!el) return;\n\n      // 如果 dom 路径中存在当前所选元素，则激活 dom 路径中该项，否则重新生成 dom 路径\n      const foundIndex = this.domPaths.findIndex(path => path.el === el);\n      if (foundIndex === -1) {\n        const domPaths = [];\n        let ref = this.selectedRef;\n        while (el && el !== document) {\n          domPaths.unshift({\n            el: el,\n            ref: ref\n          });\n          el = el.parentNode;\n          ref = ref.parentNode;\n        }\n        this.domPaths = domPaths;\n        this.selectedDomPathIndex = domPaths.length - 1;\n      } else {\n        this.selectedDomPathIndex = foundIndex;\n      }\n\n      // 将元素滚动到窗口可视区域\n      if (this.$refs.nodeLinks && this.$refs.nodeLinks[this.selectedDomPathIndex]) {\n        this.$nextTick(() => {\n          this.$refs.nodeLinks[this.selectedDomPathIndex].$el.scrollIntoView();\n        });\n      }\n      this.$nextTick(() => {\n        const domPathEl = this.$refs.domPath;\n        if (domPathEl) {\n          this.shouldDomPathCompact = domPathEl.scrollWidth > domPathEl.clientWidth;\n        }\n      });\n    }\n  },\n  mounted() {\n    // FIXME: test\n    // this.selectedEl = document.querySelector(\"#element\");\n    // this.isStylePanelVisible = true;\n  },\n  methods: {\n    onClickDomPath(index) {\n      this.selectedDomPathIndex = index;\n\n      // 更新选中元素，并将元素滚入可视区域\n      const domPath = this.domPaths[index];\n      this.selectedEl = domPath.el;\n      this.scrollIntoDomTree(domPath.ref);\n    },\n    /**\n     * 将指定元素滚动到 DOM 树可视区域\n     *\n     *  -------    -------   <-- domTreeEl.getBoundingClientRect().top\n     * |       |  |   A   |\n     * |       |  |       |\n     * |   A   |  |       |  <-- targetEl.getBoundingClientRect().top\n     *  -------    -------\n     */\n    scrollIntoDomTree(targetEl) {\n      const domTreeEl = this.$refs.domTree;\n      let offsetY = 0;\n      if (targetEl.nodeType === Node.ELEMENT_NODE) {\n        offsetY = targetEl.getBoundingClientRect().top - domTreeEl.getBoundingClientRect().top;\n      } else {\n        // TEXT_NODE 和 COMMENT_NODE 等非 Element 类型节点没有 getBoundingClientRect() 方法\n        // 但它们的父节点一定是 Element 类型节点，使用其父节点的坐标进行判断\n        offsetY = targetEl.parentNode.getBoundingClientRect().top - domTreeEl.getBoundingClientRect().top;\n      }\n      // 选中元素与 DOM 树顶部的距离，这里取 DOM 树高度的一半，使选中元素垂直居中显示\n      const paddingTop = domTreeEl.clientHeight / 2;\n      domTreeEl.scrollTop += offsetY - paddingTop;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.element-panel {\n  height: $panel-height;\n  display: flex;\n  flex-direction: column;\n  .dom-tree {\n    flex: 1 1 auto;\n    overflow-y: auto;\n    padding: 2px 0;\n  }\n  .dom-path {\n    flex: 0 0 auto;\n    height: 30px;\n    border-top: 1px solid $toolbar-border-color;\n    display: flex;\n    align-items: center;\n    overflow-x: auto;\n  }\n  .inspect-panel {\n    flex: 1 1 auto;\n    display: flex;\n    flex-direction: column;\n  }\n  .foot {\n    flex: 0 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/NodeLink.vue",
    "content": "<template>\n  <span class=\"node-link\" :class=\"{'selected': selected}\" @click=\"$emit('click')\">\n    <template v-if=\"el.nodeType === Node.ELEMENT_NODE\">\n      <template v-if=\"compacted\">\n        <span v-if=\"el.id\" class=\"node-link__id\">{{id}}</span>\n        <span v-else class=\"node-link__tag-name\">{{tagName}}</span>\n      </template>\n      <template v-else>\n        <span class=\"node-link__tag-name\">{{tagName}}</span>\n        <span v-if=\"el.id\" class=\"node-link__id\">{{id}}</span>\n        <span v-if=\"el.classList.length > 0\" class=\"node-link__class-name\">{{className}}</span>\n      </template>\n    </template>\n    <template v-else-if=\"el.nodeType === Node.TEXT_NODE\">\n      <span>(text)</span>\n    </template>\n    <template v-else-if=\"el.nodeType === Node.COMMENT_NODE\">\n      <span>&lt;!--&gt;</span>\n    </template>\n    <template v-else-if=\"el.nodeType === Node.DOCUMENT_TYPE_NODE\">\n      <span>&lt;!doctype&gt;</span>\n    </template>\n    <template v-else>\n      <span>(unknow)</span>\n    </template>\n  </span>\n</template>\n\n<script>\n/**\n * 元素节点链接，如 div#app.a.b\n */\nexport default {\n  props: {\n    el: {\n      type: Node,\n      required: true\n    },\n    selected: {\n      type: Boolean,\n      default: false\n    },\n    compacted: {\n      type: Boolean,\n      default: false\n    }\n  },\n  computed: {\n    Node() {\n      return Node;\n    },\n    tagName() {\n      return this.el.tagName.toLowerCase();\n    },\n    id() {\n      return this.el.id ? \"#\" + this.el.id : \"\";\n    },\n    className() {\n      return this.el.classList.length > 0 ? \".\" + [].slice.call(this.el.classList).join(\".\") : \"\";\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n.node-link {\n  height: 100%;\n  line-height: 30px;\n  white-space: nowrap;\n  padding: 0 7px;\n  // overflow: hidden;\n  // text-overflow: ellipsis;\n  // max-width: 20vw;\n  &.selected {\n    background-color: $toolbar-bg-color;\n    span {\n      color: $default-color !important;\n    }\n  }\n  &__tag-name {\n    color: $dom-tag-name-color;\n  }\n  &__id {\n  }\n  &__class-name {\n    color: $dom-attribute-name-color;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/NodeView.vue",
    "content": "<template>\n  <!-- 为了尽量减少标签的使用，会有重复属性分散在多个同级标签中 -->\n  <div v-if=\"el && el.nodeType === Node.ELEMENT_NODE\" class=\"node element\">\n    <template v-if=\"isExpandable\">\n      <!-- 展开态 -->\n      <template v-if=\"isExpand\">\n        <!-- 起始标签 -->\n        <!-- 只有标签可点击 -->\n        <Tag type=\"start\"\n          :el=\"el\"\n          :class=\"{'unfold': deepth > 0, 'select': isSelected && selectTagType !== 'end'}\"\n          :style=\"indentStyle\"\n          @click=\"onClickTag('start')\"\n        />\n        <!-- 子节点 -->\n        <NodeView v-for=\"(node, index) in childNodes\" :key=\"deepth + '-' + index\"\n          :el=\"node\"\n          :deepth=\"deepth + 1\"\n          :expandDeepth=\"expandDeepth - 1\"\n        />\n        <!-- 结束标签 -->\n        <Tag type=\"end\"\n          :el=\"el\"\n          :style=\"indentStyle\"\n          :class=\"{'select': isSelected && selectTagType === 'end'}\"\n          @click=\"onClickTag('end')\"\n        />\n      </template>\n      <!-- 折叠态-->\n      <template v-else>\n        <Tag type=\"inline\"\n          :el=\"el\"\n          :class=\"{'fold': deepth > 0, 'select': isSelected}\"\n          :style=\"indentStyle\"\n          @click=\"onClickTag('start')\"\n          >...</Tag>\n      </template>\n    </template>\n    <!-- 只有一个 Text 类型元素时内联展示 -->\n    <template v-else>\n      <Tag type=\"inline\"\n        :el=\"el\"\n        :style=\"indentStyle\"\n        :class=\"{'select': isSelected}\"\n        @click=\"onClickTag\"\n        >{{el.textContent}}</Tag>\n    </template>\n  </div>  \n  <div v-else-if=\"el && el.nodeType === Node.TEXT_NODE\"\n    class=\"node text\"\n    :class=\"{'select': isSelected}\"\n    :style=\"indentStyle\"\n    @click=\"onClickTag\"\n    >\n    <span v-if=\"el.parentNode.nodeName === 'STYLE' || el.parentNode.nodeName === 'SCRIPT'\">{{el.data}}</span>\n    <span v-else>\"{{el.data}}\"</span>\n  </div>\n  <div v-else-if=\"el && el.nodeType === Node.COMMENT_NODE\"\n    class=\"node comment\"\n    :class=\"{'select': isSelected}\"\n    :style=\"indentStyle\"\n    @click=\"onClickTag\"\n    >\n    <span>&lt;!--{{el.data}}--&gt;</span>\n  </div>\n  <div v-else-if=\"el && el.nodeType === Node.DOCUMENT_TYPE_NODE\"\n    class=\"node doctype\"\n    :class=\"{'select': isSelected}\"\n    :style=\"indentStyle\"\n    @click=\"onClickTag\"\n    >\n    <span>&lt;!doctype html&gt;</span>\n  </div>\n  <div v-else\n    class=\"node\"\n    :class=\"{'select': isSelected}\"\n    :style=\"indentStyle\"\n    @click=\"onClickTag\"\n    >\n    <span>&lt;unknow({{el.nodeName}})&gt;</span>\n  </div>\n  <!-- </div> -->\n</template>\n\n<script>\nimport Tag from \"./Tag\";\n/**\n * DOM 树\n * 节点类型\n *\n *\n * 子节点的展示\n * 1)如果父节点只有一个子节点，且子节点为 Text 类型，则内联展示子节点，且 Text 内容不需要双引号包裹\n * <div>hello</div>\n * 2)否则，每个子节点独占一行展示\n * <div>\n *   \"hello\"\n *   <!-- comment -->\n * </div>\n */\nexport default {\n  name: \"NodeView\",\n  components: {\n    Tag\n  },\n  inject: [\"setSelectedElement\", \"getSelectedElement\"],\n  props: {\n    el: {\n      type: Node,\n      default: null\n    },\n    /**\n     * 相对根节点的深度\n     *\n     * 例如节点数为 A>B>C\n     * A 的深度为 0\n     * B 的深度为 1\n     * C 的深度为 2\n     */\n    deepth: {\n      type: Number,\n      default: 0\n    },\n    /**\n     * 当前节点往下展开的深度（仅初始有效）\n     *\n     * 例如节点树为 div>div>div，当前处于第一个 div 处，则：\n     * 深度为 0，展示 <div>...</div>\n     * 深度为 1，展示 <div><div>...<div></div>\n     * 深度为 2，展示 <div><div><div></div><div></div>\n     */\n    expandDeepth: {\n      type: Number,\n      default: 0\n    }\n  },\n  data() {\n    return {\n      // 是否展开\n      isExpand: false,\n      // 选中的标签类型，用于区分开始标签和结束标签的选中态，其他类型默认选中整个标签\n      selectTagType: \"\"\n    };\n  },\n  computed: {\n    Node() {\n      return Node;\n    },\n    isExpandable() {\n      const childNodes = this.childNodes;\n      if (childNodes.length > 1) return true;\n\n      if (childNodes.length === 0) return false;\n\n      // <style>和<script>标签只有一个节点时折叠\n      const tagName = this.el.nodeName.toLowerCase();\n      if (childNodes.length === 1 && [\"style\", \"script\"].findIndex(v => v === tagName) !== -1) return true;\n\n      // 子节点非 TEXT 类型节点时折叠\n      const child = childNodes[0];\n      if (child.nodeType !== Node.TEXT_NODE) return true;\n\n      // 其他情况不可折叠，即直接展示内容\n      return false;\n    },\n    isSelected() {\n      return this.getSelectedElement() === this.el;\n    },\n    indentStyle() {\n      return {\n        \"padding-left\": this.deepth > 0 ? `${this.deepth}em` : \"auto\"\n      };\n    },\n    childNodes() {\n      const arr = [];\n      if (!this.el) return arr;\n\n      const len = this.el.childNodes.length;\n      for (let i = 0; i < len; ++i) {\n        const node = this.el.childNodes[i];\n        // Node.childNodes 是动态变化的，取值时需要判空\n        if (node) {\n          const isEmptyTextNode = /^\\s+$/.test(node.data);\n          // 过滤掉空白字符组成的 Text 节点\n          if (node.nodeType !== Node.TEXT_NODE || !isEmptyTextNode) {\n            arr.push(node);\n          }\n        }\n      }\n      return arr;\n    }\n  },\n  mounted() {\n    this.isExpand = this.expandDeepth > 0;\n  },\n  methods: {\n    onClickTag(selectTagType) {\n      const el = this.el;\n      const selectedEl = this.getSelectedElement();\n      if (selectedEl !== el) {\n        // 如果未选中则设未选中元素\n        this.setSelectedElement(el, this.$el);\n      } else {\n        const preSelectTagType = this.selectTagType;\n        // 如果已选中，且元素可展开，且点击的是开始标签，则切换折叠/展开\n        if (this.isExpandable && preSelectTagType === selectTagType && selectTagType !== \"end\") {\n          this.isExpand = !this.isExpand;\n        }\n      }\n      // 只有 Element 类型才有开始和结束标签\n      if (this.el.nodeType === Node.ELEMENT_NODE) {\n        this.selectTagType = selectTagType;\n      }\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/triangles\";\n@import \"../../styles/mixins\";\n$triangles-border-width: 6px;\n$selection-bg-color: #2196f3;\n.node {\n  display: flex;\n  flex-direction: column;\n  margin-top: 1px;\n  line-height: 1.4em;\n  .select {\n    /deep/ span {\n      color: white;\n    }\n    background-color: $selection-bg-color;\n    &.fold::before {\n      @include triangles-collapse($triangles-border-width, #fff);\n      margin-right: 2px;\n    }\n    &.unfold::before {\n      @include triangles-expand($triangles-border-width, #fff);\n      margin-right: 2px;\n      margin-bottom: 1px;\n    }\n  }\n  &__indent {\n    width: 1em;\n  }\n  .element {\n  }\n  .text {\n    @include descendant-attr(\"color\", rgb(48, 57, 66));\n    white-space: pre-wrap;\n    word-break: break-all;\n  }\n  .comment {\n    @include descendant-attr(\"color\", rgb(35, 110, 37));\n  }\n  // FIXME: '.node .doctype' 选择器无法选中元素，原因位置，暂时用串联选择器解决\n  &.doctype {\n    @include descendant-attr(\"color\", rgb(192, 192, 192));\n    &.select {\n      color: white;\n      background-color: $selection-bg-color;\n    }\n  }\n  .fold::before {\n    @include triangles-collapse($triangles-border-width, #727272);\n    margin-right: 2px;\n  }\n  .unfold::before {\n    @include triangles-expand($triangles-border-width, #727272);\n    margin-right: 2px;\n    margin-bottom: 1px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/StyleColorValue.vue",
    "content": "<template>\n  <span class=\"style-color-value\">\n    <span class=\"color-box\" :style=\"{'background-color': color}\"></span>\n    <span>{{color}}</span>\n  </span>\n</template>\n\n<script>\nexport default {\n  props: {\n    // CSS 颜色值\n    color: {\n      type: String,\n      required: true\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.style-color-value {\n  .color-box {\n    display: inline-block;\n    vertical-align: middle;\n    width: 1em;\n    height: 1em;\n    margin-right: 3px;\n    border: 1px solid rgba(128, 128, 128, 0.6);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/StyleProperty.vue",
    "content": "<template>\n  <div class=\"style-property\">\n    <div class=\"style-property__item\" @click=\"isExpand = !isExpand\">\n      <!-- 属性名可空，为空时不展示 -->\n      <span class=\"style-property__name\">{{name}}</span>\n      <span>:</span>\n      <!-- 属性值，如果是缩写属性，属性值前面有三角形箭头 -->\n      <span class=\"style-property__value\" :class=\"[isExpandable ? (isExpand ? 'expand' : 'collapse') : '']\">\n        <!-- 如属性值中包含颜色值，会被分隔成三部分，中间部分是可展示的颜色值 -->\n        <template v-if=\"colorValueList.length > 1\">\n          <span>{{colorValueList[0]}}</span>\n          <StyleColorValue :color=\"colorValueList[1]\" />\n          <span>{{colorValueList[2]}}</span>\n        </template>\n        <span v-else>{{value}}</span>\n      </span>\n      <span>;</span>\n    </div>\n    <!-- 属性展开形式，如 padding 展开后 padding-left/padding-right/padding-top/padding-bottom -->\n    <div v-if=\"isExpandable && isExpand\" class=\"style-property__children\">\n      <StyleProperty\n        v-for=\"prop in subProps\"\n        :key=\"prop.name\"\n        :name=\"prop.name\"\n        :value=\"prop.value\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport { Style } from \"@/utils\";\nimport StyleColorValue from \"./StyleColorValue\";\n/**\n * 展示 CSS 属性的键值对，支持属性的缩写形式，支持颜色值特殊展示\n *\n * 非缩写形式属性：\n * {\n *  padding-left: 5px\n * }\n *\n * 缩写形式属性：\n * {\n *  padding: 5px;\n *    padding-top: 5px;\n *    padding-right: 5px;\n *    padding-bottom: 5px;\n *    padding-left: 5px;\n * }\n */\nexport default {\n  name: \"StyleProperty\",\n  components: {\n    StyleColorValue\n  },\n  props: {\n    // CSS属性名\n    name: String,\n    // CSS属性值\n    value: String,\n    // 子属性\n    subProps: {\n      type: Array,\n      default: () => []\n    }\n  },\n  data() {\n    return {\n      // 展开/折叠属性\n      isExpand: false\n    };\n  },\n  computed: {\n    // 是否可展开属性\n    isExpandable() {\n      return Array.isArray(this.subProps) && this.subProps.length > 0;\n    },\n    // 根据颜色值在属性值中的位置，将属性值分成一个数组，数组元素之间间隔一个颜色块\n    colorValueList() {\n      const value = this.value;\n      const colorRegExp = Style.getColorRegExp();\n      if (colorRegExp.test(value)) {\n        const matchResult = colorRegExp.exec(value);\n        const colorIndex = matchResult.index;\n        const color = matchResult[0];\n        return [value.slice(0, colorIndex), color, value.slice(colorIndex + color.length)];\n      } else {\n        return [value];\n      }\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/triangles\";\n\n.style-property {\n  display: flex;\n  flex-direction: column;\n  color: rgb(48, 57, 66);\n  &__item {\n    padding-left: 1.5em;\n    line-height: 1.4em;\n  }\n  &__children {\n    padding-left: 1em;\n  }\n  &__name {\n    color: rgb(200, 0, 0);\n  }\n\n  &__value {\n    padding-left: 0.5em;\n    &.collapse::before {\n      @include triangles-collapse(6px, #727272);\n      margin-right: 0.2em;\n    }\n    &.expand::before {\n      @include triangles-expand(6px, #727272);\n      margin-right: 0.2em;\n      margin-bottom: 1px;\n    }\n  }\n\n  &__color {\n    display: inline-block;\n    vertical-align: middle;\n    width: 1em;\n    height: 1em;\n    margin-right: 3px;\n    border: 1px solid rgba(128, 128, 128, 0.6);\n  }\n\n  // 不可继承的属性\n  &--not-inheritable {\n  }\n  // 被覆盖的属性\n  &--overried {\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/StyleRule.vue",
    "content": "<template>\n  <div class=\"css-rule\">\n    <div class=\"css-rule__start\">\n      <span class=\"css-rule__selector\" :class=\"{'css-rule__selector--style': isStyleAttribute}\">{{selector}}</span>\n      <span class=\"css-rule__left-brace\">&nbsp;{</span>\n      <span class=\"css-rule__href\">{{rule.href}}</span>\n    </div>\n    <div class=\"css-rule__content\">\n      <StyleProperty v-for=\"prop in properties\"\n        :key=\"prop.name\"\n        :name=\"prop.name\"\n        :value=\"prop.value\"\n        :subProps=\"prop.subProps\"\n      />\n    </div>\n    <div class=\"css-rule__end\">\n      <span class=\"css-rule__right-brace\">}</span>\n    </div>\n  </div>\n</template>\n\n<script>\nimport StyleProperty from \"./StyleProperty\";\n\n// 支持缩写的 CSS 属性\nconst ABBR_PROPS = [\n  \"border\",\n  \"border-top\",\n  \"border-left\",\n  \"border-right\",\n  \"border-bottom\",\n  \"border-image\",\n  \"margin\",\n  \"padding\",\n  \"background\",\n  \"animation\",\n  \"flex\",\n  \"font\",\n  \"grid\",\n  \"inset\",\n  \"outline\",\n  \"list-style\",\n  \"text-decoration\",\n  \"text-emphasis\",\n  \"transform\",\n  \"transition\"\n];\n\nexport default {\n  components: {\n    StyleProperty\n  },\n  props: {\n    /*\n    * {\n    *  // 样式规则来源(styleAttribute, styleSheet)\n    *  from: String\n    *  // 选择器\n    *  selector: String,\n    *  // 是否是继承的\n    *  inherit: Boolean,\n    *  // 样式声明\n    *  style: CSSStyleDeclaration,\n    *  // 如果是导入或链入的样式表，该字段表示地址\n    *  href: String\n    * },\n    */\n    rule: {\n      type: Object\n    }\n  },\n  computed: {\n    selector() {\n      const rule = this.rule;\n      if (rule.from === \"styleSheet\") {\n        // 样式表\n        return rule.selector;\n      } else if (rule.from === \"styleAttribute\") {\n        // 标签 style 属性\n        return rule.inherit ? \"Style Attribute\" : \"element.style\";\n      }\n    },\n    isStyleAttribute() {\n      return this.rule.from === \"styleAttribute\";\n    },\n    /**\n     * 将可缩写的 CSS 属性中缩写成一个属性，缩写后的属性多出一个 subProps 字段存储原始的属性集合\n     *\n     * 例如，将 CSSStyleDeclaration 进行处理后\n     * {\n     *  0: 'padding-left',\n     *  1: 'padding-right',\n     *  2: 'padding-top',\n     *  3: 'padding-bottom',\n     *  'padding-left': '5px',\n     *  'padding-right': '5px',\n     *  'padding-top': '5px',\n     *  'padding-bottom': '5px',\n     * }\n     * 输出下面结果：\n     * [\n     *  {\n     *    name: 'padding',\n     *    value: '5px',\n     *    subProps: [\n     *      {name: 'padding-left', value: '5px'},\n     *      {name: 'padding-right', value: '5px'},\n     *      {name: 'padding-top', value: '5px'},\n     *      {name: 'padding-bottom', value: '5px'},\n     *    ]\n     *  }\n     * ]\n     */\n    properties() {\n      const style = this.rule.style;\n      const styleArr = [].slice.call(style);\n      const properties = [];\n\n      /**\n       * i. 遍历所有可缩写的 CSS 属性，假设当前遍历的属性名为 abbr\n       * ii. 如果元素属性列表中存在缩写属性 abbr，则创建一个对象 M 保存缩写属性 abbr 及其值，\n       *     并将 abbr 展开后的所有属性键值对加入到 M.subProps 数组，将 M 加入到待返回数组中，\n       *     最后移除元素样式列表中 abbr 展开后的所有属性\n       * iii. 遍历结束后，将元素样式列表中值不为空的属性全部加入到待返回的属性列表\n       */\n      ABBR_PROPS.forEach(abbrName => {\n        if (!style[abbrName]) return;\n\n        // 存在缩写属性\n        const prop = {\n          name: abbrName,\n          value: style[abbrName],\n          subProps: []\n        };\n        const nameArr = styleArr.filter(name => name.indexOf(abbrName) !== -1);\n        if (nameArr.length === 0) return;\n        const subProps = nameArr.map(name => ({\n          name,\n          value: style[name],\n          important: style.getPropertyPriority(name) === \"important\"\n        }));\n        prop.subProps.push(...subProps);\n        // 移除已处理的属性\n        nameArr.forEach(name => {\n          styleArr.splice(styleArr.findIndex(v => v === name), 1);\n        });\n        properties.push(prop);\n      });\n\n      styleArr.forEach(name => {\n        if (style[name]) {\n          properties.push({\n            name,\n            value: style[name],\n            important: style.getPropertyPriority(name) === \"important\"\n          });\n        }\n      });\n\n      return properties;\n    }\n  },\n  methods: {}\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.css-rule {\n  display: flex;\n  flex-direction: column;\n  border-bottom: 1px solid $divider-color;\n  padding: 2px 2px 4px 4px;\n\n  &__start {\n    display: flex;\n    line-height: 1.5em;\n  }\n  &__content {\n    display: flex;\n    flex-direction: column;\n  }\n  &__end {\n    line-height: 1.5em;\n  }\n  &__selector {\n    flex: 0 0 auto;\n    color: #222;\n    &--style {\n      color: #888;\n    }\n  }\n  &__left-brace {\n    flex: 1 1 auto;\n  }\n  &__href {\n    flex: 0 0 auto;\n    color: rgb(85, 85, 85);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/TabComputed.vue",
    "content": "<template>\n  <div class=\"tab-computed\" v-prevent-bkg-scroll>\n    <div class=\"box-model-container\">\n      <BoxModel v-if=\"el\" :el=\"el\" />\n    </div>\n    <div class=\"filter-bar\">\n      <input class=\"filter-bar__input\" placeholder=\"Filter\" v-model=\"filter\" />\n      <input class=\"filter-bar__checkbox\" id=\"showAll\" type=\"checkbox\" v-model=\"isShowAll\" />\n      <label class=\"filter-bar__label\" for=\"showAll\">Show all</label>\n    </div>\n    <div class=\"computed-style table\">\n      <template v-for=\"{name, value, isColorValue, matchedRules} in filteredComputedStyleArr\">\n        <div class=\"table__row\"\n          :class=\"{'table__row--override': matchedRules.length > 0, 'collapse':  matchedRules.length > 0 && computedStyleCollapseMap[name], 'expand': matchedRules.length > 0 && !computedStyleCollapseMap[name] }\"\n          :key=\"name\"\n          @click=\"onClickRow(name)\"\n          >\n          <div class=\"table__cell table__cell--name\">{{name}}</div>\n          <div class=\"table__cell table__cell--value\">\n            <StyleColorValue v-if=\"isColorValue\" :color=\"value\" />\n            <span v-else>{{value}}</span>\n          </div>\n        </div>\n        <template v-if=\"!computedStyleCollapseMap[name]\">\n          <div v-for=\"(rule, index) in matchedRules\"\n            class=\"table__row table__row--intent\"\n            :class=\"{'table__row--override': matchedRules.length > 0}\"\n            :key=\"name + '-' + index\" >\n            <div class=\"table__cell table__cell--value\" :class=\"{'table__cell--through': index !== 0}\">\n              <StyleColorValue v-if=\"isColorValue\" :color=\"rule.value\" />\n              <span v-else>{{rule.value}}</span>\n            </div>\n            <div class=\"table__cell table__cell--selector\">{{rule.selectorText}}</div>\n            <div class=\"table__cell table__cell--href\">{{rule.href}}</div>\n          </div>\n        </template>\n      </template>\n    </div>\n  </div>\n</template>\n\n<script>\n/**\n * 元素计算属性展示\n */\nimport { Style, /*Logger, */ getURLFileName } from \"@/utils\";\nimport BoxModel from \"./BoxModel\";\nimport StyleColorValue from \"./StyleColorValue\";\n\n// const logger = new Logger(\"[TabComputed]\");\nexport default {\n  components: {\n    BoxModel,\n    StyleColorValue\n  },\n  props: {\n    el: {\n      type: Element,\n      required: true\n    }\n  },\n  data() {\n    return {\n      filter: \"\",\n      isShowAll: true,\n      /**\n       * 与当前元素匹配的计算属性\n       * {\n       *  name: String,\n       *  value: String,\n       *  matchedRules: [{\n       *    value: String,\n       *    selectorText: String,\n       *    href: String\n       *  }]\n       * }\n       */\n      computedStyleArr: [],\n      /*\n      * 记录计算属性的折叠状态\n      * {\n      *   // key 是CSS样式属性名，value 是折叠状态\n      *   [String]: Boolean\n      * }\n      */\n      computedStyleCollapseMap: {}\n    };\n  },\n  computed: {\n    filteredComputedStyleArr() {\n      const filter = this.filter;\n      const clonedComputedStyleArr = [...this.computedStyleArr];\n      if (!filter) return clonedComputedStyleArr.sort(compareFn);\n\n      return clonedComputedStyleArr\n        .filter(({ name, value }) => {\n          return name.indexOf(filter) !== -1 || value.indexOf(filter) !== -1;\n        })\n        .sort(compareFn);\n    }\n  },\n  watch: {\n    isShowAll(val) {\n      this.updateComputedStyleArr(val);\n    }\n  },\n  mounted() {\n    this.updateComputedStyleArr(this.isShowAll);\n    this.computedStyleCollapseMap = this.getComputedStyleArr(this.el).reduce((pre, cur) => {\n      if (cur.matchedRules.length > 0) {\n        pre[cur.name] = true;\n      }\n      return pre;\n    }, {});\n  },\n  methods: {\n    onClickRow(propName) {\n      this.$set(this.computedStyleCollapseMap, propName, !this.computedStyleCollapseMap[propName]);\n    },\n    updateComputedStyleArr(isShowAll) {\n      if (isShowAll) {\n        this.computedStyleArr = this.getComputedStyleArr(this.el);\n      } else {\n        this.computedStyleArr = this.getComputedStyleArr(this.el).filter(item => item.matchedRules.length > 0);\n      }\n    },\n    // 获取元素的计算样式\n    getComputedStyleArr(el) {\n      const computedStyle = window.getComputedStyle(el);\n      const computedStyleArr = [];\n\n      // 获取与当前元素匹配的所有 CSS 规则，并按层叠顺序进行排序\n      const matchedCSSRules = Style.getMatchedCSSRules(el).sort(Style.compareCSSRule);\n      // 将元素标签上的 style 属性装饰成一条 CSS 规则，方便统一处理\n      matchedCSSRules.unshift({\n        selectorText: \"element.style\",\n        style: el.style\n      });\n\n      // 搜集计算属性的键值对，并且将包含该属性的 CSS 规则保存到数组\n      let name, value;\n      for (let i = 0; i < computedStyle.length; ++i) {\n        name = computedStyle[i];\n        value = computedStyle.getPropertyValue(name);\n        const matchedRules = matchedCSSRules.filter(rule => !!rule.style[name]).map(rule => ({\n          value: rule.style[name],\n          isColorValue: Style.getColorRegExp().test(rule.style[name]),\n          selectorText: rule.selectorText,\n          href: (rule.parentStyleSheet && getURLFileName(rule.parentStyleSheet.href)) || \"\"\n        }));\n        computedStyleArr.push({\n          name,\n          value,\n          isColorValue: Style.getColorRegExp().test(value),\n          matchedRules\n        });\n      }\n      // logger.log('matchedCSSRules:', matchedCSSRules)\n      // logger.log('computedStyleArr:', computedStyleArr)\n\n      return computedStyleArr;\n    }\n  }\n};\n\n// 计算属性名排序规则\nfunction compareFn(a, b) {\n  if (a.name[0] === \"-\" && b.name[0] !== \"-\") {\n    return 1;\n  }\n  if (a.name[0] !== \"-\" && b.name[0] === \"-\") {\n    return -1;\n  }\n  return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;\n}\n</script>\n\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n@import \"../../styles/mixins\";\n@import \"../../styles/triangles\";\n.tab-computed {\n  flex: auto;\n  display: flex;\n  flex-direction: column;\n  overflow-y: auto;\n}\n\n.box-model-container {\n  flex: none;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 8px;\n  overflow: scroll;\n}\n\n.filter-bar {\n  flex: none;\n  display: flex;\n  border-top: 1px solid $divider-color;\n  border-bottom: 1px solid #eee;\n  align-items: center;\n  &__input {\n    @include input(2em);\n    flex: auto;\n    padding: 0 4px;\n    margin: 3px;\n  }\n  &__checkbox {\n    width: 1.2em;\n    height: 1.2em;\n    margin: 3px 5px;\n  }\n  &__label {\n    color: $default-color;\n    padding-right: 5px;\n  }\n}\n\n.computed-style {\n  // 平分垂直空间\n  flex: auto;\n  margin-top: 3px;\n}\n\n.table {\n  $row-height: 1.6em;\n  $opacity: 0.5;\n  $icon-size: 6px;\n  $icon-padding: 4px;\n  display: flex;\n  flex-direction: column;\n  &__row {\n    flex: 0 0 $row-height;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    background-color: white;\n    opacity: $opacity;\n    // 保留与图标相同宽度的空白空间\n    padding-left: $icon-padding + $icon-size;\n    &:nth-child(odd) {\n      background-color: #f5f5f5;\n    }\n    &:active {\n      background-color: rgb(235, 242, 252);\n    }\n    &--override {\n      opacity: 1;\n    }\n    &--intent {\n      padding-left: 20px;\n    }\n    &.collapse {\n      padding-left: $icon-padding;\n      &::before {\n        @include triangles-collapse($icon-size, #727272);\n      }\n    }\n    &.expand {\n      padding-left: $icon-padding;\n      &::before {\n        @include triangles-expand($icon-size, #727272);\n      }\n    }\n  }\n  &__cell {\n    @include text-scroll;\n    @include hide-scrollbar;\n    line-height: $row-height;\n    width: 0;\n    margin: 0 4px;\n    &--name {\n      flex: 2;\n      color: rgb(200, 0, 0);\n    }\n    &--value {\n      flex: 1;\n      color: $default-color;\n    }\n    &--through {\n      text-decoration: line-through;\n    }\n    &--selector {\n      flex: 1;\n      opacity: $opacity;\n    }\n    &--href {\n      flex: 1;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/TabStyles.vue",
    "content": "<template>\n  <div class=\"tab-style source-code\" v-prevent-bkg-scroll>\n    <div v-for=\"(styleSheet, i) in displayStyleSheets\" :key=\"i\" class=\"style-sheet\">\n      <template v-if=\"styleSheet.type === 'element'\">\n        <!-- 当前元素不显示头部 -->\n        <div v-if=\"styleSheet.el !== el\" class=\"style-sheet__head\">\n          Inherited from\n          <NodeLink class=\"style-sheet__monospace\" :el=\"styleSheet.el\" />\n        </div>\n        <StyleRule v-for=\"(rule, j) in styleSheet.displayRules\"\n          :key=\"i + '-' + j\"\n          :rule=\"rule\"\n        />\n      </template>\n    </div>\n    <div class=\"box-model-container\">\n      <BoxModel v-if=\"el\" :el=\"el\" />\n    </div>\n  </div>\n</template>\n\n<script>\nimport { /*Logger, */ getURLFileName, Style } from \"@/utils\";\nimport StyleRule from \"./StyleRule\";\nimport NodeLink from \"./NodeLink\";\nimport BoxModel from \"./BoxModel\";\n\n// const logger = new Logger(\"[TabStyles.vue]\");\nexport default {\n  components: {\n    StyleRule,\n    BoxModel,\n    NodeLink\n  },\n  props: {\n    el: {\n      type: Element,\n      default: null\n    }\n  },\n  data() {\n    return {\n      /**\n       * CSS 样式规则\n       * 数据结构如下：\n       * [\n       *   {\n       *     // 类型(element, keyframe)\n       *     type: String,\n       *     el: Element,\n       *     displayRules: [\n       *       {\n       *        // 样式规则来源(styleAttribute, styleSheet)\n       *        from: String\n       *        // 选择器\n       *        selector: String,\n       *        // 是否是继承的\n       *        inherit: Boolean,\n       *        // 样式声明\n       *        style: CSSStyleDeclaration,\n       *        // 如果是导入或链入的样式表，该字段表示地址\n       *        href: String\n       *       },\n       *       ...\n       *     ]\n       *   },\n       *   ...\n       * ]\n       */\n      displayStyleSheets: []\n    };\n  },\n  mounted() {\n    this.displayStyleSheets = getDisplayStyleSheets(this.el);\n  },\n  methods: {\n    getTagName(el) {\n      return el.tagName.toLowerCase();\n    },\n    getId(el) {\n      return el.id ? \"#\" + el.id : \"\";\n    },\n    getClass(el) {\n      return el.classList.length > 0 ? \".\" + [].slice.call(el.classList).join(\".\") : \"\";\n    }\n  }\n};\n\n/**\n * 获取指定元素的样式表集合，每个样式表内包含一组样式规则列表，按特殊性从高到低排序\n */\nfunction getDisplayStyleSheets(_el) {\n  const displayStyleSheets = [];\n  let el = _el;\n  while (el !== document.documentElement) {\n    const inherit = el !== _el;\n    const matchedCSSRules = Style.getMatchedCSSRules(el);\n    // logger.log(matchedCSSRules);\n    const displayRules = matchedCSSRules.sort(Style.compareCSSRule).map(rule => {\n      // 映射 CSSRule 到视图所需数据结构\n      return {\n        from: \"styleSheet\",\n        inherit,\n        // 出现顺序，值越小表示越先出现\n        order: rule.order,\n        selector: rule.selectorText,\n        style: rule.style,\n        href: (rule.parentStyleSheet && getURLFileName(rule.parentStyleSheet.href)) || \"<style>...</style>\"\n      };\n    });\n\n    /**\n     * 添加内联样式到样式规则列表\n     * a. 如果是当前元素，无论是否有 CSS 规则都添加一个规则项\n     * b. 如果是祖先元素，则只有存在 CSS 规则时才添加规则项\n     */\n    if (!inherit || el.style.length > 0) {\n      displayRules.unshift({\n        from: \"styleAttribute\",\n        inherit,\n        style: el.style\n      });\n    }\n\n    if (displayRules.length > 0) {\n      displayStyleSheets.push({\n        type: \"element\",\n        el,\n        displayRules\n      });\n    }\n    el = el.parentNode;\n  }\n  return displayStyleSheets;\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.tab-style {\n  flex: auto;\n  overflow-y: auto;\n}\n\n.style-sheet {\n  &__head {\n    height: $source-code-font-size * 2;\n    line-height: $source-code-font-size * 2;\n    padding-right: 2px;\n    padding-left: 4px;\n    background-color: $tabbar-bg-color;\n    border-bottom: 1px solid $divider-color;\n  }\n  &__monospace {\n    background: rgb(255, 255, 255);\n    padding: 1px 3px;\n    border: 1px solid $divider-color;\n  }\n}\n\n.box-model-container {\n  flex: none;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 8px;\n}\n</style>\n"
  },
  {
    "path": "src/panels/element/Tag.vue",
    "content": "<template>\n  <!-- 不要换行，否则内容前后多出空白符 -->\n  <span v-if=\"type === 'start'\" class=\"tag tag--start\" @click=\"$emit('click')\">\n    <span>&lt;</span>\n    <span class=\"tag__tag-name\">{{tagName}}</span>\n    <span v-for=\"[name, value] in attrs\" :key=\"name\">\n      <span>&nbsp;</span>\n      <span class=\"tag__attr-name\">{{name}}</span>\n      <span>=\"</span>\n      <span class=\"tag__attr-value\">{{value}}</span>\n      <span>\"</span>\n    </span>\n    <span>&gt;</span>\n  </span>\n  <span v-else-if=\"type === 'end' && !isVoidElement\" class=\"tag tag--end\" @click=\"$emit('click')\">\n    <span>&lt;/</span>\n    <span class=\"tag__tag-name\">{{tagName}}</span>\n    <span>&gt;</span>\n  </span>\n  <span v-else-if=\"type === 'inline'\" class=\"tag tag--inline\" @click=\"$emit('click')\">\n    <Tag :el=\"el\" type=\"start\"/>\n    <span class=\"tag__text\" v-if=\"hasDefaultSlot\">\n      <slot></slot>\n    </span>\n    <Tag :el=\"el\" type=\"end\"/>\n  </span>\n</template>\n\n<script>\nexport default {\n  name: \"Tag\",\n  props: {\n    el: {\n      type: Element,\n      required: true\n    },\n    type: {\n      type: String,\n      default: \"start\",\n      validator(value) {\n        return [\"start\", \"end\", \"inline\"].indexOf(value) !== -1;\n      }\n    }\n  },\n  computed: {\n    tagName() {\n      return this.el.tagName.toLowerCase();\n    },\n    attrs() {\n      const el = this.el;\n      return el.getAttributeNames().map(name => [name, el.getAttribute(name)]);\n    },\n    hasDefaultSlot() {\n      const arr = this.$slots.default;\n      if (Array.isArray(arr) && arr.length > 0) {\n        return Boolean(arr[0].text);\n      } else {\n        return false;\n      }\n    },\n    // 是否是无内容标签\n    // 无内容标签不能有结束标签，HTML5 中定义的无内容标签见<https://html.spec.whatwg.org/multipage/syntax.html#void-elements>\n    isVoidElement() {\n      return (\n        [\n          \"area\",\n          \"base\",\n          \"br\",\n          \"col\",\n          \"embed\",\n          \"hr\",\n          \"img\",\n          \"input\",\n          \"link\",\n          \"meta\",\n          \"param\",\n          \"source\",\n          \"track\",\n          \"wbr\"\n        ].indexOf(this.tagName) !== -1\n      );\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n@import \"../../styles/mixins\";\n.tag {\n  @include descendant-attr(\"color\", rgb(168, 148, 166));\n  white-space: pre-wrap;\n  word-break: break-all;\n  &--start,\n  &--end,\n  &--inline {\n  }\n  &__tag-name {\n    color: $dom-tag-name-color;\n  }\n  &__attr-name {\n    color: $dom-attribute-name-color;\n  }\n  &__attr-value {\n    color: $dom-attribute-value-color;\n  }\n  &__text {\n    color: rgb(48, 57, 66);\n    white-space: pre;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/index.js",
    "content": "export { default as SettingsPanel } from \"./settings/SettingsPanel.vue\";\nexport { default as NetworkPanel } from \"./network/NetworkPanel.vue\";\nexport { default as ConsolePanel } from \"./console/ConsolePanel\";\nexport { default as ApplicationPanel } from \"./application/ApplicationPanel\";\nexport { default as ElementPanel } from \"./element/ElementPanel.vue\";\n"
  },
  {
    "path": "src/panels/network/HttpStatus.js",
    "content": "export default {\n  \"100\": \"Continue\",\n  \"101\": \"Switching Protocols\",\n  \"102\": \"Processing\",\n  \"200\": \"OK\",\n  \"201\": \"Created\",\n  \"202\": \"Accepted\",\n  \"203\": \"Non-Authoritative Information\",\n  \"204\": \"No Content\",\n  \"205\": \"Reset Content\",\n  \"206\": \"Partial Content\",\n  \"207\": \"Multi-Status\",\n  \"208\": \"Already Reported\",\n  \"226\": \"IM Used\",\n  \"300\": \"Multiple Choices\",\n  \"301\": \"Moved Permanently\",\n  \"302\": \"Found\",\n  \"303\": \"See Other\",\n  \"304\": \"Not Modified\",\n  \"305\": \"Use Proxy\",\n  \"307\": \"Temporary Redirect\",\n  \"308\": \"Permanent Redirect\",\n  \"400\": \"Bad Request\",\n  \"401\": \"Unauthorized\",\n  \"402\": \"Payment Required\",\n  \"403\": \"Forbidden\",\n  \"404\": \"Not Found\",\n  \"405\": \"Method Not Allowed\",\n  \"406\": \"Not Acceptable\",\n  \"407\": \"Proxy Authentication Required\",\n  \"408\": \"Request Timeout\",\n  \"409\": \"Conflict\",\n  \"410\": \"Gone\",\n  \"411\": \"Length Required\",\n  \"412\": \"Precondition Failed\",\n  \"413\": \"Payload Too Large\",\n  \"414\": \"URI Too Long\",\n  \"415\": \"Unsupported Media Type\",\n  \"416\": \"Range Not Satisfiable\",\n  \"417\": \"Expectation Failed\",\n  \"418\": \"I'm a teapot\",\n  \"421\": \"Misdirected Request\",\n  \"422\": \"Unprocessable Entity\",\n  \"423\": \"Locked\",\n  \"424\": \"Failed Dependency\",\n  \"425\": \"Unordered Collection\",\n  \"426\": \"Upgrade Required\",\n  \"428\": \"Precondition Required\",\n  \"429\": \"Too Many Requests\",\n  \"431\": \"Request Header Fields Too Large\",\n  \"451\": \"Unavailable For Legal Reasons\",\n  \"500\": \"Internal Server Error\",\n  \"501\": \"Not Implemented\",\n  \"502\": \"Bad Gateway\",\n  \"503\": \"Service Unavailable\",\n  \"504\": \"Gateway Timeout\",\n  \"505\": \"HTTP Version Not Supported\",\n  \"506\": \"Variant Also Negotiates\",\n  \"507\": \"Insufficient Storage\",\n  \"508\": \"Loop Detected\",\n  \"509\": \"Bandwidth Limit Exceeded\",\n  \"510\": \"Not Extended\",\n  \"511\": \"Network Authentication Required\"\n};\n"
  },
  {
    "path": "src/panels/network/NetworkPanel.vue",
    "content": "<template>\n  <div class=\"network-panel\">\n    <div class=\"head\">\n      <span class=\"cell cell--long\" :style=\"{'max-width': `${4/6*100}vw`}\">\n        Name {{requestInfoList.length > 0 ? `(${requestInfoList.length})` : ''}}\n      </span>\n      <span class=\"cell\">Method</span>\n      <span class=\"cell\">Status</span>\n      <span v-if=\"showRequestType\" class=\"cell\">Type</span>\n    </div>\n    <div class=\"body\" v-prevent-bkg-scroll>\n      <NetworkRequest\n        v-for=\"(requestInfo, index) in requestInfoList\"\n        :key=\"requestInfo.id\"\n        :requestInfo=\"requestInfo\"\n        :isSelected=\"selectedId === requestInfo.id\"\n        :isEven=\"index % 2 === 0\"\n        :showRequestType=\"showRequestType\"\n        @click=\"onClickItem(requestInfo.id)\"\n      />\n    </div>\n    <VFootBar :buttons=\"footBarButtons\" class=\"foot\" />\n  </div>\n\n</template>\n\n<script>\nimport { VFootBar, VJSONViewer } from \"@/components\";\nimport { nextTick, Logger, eventBus, isFunction, uuid } from \"@/utils\";\nimport NetworkRequest from \"./NetworkRequest\";\nimport RequestType from \"./RequestType\";\n\nconst logger = new Logger(\"[NetworkPanel]\");\n\nconst ReadyState = Object.freeze({\n  UNSENT: 0,\n  OPENED: 1,\n  HEADERS_RECEIVED: 2,\n  LOADING: 3,\n  DONE: 4\n});\n\nconst DisplayStatus = Object.freeze({\n  UNSENT: \"-\",\n  PENDDING: \"(pendding)\",\n  LOADING: \"(loading)\",\n  FAIL: \"(failed)\"\n});\n\nexport default {\n  name: \"NetworkPanel\",\n  components: {\n    VFootBar,\n    VJSONViewer,\n    NetworkRequest\n  },\n  data() {\n    return {\n      // 请求列表\n      requestInfoMap: {\n        /*\n        * [id: number]: {\n        *   id: string     // 请求编号(UUID)\n        *   method: string // 请求方法\n        *   url: string    // 请求地址\n        *   requestHeaders: Object   // 请求 HTTP 头\n        *   body: string   // 请求参数\n        * \n        *   status: number // 状态码\n        *   statusText: string // 状态码描述\n        *   responseHeaders: Object  // 响应 HTTP 头\n        *   responseText: string  // 响应数据\n        * \n        *   displayStatus: string // 展示请求状态\n        *   activeTab: string  // 请求详情当前激活的面板\n        *   isExpand: boolean  // 是否展开请求详情\n        * }\n        */\n      },\n      // 选中的请求编号\n      selectedId: \"\",\n      // 是否显示请求类型(xhr, fetch)\n      showRequestType: false\n    };\n  },\n  computed: {\n    // 展示的列表（后面会按时间或类型进行排序）\n    requestInfoList() {\n      return Object.keys(this.requestInfoMap).map(key => this.requestInfoMap[key]);\n    },\n    /* eslint-disable */\n    footBarButtons() {\n      return [\n        {\n          text: \"Clear\",\n          click: () => {\n            this.requestInfoMap = {};\n          }\n        },\n        {\n          text: \"Hide\",\n          click: () => {\n            eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE);\n          }\n        }\n      ];\n    }\n    /* eslint-enable */\n  },\n  created() {\n    // 监听\"偏好设置\"变化\n    const bindOnSettingsChanged = this.onSettingsChanged.bind(this);\n    eventBus.on(eventBus.SETTINGS_LOADED, bindOnSettingsChanged);\n    eventBus.on(eventBus.SETTINGS_CHANGE, bindOnSettingsChanged);\n  },\n  mounted() {\n    // 拦截 XMLHttpRequest\n    this.hookXMLHttpRequest();\n\n    // 拦截 fetch 请求\n    this.hookFetch();\n  },\n  errorCaptured(error) {\n    logger.error(error);\n    return false;\n  },\n  methods: {\n    onClickItem(id) {\n      const item = this.requestInfoMap[id];\n      // 点击同一行，切换展开态\n      // 点击不同行，展开当前选中行，折叠之前选中行\n      if (id === this.selectedId) {\n        item.isExpand = !item.isExpand;\n      } else {\n        this.requestInfoMap[id].isExpand = true;\n        if (this.requestInfoMap[this.selectedId]) {\n          this.requestInfoMap[this.selectedId].isExpand = false;\n        }\n      }\n      this.selectedId = id;\n    },\n    /**\n     * 拦截 XMLHttpRequest 请求\n     *\n     * XMLHttpReqeust.prototype.open(method: string, url: string, async?: boolean, user?: string, password?: string)\n     * XMLHttpReqeust.prototype.send(body?: Object | null)\n     * XMLHttpReqeust.prototype.setRequestHeader(key: string, value: string)\n     * xhr.onreadystatechange\n     *\n     * XHR 调用 open() 后就会触发 onreadystatechange 变化，调用 send() 后开始发送请求并触发 onreadystatechange 变化，通过监听\n     * onreadystatechange 状态变化来跟踪请求的阶段以及获取返回数据。\n     *\n     * XHR 的请求地址、请求方法从 open() 方法中获取，post数据从 send() 方法中获取，HTTP请求头从 setRequestHeader 中获取\n     */\n    hookXMLHttpRequest() {\n      const vm = this;\n      const XMLHttpRequest = window.XMLHttpRequest;\n      const _open = XMLHttpRequest.prototype.open;\n      const _send = XMLHttpRequest.prototype.send;\n      const _setRequestHeaders = XMLHttpRequest.prototype.setRequestHeader;\n\n      XMLHttpRequest.prototype.open = function(method, url) {\n        const xhr = this;\n        const id = uuid();\n\n        // 保存数据在 xhr 实例中，方便后续获取\n        xhr.$id = id;\n        xhr.$method = method;\n        xhr.$url = url;\n        xhr.$displayStatus = DisplayStatus.UNSENT;\n\n        // 返回重写的 onreadystatechange 事件处理程序\n        const getOnReadyStateChange = () => {\n          const _onreadystatechange = xhr.onreadystatechange || function() {};\n          return function(...args) {\n            const requestInfo = vm.getRequestInfo(id);\n            switch (xhr.readyState) {\n              case ReadyState.UNSENT:\n              case ReadyState.OPENED:\n                // 在发送请求前，不会创建 requestInfo(避免显示到 UI 上)，此时如果状态变化，\n                // 先记录到 xhr 实例上，待调用 send() 后从实例上读取初始状态值创建 requestInfo 实例\n                xhr.$displayStatus = DisplayStatus.PENDDING;\n                break;\n              case ReadyState.HEADERS_RECEIVED:\n                requestInfo.displayStatus = DisplayStatus.LOADING;\n                const headers = xhr.getAllResponseHeaders();\n                const headerArr = headers.split(/[\\r\\n]+/);\n                const responseHeaders = {};\n                headerArr.forEach(line => {\n                  if (!line) return;\n                  const parts = line.split(\": \");\n                  const key = parts.shift();\n                  const value = parts.join(\": \");\n                  responseHeaders[key] = value;\n                });\n                requestInfo.responseHeaders = responseHeaders;\n                break;\n              case ReadyState.LOADING:\n                requestInfo.displayStatus = DisplayStatus.LOADING;\n                break;\n              case ReadyState.DONE:\n                requestInfo.status = xhr.status;\n                requestInfo.statusText = xhr.statusText;\n                requestInfo.displayStatus = xhr.status;\n                requestInfo.responseText = xhr.responseText;\n                break;\n              default:\n                break;\n            }\n\n            vm.updateRequestInfo(id, requestInfo);\n\n            _onreadystatechange.call(this, ...args);\n          };\n        };\n\n        // 如果 open() 方法调用前，onreadystatechange 已注册，可以立即重写\n        // 否则，在下一个微任务中重写，即等到用户注册后再执行\n        if (isFunction(xhr.onreadystatechange)) {\n          xhr.onreadystatechange = getOnReadyStateChange();\n        } else {\n          nextTick(() => {\n            xhr.onreadystatechange = getOnReadyStateChange();\n          });\n        }\n\n        _open.apply(this, arguments);\n      };\n\n      XMLHttpRequest.prototype.send = function(body) {\n        const xhr = this;\n        const method = String(xhr.$method).toUpperCase();\n        vm.addRequestInfo(xhr.$id, {\n          type: RequestType.XHR,\n          url: xhr.$url,\n          method,\n          displayStatus: xhr.$displayStatus,\n          body: method === \"GET\" || method === \"HEAD\" ? null : body,\n          requestHeaders: xhr.$requestHeaders\n        });\n\n        _send.apply(this, arguments);\n      };\n\n      XMLHttpRequest.prototype.setRequestHeader = function(key, value) {\n        const xhr = this;\n        const requestInfo = vm.getRequestInfo(xhr.$id);\n        // 如果调用 XHR 在 send() 之前调用 setRequestHeader()，此时 XHR 请求信息尚未创建，暂时挂载到 xhr 实例上\n        // 稍后调用 send() 创建请求信息时，再从实例中获取请求后信息进行初始化\n        if (requestInfo) {\n          vm.updateRequestInfo(xhr.$id, {\n            requestHeaders: {\n              ...requestInfo.requestHeaders,\n              [key]: value\n            }\n          });\n        } else {\n          const requestHeaders = xhr.$requestHeaders || {};\n          requestHeaders[key] = value;\n          xhr.$requestHeaders = requestHeaders;\n        }\n\n        _setRequestHeaders.apply(this, arguments);\n      };\n    },\n    /**\n     * 拦截 fetch 请求\n     *\n     * fetch(input: string | Request, init?: Object): Promise<Response>\n     * Request(input: string, init?: Object)\n     *\n     * fetch 有多种调用方式，hook 时都需要考虑到，且 fetch 中的 init 优先级高于 Request 中的 init\n     * 1) fetch(url)\n     * 2) fetch(url, init)\n     * 3) fetch(new Request(url))\n     * 4) fetch(new Request(url, init))\n     * 3) fetch(new Request(url, init), init)\n     *\n     * fetch 请求地址、请求方法、post数据、HTTP头都从 init 参数中获取\n     *\n     * fetch 没有 XHR 中的 readystate 来标志请求阶段，只能通过 Promise 已被 resolve 或 reject 了来判断请求已完成，从而读取请求结果。\n     */\n    hookFetch() {\n      if (!isFunction(window.fetch)) return;\n\n      const vm = this;\n      // save original \"fetch\"\n      const _fetch = window.fetch;\n      window.fetch = function(...args) {\n        // invoke original \"fetch\"\n        const resultPromise = _fetch.call(this, ...args);\n\n        const request = args[0] instanceof Request ? args[0] : { url: args[0] };\n        const init = args[1];\n\n        const id = uuid();\n        vm.addRequestInfo(id, {\n          type: RequestType.FETCH,\n          url: request.url,\n          method: init.method || request.method || \"GET\",\n          requestHeaders: init.headers || request.headers,\n          body: init.body || request.body || null\n        });\n\n        resultPromise.then(\n          response => {\n            return response.text().then(text => {\n              const responseHeaders = {};\n              if (isFunction(response.headers.entries)) {\n                for (const [key, value] of response.headers.entries()) {\n                  responseHeaders[key] = value;\n                }\n              }\n              vm.updateRequestInfo(id, {\n                displayStatus: response.status,\n                status: response.status,\n                statusText: response.statusText,\n                responseHeaders,\n                responseText: text\n              });\n            });\n          },\n          err => {\n            // network error\n            vm.updateRequestInfo(id, {\n              status: -1,\n              displayStatus: DisplayStatus.FAIL\n            });\n          }\n        );\n\n        // return result\n        return resultPromise;\n      };\n    },\n    addRequestInfo(\n      id,\n      {\n        type,\n        url,\n        method,\n        displayStatus = DisplayStatus.UNSENT,\n        status = 0,\n        statusText = \"\",\n        body = null,\n        requestHeaders = {},\n        responseHeaders = {},\n        isExpand = false,\n        activeTab = \"preview\"\n      }\n    ) {\n      if (type !== RequestType.XHR && type !== RequestType.FETCH) {\n        throw new Error('invalid arguments \"type\":', type);\n      }\n      this.$set(this.requestInfoMap, id, {\n        id,\n        type,\n        url,\n        method,\n        displayStatus,\n        status,\n        statusText,\n        body,\n        responseHeaders,\n        requestHeaders,\n        isExpand,\n        activeTab\n      });\n    },\n    updateRequestInfo(id, requestInfo = {}) {\n      const _requestInfo = this.requestInfoMap[id];\n      if (!_requestInfo) {\n        // logger.warn('invalid params:', ...arguments)\n        return;\n      }\n      if (Object.keys(requestInfo).length <= 0) {\n        return;\n      }\n      this.$set(this.requestInfoMap, id, { ..._requestInfo, ...requestInfo });\n    },\n    getRequestInfo(id) {\n      return this.requestInfoMap[id];\n    },\n    onSettingsChanged(settings) {\n      this.showRequestType = settings.showRequestType;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.network-panel {\n  height: $panel-height;\n  display: flex;\n  flex-direction: column;\n  .head {\n    flex: none;\n    display: flex;\n    flex-direction: row;\n    height: $list-row-height;\n    width: 100%;\n    .cell {\n      display: flex;\n      width: 100%;\n      height: 100%;\n      background-color: $toolbar-bg-color;\n      border-bottom: 1px solid $toolbar-border-color;\n      border-left: 1px solid $toolbar-border-color;\n      justify-content: left;\n      padding: 0px 4px;\n      align-items: center;\n      flex: 1 1;\n      &--long {\n        flex: 4 1;\n        display: inline-block;\n        text-overflow: ellipsis;\n        overflow-x: hidden;\n        overflow-y: hidden;\n        white-space: nowrap;\n        line-height: $list-row-height;\n      }\n      &:first-child {\n        border-left: none;\n      }\n      &:active {\n        background-color: $toolbar-border-color;\n      }\n    }\n  }\n  .body {\n    flex-grow: 1;\n    overflow-y: scroll;\n  }\n  .foot {\n    flex: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/network/NetworkRequest.vue",
    "content": "<template>\n  <div class=\"request\">\n    <div class=\"row\" @click=\"$emit('click')\" :class=\"{selected: isSelected, even: isEven, error: isStatusError(requestInfo)}\">\n      <span class=\"cell cell--long\" :style=\"{'max-width': `${4/6*100}vw`}\">{{requestInfo.url}}</span>\n      <span class=\"cell\">{{requestInfo.method}}</span>\n      <span class=\"cell\">{{requestInfo.displayStatus}}</span>\n      <span v-if=\"showRequestType\" class=\"cell\">{{requestInfo.type}}</span>\n    </div>\n    <div class=\"row-expand\" v-if=\"requestInfo.isExpand\">\n      <VTabBar v-model=\"requestInfo.activeTab\" :show-bottom-border=\"false\">\n        <VTabBarItem id=\"headers\">Headers</VTabBarItem>\n        <VTabBarItem id=\"preview\">Preview</VTabBarItem>\n        <VTabBarItem id=\"response\">Response</VTabBarItem>\n      </VTabBar>\n\n      <TabHeaders\n        v-show=\"requestInfo.activeTab === 'headers'\"\n        :requestInfo=\"requestInfo\"\n      />\n      <TabPreview\n        v-show=\"requestInfo.activeTab === 'preview'\"\n        :requestInfo=\"requestInfo\"\n      />\n      <TabResponse\n        v-show=\"requestInfo.activeTab === 'response'\"\n        :requestInfo=\"requestInfo\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport { VTabBar, VTabBarItem } from \"@/components\";\nimport TabHeaders from \"./TabHeaders\";\nimport TabResponse from \"./TabResponse\";\nimport TabPreview from \"./TabPreview\";\n\nexport default {\n  components: {\n    VTabBar,\n    VTabBarItem,\n    TabHeaders,\n    TabResponse,\n    TabPreview\n  },\n  name: \"NetworkRequest\",\n  props: {\n    // 请求信息\n    requestInfo: {\n      type: Object,\n      required: true\n    },\n    isSelected: {\n      type: Boolean,\n      default: false\n    },\n    isEven: {\n      type: Boolean,\n      default: false\n    },\n    showRequestType: {\n      type: Boolean,\n      required: true\n    }\n  },\n  methods: {\n    isStatusError(requestInfo) {\n      if (requestInfo.status >= 400 && requestInfo.status < 600) {\n        return true;\n      }\n      if (requestInfo.status === -1) {\n        return true;\n      }\n\n      return false;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n@import \"../../styles/mixins\";\n\n$status-error-color: rgb(230, 0, 0);\n.request {\n  display: flex;\n  flex-direction: column;\n  .row {\n    width: 100%;\n    height: $list-row-height;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    &.error {\n      @include descendant-attr(\"color\", $status-error-color);\n    }\n    &.even {\n      background-color: rgb(245, 245, 245);\n    }\n    &.selected {\n      @include descendant-attr(\"color\", white);\n      background-color: #2196f3;\n    }\n    .cell {\n      display: flex;\n      width: 100%;\n      height: 100%;\n      justify-content: left;\n      padding: 0px 4px;\n      align-items: center;\n      flex: 1 1;\n      &--long {\n        flex: 4 1;\n        display: inline-block;\n        text-overflow: ellipsis;\n        overflow-x: hidden;\n        white-space: nowrap;\n        line-height: $list-row-height;\n      }\n    }\n  }\n  .row-expand {\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/network/RequestType.js",
    "content": "// 请求类型\nexport default Object.freeze({\n  XHR: \"xhr\",\n  FETCH: \"fetch\"\n});\n"
  },
  {
    "path": "src/panels/network/TabHeaders.vue",
    "content": "<template>\n  <div class=\"tab-headers\">\n    <div class=\"section\">\n      <div class=\"header\">\n        <div class=\"title\">General</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"line\" v-for=\"item in sectionGeneral\" :key=\"item.key\">\n          <span class=\"name\">{{item.key}}: </span>\n          <span class=\"source-code\">\n            <img v-if=\"item.icon\" class=\"icon\" :src=\"item.icon\" />{{item.value}}\n          </span>\n        </div>\n      </div>\n    </div>\n\n    <div v-if=\"sectionResponseHeaders.length > 0\" class=\"section\">\n      <div class=\"header\">\n        <div class=\"title\">Response Headers</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"line\" v-for=\"item in sectionResponseHeaders\" :key=\"item.key\">\n          <span class=\"name\">{{item.key}}: </span>\n          <span class=\"source-code\">{{item.value}}</span>\n        </div>\n      </div>\n    </div>\n\n    <div v-if=\"sectionRequestHeaders.length > 0\" class=\"section\">\n      <div class=\"header\">\n        <div class=\"title\">Request Headers</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"line\" v-for=\"item in sectionRequestHeaders\" :key=\"item.key\">\n          <span class=\"name\">{{item.key}}: </span>\n          <span class=\"source-code\">{{item.value}}</span>\n        </div>\n      </div>\n    </div>\n\n    <div v-if=\"sectionQueryStringParameters.length > 0\" class=\"section\">\n      <div class=\"header\">\n        <div class=\"title\">Query String Parameters</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"line\" v-for=\"item in sectionQueryStringParameters\" :key=\"item.key\">\n          <span class=\"name\">{{item.key}}: </span>\n          <span class=\"source-code\">{{item.value}}</span>\n        </div>\n      </div>\n    </div>\n\n    <div v-if=\"sectionRequestPayload.headers.length > 0\" class=\"section\">\n      <div class=\"header\">\n        <div class=\"title\">{{sectionRequestPayload.title}}</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"line\" v-for=\"item in sectionRequestPayload.headers\" :key=\"item.key\">\n          <span v-if=\"item.key\" class=\"name\">{{item.key}}: </span>\n          <span class=\"source-code\">{{item.value}}</span>\n        </div>\n      </div>\n    </div>\n\n  </div>  \n</template>\n\n<script>\nimport URLSearchParams from \"url-search-params\";\nimport HttpStatus from \"./HttpStatus\";\n\nexport default {\n  props: {\n    requestInfo: {\n      type: Object,\n      required: true\n    }\n  },\n  computed: {\n    sectionGeneral() {\n      const requestInfo = this.requestInfo;\n      const arr = [];\n      arr.push({ key: \"Request URL\", value: requestInfo.url });\n\n      const status = requestInfo.status;\n      const statusText = requestInfo.statusText || HttpStatus[status];\n      const statusRange = parseInt(status / 100);\n      if (statusRange === 2 || statusRange === 3 || statusRange === 4 || statusRange === 5) {\n        arr.push({\n          key: \"Request Method\",\n          value: (requestInfo.method || \"\").toUpperCase()\n        });\n        arr.push({\n          key: \"Status Code\",\n          value: `${status} ${statusText}`,\n          icon: require(`@/assets/icons/${statusRange}xx.png`)\n        });\n      }\n      return arr;\n    },\n    sectionResponseHeaders() {\n      const responseHeaders = this.requestInfo.responseHeaders;\n      return Object.keys(responseHeaders).reduce((arr, key) => {\n        arr.push({\n          key: key\n            .split(\"-\")\n            .map(v => v[0].toUpperCase() + v.slice(1))\n            .join(\"-\"),\n          value: responseHeaders[key]\n        });\n        return arr;\n      }, []);\n    },\n    sectionRequestHeaders() {\n      const requestHeaders = this.requestInfo.requestHeaders;\n      return Object.keys(requestHeaders).reduce((arr, key) => {\n        arr.push({ key, value: requestHeaders[key] });\n        return arr;\n      }, []);\n    },\n    sectionQueryStringParameters() {\n      const url = this.requestInfo.url;\n      if (!url) return [];\n\n      const arr = [];\n      const index = url.indexOf(\"?\");\n      if (index !== -1) {\n        const searchPart = url.slice(index + 1);\n        const params = new URLSearchParams(searchPart);\n        for (let pair of params.entries()) {\n          arr.push({ key: pair[0], value: pair[1] });\n        }\n      }\n      return arr;\n    },\n    sectionRequestPayload() {\n      const result = {\n        title: \"Request Payload\",\n        headers: []\n      };\n\n      const body = this.requestInfo.body;\n      if (!body) return result;\n\n      const requestHeaders = this.requestInfo.requestHeaders;\n      const mimeType = (requestHeaders[\"Content-Type\"] || \"\").split(\";\")[0].replace(/(^\\s+)|(\\s+$)/g, \"\");\n      switch (mimeType) {\n        case \"application/x-www-form-urlencoded\":\n          result.title = \"Form Data\";\n          const params = new URLSearchParams(body);\n          for (let pair of params.entries()) {\n            result.headers.push({\n              key: decodeURIComponent(pair[0]),\n              value: decodeURIComponent(pair[1])\n            });\n          }\n          break;\n        case \"application/json\":\n          try {\n            result.headers.push({ value: JSON.parse(body) });\n          } catch (error) {\n            result.headers.push({ value: body });\n          }\n          break;\n        default:\n          // raw text\n          result.headers.push({ value: body });\n          break;\n      }\n\n      return result;\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n\n.tab-headers {\n  margin: 0 5px;\n  display: flex;\n  flex-direction: column;\n  color: rgb(33%, 33%, 33%);\n  height: 75vw;\n  overflow: scroll;\n  .section {\n    padding-bottom: 5px;\n    border-bottom: solid 1px #e0e0e0;\n    .header {\n      display: flex;\n      flex-direction: row;\n      margin-top: 1px;\n      line-height: $primary-font-size * 5 / 3;\n      .title {\n        font-weight: bold;\n      }\n    }\n    .body {\n      padding-left: 5px;\n      .line {\n        margin-top: 1px;\n        min-height: $primary-font-size;\n        line-height: $primary-font-size * 5 / 3;\n        .name {\n          font-weight: bold;\n          margin-right: 0.25em;\n          white-space: nowrap;\n        }\n        .icon {\n          width: $primary-font-size * 4 / 5;\n          height: $primary-font-size * 4 / 5;\n          margin-right: 4px;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/network/TabPreview.vue",
    "content": "<template>\n  <div class=\"tab-preview\">\n    <div v-if=\"isImage\" class=\"image-container\">\n      <img :src=\"requestInfo.url\" />\n    </div>\n    <div v-else-if=\"isPlain\" class=\"html-container\">\n      <iframe :src=\"'data:text/plain,' + content\" sandbox=\"\" />\n    </div>\n    <div v-else-if=\"isHtml\" class=\"html-container\">\n      <iframe :src=\"'data:text/html,' + content\" sandbox=\"\" />\n    </div>\n    <div v-else-if=\"isJSON && isValidJSON(content)\">\n      <VJSONViewer :value=\"parseJSON(content)\" />\n    </div>\n    <div v-else>\n      <VHighlightView class=\"data\" :language=\"language\" :code=\"content\" />\n    </div>\n  </div>\n</template>\n\n\n<script>\nimport { VJSONViewer, VHighlightView } from \"@/components\";\nimport { mimeType2Language } from \"@/utils\";\n\nexport default {\n  components: {\n    VJSONViewer,\n    VHighlightView\n  },\n  props: {\n    requestInfo: {\n      type: Object,\n      required: true\n    }\n  },\n  computed: {\n    content() {\n      return this.requestInfo.responseText;\n    },\n    contentType() {\n      return this.requestInfo.responseHeaders[\"content-type\"];\n    },\n    language() {\n      return mimeType2Language(this.contentType);\n    },\n    isImage() {\n      return /image/.test(this.contentType);\n    },\n    isPlain() {\n      return /plain/.test(this.contentType);\n    },\n    isHtml() {\n      return /html/.test(this.contentType);\n    },\n    isJSON() {\n      return /json/.test(this.contentType);\n    }\n  },\n  methods: {\n    isValidJSON(text) {\n      try {\n        JSON.parse(text);\n        return true;\n      } catch (error) {\n        return false;\n      }\n    },\n    parseJSON(text) {\n      return JSON.parse(text);\n    }\n  }\n};\n</script>\n\n\n<style lang=\"scss\" scoped>\n.tab-preview {\n  height: 75vw;\n  overflow: scroll;\n  .image-container {\n    height: 100%;\n    padding: 20px 20px 10px 20px;\n    text-align: center;\n    img {\n      box-shadow: 0 5px 10px rgba(0, 0, 0, 0.5);\n      background: url(\"../../assets/icons/transparent_bg.png\");\n      background-repeat: repeat;\n    }\n  }\n  .html-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    iframe {\n      border: none;\n      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);\n      flex-grow: 1;\n      margin: 20px;\n    }\n  }\n  .data {\n    overflow-x: scroll;\n  }\n  .no-data {\n    color: hsla(0, 0%, 65%, 1);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/network/TabResponse.vue",
    "content": "<template>\n  <div class=\"tab-response\">\n    <VHighlightView\n      v-if=\"isTextData\"\n      class=\"data\"\n      :language=\"language\"\n      :code=\"content\"\n    />\n    <div v-else class=\"no-data\">\n      <h2>This request has no response data available.</h2>\n    </div>\n  </div>\n</template>\n\n\n<script>\nimport { VHighlightView } from \"@/components\";\nimport { mimeType2Language } from \"@/utils\";\nimport RequestType from \"./RequestType\";\n\nexport default {\n  components: {\n    VHighlightView\n  },\n  props: {\n    requestInfo: {\n      type: Object,\n      required: true\n    }\n  },\n  computed: {\n    content() {\n      return this.requestInfo.responseText;\n    },\n    contentType() {\n      return this.requestInfo.responseHeaders[\"content-type\"];\n    },\n    language() {\n      return mimeType2Language(this.contentType);\n    },\n    isTextData() {\n      const contentType = this.contentType;\n      switch (true) {\n        case /image/.test(contentType):\n          return false;\n        default:\n          return true;\n      }\n    }\n  }\n};\n</script>\n\n\n<style lang=\"scss\" scoped>\n.tab-response {\n  height: 75vw;\n  overflow: scroll;\n  .data {\n    overflow-x: scroll;\n    height: 100%;\n    pre,\n    code {\n      height: 100%;\n    }\n  }\n  .no-data {\n    height: 100%;\n    color: hsla(0, 0%, 65%, 1);\n    h1 {\n      height: 100%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/panels/settings/SettingsPanel.vue",
    "content": "<template>\n  <div v-if=\"value\" class=\"settings-panel\">\n    <div class=\"toolbar\">\n      <VIcon @click=\"onClickClose\" name=\"close\" style=\"width: 20px\" />\n    </div>\n    <div class=\"main\">\n      <!-- 侧边栏 -->\n      <div class=\"side\">\n        <div class=\"title\">Settings</div>\n        <div\n          v-for=\"(cfg, index) in configs\"\n          :key=\"cfg.title\"\n          @click=\"activedIndex = index\"\n          :class=\"{selected: activedIndex === index}\"\n          class=\"item\"\n          >\n          <span>{{cfg.desc}}</span>\n        </div>\n      </div>\n      <!-- 设置选项 -->\n      <div class=\"content\">\n        <div class=\"title\">\n          <span>{{activedConfig.desc}}</span>\n        </div>\n        <div\n          v-for=\"(setting, index) in activedConfig.items || []\"\n          :key=\"index\"\n          class=\"setting\"\n          :class=\"setting.type\"\n        >\n          <template v-if=\"setting.type === 'section'\">\n            <div class=\"section\">{{setting.desc}}</div>\n          </template>\n          <template v-else-if=\"setting.type === 'text'\">\n            <div class=\"text\">{{setting.desc}}</div>\n          </template>\n          <template v-else-if=\"setting.type === 'checkbox'\">\n            <input type=\"checkbox\" v-model=\"setting.value\" :id=\"index\" @change=\"onSettingsChanged\" />\n            <label :for=\"index\">&nbsp;{{setting.desc}}</label>\n          </template>\n          <template v-else-if=\"setting.type === 'select'\">\n            <span>{{setting.desc}}</span>\n            <select v-model=\"setting.value\" @change=\"onSettingsChanged\">\n              <option disabled value=\"\">Select</option>\n              <option\n                v-for=\"option in setting.options\"\n                :key=\"option.name\"\n                :value=\"option.value\">\n                {{option.text}}\n              </option>\n            </select>\n          </template>\n          <template v-else-if=\"setting.type === 'link'\">\n            <a :href=\"setting.src\" alt=\"\" target=\"_bank\">{{setting.desc}}</a>\n          </template>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { VIcon } from \"@/components\";\nimport { eventBus, Logger, cloneDeep } from \"@/utils\";\nimport { pluginManager } from \"@/plugins\";\n\nconst logger = new Logger(\"[SettingsPanel]\");\nconst KEY_SETTINGS = \"web-console:settings\";\n\n/**\n * 默认配置，一组配置段，每个配置段包含一个或多个表单配置项\n * name 配置段名称\n * desc 配置段描述\n * items 表单配置项列表\n *   type 表单类型\n *   desc 字段描述\n *   name 字段名\n *   value 字段值\n */\nconst defaultConfigs = [\n  {\n    name: \"preferences\",\n    desc: \"Preferences\",\n    items: [\n      { type: \"section\", desc: \"Console\" },\n      {\n        type: \"checkbox\",\n        name: \"showTimestamps\",\n        value: false,\n        desc: \"Show timestamps\"\n      },\n      {\n        type: \"select\",\n        name: \"maxMsgCount\",\n        value: 400,\n        desc: \"Max message count: \",\n        options: [\n          { text: \"200\", value: 200 },\n          { text: \"400\", value: 400 },\n          { text: \"800\", value: 800 },\n          { text: \"1600\", value: 1600 },\n          { text: \"Infinity\", value: Number.MAX_VALUE }\n        ]\n      },\n      { type: \"section\", desc: \"Network\" },\n      {\n        type: \"checkbox\",\n        name: \"showRequestType\",\n        value: false,\n        desc: \"Show request type\"\n      },\n      { type: \"section\", desc: \"Application\" },\n      {\n        type: \"checkbox\",\n        name: \"showApplicationToolbar\",\n        value: false,\n        desc: \"Always show toolbar\"\n      }\n    ]\n  },\n  {\n    desc: \"About\",\n    items: [\n      { type: \"section\", desc: \"Package Name\" },\n      { type: \"link\", desc: process.env.VUE_APP_NAME, src: \"https://github.com/whinc/web-console\" },\n      { type: \"section\", desc: \"Version\" },\n      {\n        type: \"link\",\n        desc: process.env.VUE_APP_VERSION,\n        src: \"https://www.npmjs.com/package/@whinc/web-console/v/\" + process.env.VUE_APP_VERSION\n      },\n      { type: \"section\", desc: \"Build Date\" },\n      { type: \"text\", desc: process.env.VUE_APP_DATE },\n      { type: \"section\", desc: \"CHANGELOG\" },\n      { type: \"link\", desc: \"CHANGELOG\", src: \"https://github.com/whinc/web-console/blob/master/CHANGELOG.md\" },\n      { type: \"section\", desc: \"Feedback\" },\n      { type: \"link\", desc: \"New Issue\", src: \"https://github.com/whinc/web-console/issues/new\" }\n    ]\n  }\n];\n\nexport default {\n  name: \"SettingsPanel\",\n  components: {\n    VIcon\n  },\n  props: {\n    // 是否可见，支持 v-model\n    value: Boolean\n  },\n  data() {\n    return {\n      activedIndex: 0,\n      /**\n       * settings 的 UI 配置\n       */\n      configs: cloneDeep(defaultConfigs)\n    };\n  },\n  computed: {\n    activedConfig() {\n      return this.configs[this.activedIndex];\n    }\n  },\n  beforeMount() {\n    // 安装插件的设置项\n    this.installPluginSettings();\n    // 加载配置\n    this.loadSettings(settings => {\n      pluginManager.updateSettings(settings);\n      eventBus.emit(eventBus.SETTINGS_LOADED, settings || {});\n      return;\n    });\n  },\n  methods: {\n    installPluginSettings() {\n      const pluginSettings = [];\n      const plugins = pluginManager.getPlugins();\n      plugins.forEach(plugin => {\n        if (!Array.isArray(plugin.settings) || plugin.settings.length === 0) return;\n        pluginSettings.push({ type: \"section\", desc: plugin.name }, ...plugin.settings);\n      });\n\n      this.configs[0].items.push(...pluginSettings);\n    },\n    // 通知配置更新\n    onSettingsChanged() {\n      const settings = this.extractSettings();\n      pluginManager.updateSettings(settings);\n      eventBus.emit(eventBus.SETTINGS_CHANGE, settings);\n      // logger.log('%o --extract--> %o --expand--> %o', this.configs, settings, this.recoverSettings(settings))\n      // logger.log(\"extractSettings:\", settings);\n    },\n    onClickClose() {\n      // 是否可见，支持 v-model\n      this.$emit(\"input\", false);\n\n      this.saveSettings();\n    },\n    saveSettings() {\n      const settings = this.extractSettings();\n      window.localStorage.setItem(KEY_SETTINGS, JSON.stringify(settings));\n    },\n    loadSettings(cb) {\n      const content = window.localStorage.getItem(KEY_SETTINGS);\n      if (!content) {\n        cb && cb();\n        return;\n      }\n      try {\n        const settings = JSON.parse(content);\n        this.recoverSettings(settings);\n        cb && cb(settings);\n      } catch (err) {\n        logger.error(err);\n        cb && cb();\n      }\n    },\n    /**\n     * 从 UI 配置项中提取设置项\n     * [\n     *  {\n     *    setting: [\n     *      {\n     *        name: 'a1',\n     *        value: 'v1'\n     *      },\n     *      {\n     *        name: 'b1',\n     *        value: 'v2'\n     *      }\n     *    ]\n     *  }\n     * ]\n     *\n     * 提取设置：\n     * {\n     *  a1: v1,\n     *  b1: v2\n     * }\n     */\n    extractSettings() {\n      const settings = {};\n      this.configs.forEach(config => {\n        config.items.forEach(item => {\n          if (item.name) {\n            settings[item.name] = item.value;\n          }\n        });\n      });\n      return settings;\n    },\n    // extractSettings 的逆过程：将设置项恢复到 UI 配置项中\n    recoverSettings(settings) {\n      this.configs.forEach(config => {\n        config.items.forEach(item => {\n          if (item.name in settings) {\n            item.value = settings[item.name];\n          }\n        });\n      });\n    }\n  }\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../styles/variables\";\n$primary-text-color: rgb(48, 57, 66);\n$second-text-color: #777;\n.settings-panel {\n  $gap: 4px;\n  position: absolute;\n  top: $gap;\n  left: $gap;\n  bottom: $gap;\n  right: $gap;\n  background-color: white;\n  box-shadow: 0 0 0.61538462em rgba(0, 0, 0, 0.4);\n  display: flex;\n  flex-direction: column;\n  .toolbar {\n    display: flex;\n    flex-direction: row;\n    justify-content: flex-end;\n    min-height: 30px;\n    padding: 5px;\n  }\n  .main {\n    display: flex;\n    flex-direction: row;\n    .side {\n      flex: 0 0 30vw;\n      display: flex;\n      flex-direction: column;\n      .title {\n        font-size: $primary-font-size * 1.5;\n        color: $primary-text-color;\n        white-space: nowrap;\n        padding: 0 0 10px 10px;\n      }\n      .item {\n        font-size: $primary-font-size;\n        flex: 0 0 $primary-font-size * 2;\n        padding: 4px 5px;\n        color: $second-text-color;\n        border-left: 6px solid #ffff;\n        display: flex;\n        align-items: center;\n        justify-content: flex-start;\n        &.selected {\n          color: $primary-text-color;\n          border-left: 6px solid #666;\n        }\n      }\n    }\n    .content {\n      flex: 1 1 auto;\n      display: flex;\n      flex-direction: column;\n      $margin-left: 15px;\n      overflow-y: auto;\n      .title {\n        font-size: $primary-font-size * 1.5;\n        color: $primary-text-color;\n        white-space: nowrap;\n        padding-bottom: 6px;\n        margin-bottom: 10px;\n        border-bottom: 1px solid #eeeeee;\n      }\n      .setting {\n        margin-bottom: 12px;\n        &.section {\n          font-size: 110%;\n          color: #222;\n        }\n        &.text {\n          padding-left: $margin-left;\n        }\n        &.checkbox {\n          flex: 0 0 auto;\n          padding-left: $margin-left;\n          display: flex;\n          align-items: center;\n          input {\n            width: $primary-font-size;\n            height: $primary-font-size;\n          }\n        }\n        &.select {\n          flex: 0 0 auto;\n          padding-left: $margin-left;\n          display: flex;\n          align-items: center;\n          select {\n            background-color: transparent;\n            border: 1px solid rgba(0, 0, 0, 0.2);\n            color: #333;\n            border-radius: 2px;\n            padding: 0 5px;\n            margin: 0 0 0 10px;\n          }\n        }\n        &.link {\n          padding-left: $margin-left;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/plugins/Plugin.js",
    "content": "/**\n * 插件基类\n */\nimport { isFunction } from \"@/utils\";\nimport { VFootBar, VIcon, VTabBar, VTabBarItem, VHighlightView, VJSONViewer } from \"@/components\";\nimport pluginEvents from \"./pluginEvents\";\n\nexport default class Plugin {\n  constructor({ id, name, settings, component }) {\n    this.id = id;\n    this.name = name || this.id;\n    this.settings = isFunction(settings) ? settings.call(this) : settings;\n    this.component = isFunction(component) ? component.call(this) : component;\n  }\n\n  __init__(pluginMgr) {\n    // const plugin = this;\n    const component = this.component;\n\n    this.component = {\n      ...component,\n      name: component.name || this.id,\n      mixins: [\n        ...(component.mixins || []),\n        {\n          components: {\n            VFootBar,\n            VHighlightView,\n            VIcon,\n            VTabBar,\n            VTabBarItem,\n            VJSONViewer\n          },\n          created() {\n            // created 周期函数触发时，Vue 已完成事件部署\n            const vm = this;\n            // 注册插件生命周期方法\n            // 当接收到特定事件时，自动触发相应的插件生命周期方法\n            Object.keys(pluginEvents)\n              .map(key => pluginEvents[key])\n              .forEach(event => {\n                pluginMgr.on(event, (...args) => {\n                  if (isFunction(vm[event])) {\n                    vm[event].call(vm, pluginMgr.hostProxy, ...args);\n                  }\n                });\n              });\n          } // created\n        }\n      ] // mixins\n    };\n\n    // 暂不支持\n    // Object.keys(pluginEvents)\n    //   .map(key => pluginEvents[key])\n    //   .forEach(event => {\n    //     pluginMgr.on(event, (...args) => {\n    //       if (isFunction(plugin[event])) {\n    //         plugin[event].call(plugin, pluginMgr.hostProxy, ...args);\n    //       }\n    //     });\n    //   });\n  }\n\n  // [pluginEvents.WEB_CONSOLE_READY]() {}\n  // [pluginEvents.WEB_CONSOLE_SHOW]() {}\n  // [pluginEvents.WEB_CONSOLE_HIDE]() {}\n  // [pluginEvents.WEB_CONSOLE_TAB_CHANGED]() {}\n  // [pluginEvents.WEB_CONSOLE_SETTINGS_LOADED]() {}\n  // [pluginEvents.WEB_CONSOLE_SETTINGS_CHANGED]() {}\n\n  toString() {\n    return this.id + \"(\" + this.name + \")\";\n  }\n}\n"
  },
  {
    "path": "src/plugins/index.js",
    "content": "export { default as pluginManager } from \"./pluginManager\";\nexport { default as pluginEvents } from \"./pluginEvents\";\nexport { default as Plugin } from \"./Plugin\";\n"
  },
  {
    "path": "src/plugins/pluginEvents.js",
    "content": "/**\n * 插件事件\n *\n * 约定：事件名称与插件的生命周期方法同名\n */\nexport default Object.freeze({\n  // 插件 DOM 已渲染\n  WEB_CONSOLE_READY: \"onWebConsoleReady\",\n  WEB_CONSOLE_SHOW: \"onWebConsoleShow\",\n  WEB_CONSOLE_HIDE: \"onWebConsoleHide\",\n  WEB_CONSOLE_TAB_CHANGED: \"onWebConsoleTabChanged\",\n  WEB_CONSOLE_SETTINGS_LOADED: \"onWebConsoleSettingsLoaded\",\n  WEB_CONSOLE_SETTINGS_CHANGED: \"onWebConsoleSettingsChanged\"\n});\n"
  },
  {
    "path": "src/plugins/pluginManager.js",
    "content": "/**\n * 插件管理\n */\nimport { eventBus, EventBus } from \"@/utils\";\nimport Plugin from \"./Plugin\";\n\nclass PluginManager extends EventBus {\n  constructor(...args) {\n    super(...args);\n    /**\n     * 插件集合\n     * @type {Array<Plugin>}\n     */\n    this._plugins = [];\n    // 当前最新的设置\n    this._settings = {};\n    // 宿主代理\n    this._hostProxy = null;\n  }\n\n  // 宿主代理，提供一些操作 web-console 的方法\n  get hostProxy() {\n    if (!this._hostProxy) {\n      this._hostProxy = {\n        hidePanel: () => eventBus.emit(eventBus.REQUEST_WEB_CONSOLE_HIDE),\n        getSettings: () => this._settings\n      };\n    }\n    return this._hostProxy;\n  }\n\n  /**\n   * 注册插件\n   * @param {Plugin} plugin\n   */\n  addPlugin(plugin) {\n    if (!(plugin instanceof Plugin)) {\n      console.warn(\"Invalid plugin: plugin should inherit WebConsole.Plugin\");\n      return;\n    }\n\n    if (!plugin.id) {\n      console.warn(`Empty plugin id: plugin id must not be empty and must be unique among all plugins`);\n      return;\n    }\n\n    if (this._plugins.find(v => v.id === plugin.id)) {\n      console.warn(`Plugin conflict: plugin id \"${plugin.id}\" has existed`);\n      return;\n    }\n\n    plugin.__init__(this);\n    this._plugins.push(plugin);\n  }\n\n  getPlugins() {\n    return this._plugins;\n  }\n\n  updateSettings(settings) {\n    this._settings = settings;\n  }\n\n  getSettings() {\n    return this._settings;\n  }\n\n  hasPlugin(pluginId) {\n    return this._plugins.findIndex(plugin => plugin.id === pluginId) !== -1;\n  }\n\n  toString() {\n    return JSON.stringify(this._plugins, null, 4);\n  }\n}\n\nexport default new PluginManager();\n"
  },
  {
    "path": "src/polyfill.js",
    "content": "if (Element.prototype.matches === undefined) {\n  Element.prototype.matches =\n    Element.prototype.matchesSelector ||\n    Element.prototype.mozMatchesSelector ||\n    Element.prototype.msMatchesSelector ||\n    Element.prototype.oMatchesSelector ||\n    Element.prototype.webkitMatchesSelector ||\n    function(s) {\n      var matches = (this.document || this.ownerDocument).querySelectorAll(s),\n        i = matches.length;\n      while (--i >= 0 && matches.item(i) !== this);\n      return i > -1;\n    };\n}\n\nif (Element.prototype.getAttributeNames == undefined) {\n  Element.prototype.getAttributeNames = function() {\n    var attributes = this.attributes;\n    var length = attributes.length;\n    var result = new Array(length);\n    for (var i = 0; i < length; i++) {\n      result[i] = attributes[i].name;\n    }\n    return result;\n  };\n}\n"
  },
  {
    "path": "src/styles/_global.scss",
    "content": "@import \"variables\";\n\n// 全局样式\n.web-console {\n  font-size: $primary-font-size;\n  font-family: $font-family;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  color: $default-color;\n  .g-source-code,\n  .source-code {\n    font-size: $source-code-font-size !important;\n    font-family: Menlo, Consolas, monospace;\n  }\n\n  .platform-linux {\n    color: rgb(48, 57, 66);\n    font-family: Roboto, Ubuntu, Arial, sans-serif;\n  }\n  .platform-mac {\n    color: rgb(48, 57, 66);\n    font-family: \".SFNSDisplay-Regular\", \"Helvetica Neue\", \"Lucida Grande\", sans-serif;\n  }\n  .platform-windows {\n    font-family: \"Segoe UI\", Tahoma, sans-serif;\n  }\n\n  // 隐藏滚动条\n  .g-hide-scrollbar::-webkit-scrollbar {\n    display: none;\n    overflow: -moz-scrollbars-none;\n  }\n\n  // 重置默认样式\n  * {\n    box-sizing: border-box;\n    user-select: auto;\n    // IOS：移除默认的触摸高亮样式\n    -webkit-tap-highlight-color: transparent;\n    &:focus {\n      outline: none;\n    }\n  }\n\n  input,\n  select,\n  textarea,\n  button {\n    font-size: $primary-font-size;\n    font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    border: none;\n    outline: none;\n  }\n\n  pre {\n    margin: 0;\n  }\n\n  // Fix: <br> 标签不换行导致的样式问题（如 Element 面板的盒模型坍塌）\n  br:before {\n    content: \"\\A\";\n    white-space: pre-line;\n  }\n  br:after {\n    white-space: pre-line;\n  }\n}\n"
  },
  {
    "path": "src/styles/_mixins.scss",
    "content": "// 隐藏滚动条（刷新后生效）\n@mixin hide-scrollbar {\n  &::-webkit-scrollbar {\n    display: none;\n    overflow: -moz-scrollbars-none;\n  }\n}\n\n// 给当前元素以及后代元素设置属性\n@mixin descendant-attr($name, $value) {\n  #{$name}: $value;\n  /deep/ * {\n    #{$name}: $value;\n  }\n}\n\n// 文本超出父容器时显示省略号\n@mixin text-ellipsis {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n// 文本超出父容器时可滚动\n@mixin text-scroll {\n  white-space: nowrap;\n  overflow: auto;\n}\n\n// input 输入框样式\n@mixin input($height: 1.8em) {\n  height: $height;\n  outline: none;\n  color: #5a5a5a;\n  background-color: white;\n  border: 1px solid transparent;\n  &::placeholder {\n    color: rgb(128, 128, 128);\n  }\n  &:focus {\n    border: 1px solid #2196f3;\n  }\n}\n\n// 初始化元素属性，避免宿主页面与选择器同名时受影响\n@mixin initial() {\n  width: initial;\n  height: initial;\n  padding: initial;\n  margin: initial;\n  border: initial;\n  position: initial;\n}\n"
  },
  {
    "path": "src/styles/_triangles.scss",
    "content": "@mixin triangles-collapse($border-width: 4px, $color: black) {\n  display: inline-block;\n  content: \"\";\n  width: 0;\n  height: 0;\n  /* 等边三角形，tan(30) 约为 0.5773502691896257 */\n  border-left: $border-width solid $color;\n  border-top: $border-width * 0.8 solid transparent;\n  border-bottom: $border-width * 0.8 solid transparent;\n}\n\n@mixin triangles-expand($border-width: 4px, $color: black) {\n  display: inline-block;\n  content: \"\";\n  width: 0;\n  height: 0;\n  /* 等边三角形，tan(30) 约为 0.5773502691896257 */\n  border-top: $border-width solid $color;\n  border-left: $border-width * 0.8 solid transparent;\n  border-right: $border-width * 0.8 solid transparent;\n}\n"
  },
  {
    "path": "src/styles/_variables.scss",
    "content": "/* web-console UI structure */\n$panel-height: 75vh;\n\n$footbar-height: 40px;\n\n$tabbar-height: 40px;\n\n$list-row-height: 38px;\n\n/* color */\n$default-color: rgb(48, 57, 66);\n\n$accent-color: #03a9f4;\n$accent-color-b: #2196f3;\n$accent-color-c: #3e82f7;\n\n$toolbar-bg-color: rgb(243, 243, 243);\n$toolbar-hover-bg-color: #eaeaea;\n$toolbar-border-color: rgb(205, 205, 205);\n\n$selection-fg-color: white;\n$selection-bg-color: $accent-color-b;\n$selection-inactive-bg-color: #dadada;\n\n$tab-selected-fg-color: #333;\n$tab-selected-bg-color: $toolbar-bg-color;\n\n$drop-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.1);\n\n$divider-color: #d0d0d0;\n\n$tabbar-bg-color: $toolbar-bg-color;\n$tabbar-border-color: $toolbar-border-color;\n$tab-fg-color: rgb(90, 90, 90);\n$tab-selected-fg-color: rgb(51, 51, 51);\n\n$dom-tag-name-color: rgb(136, 18, 128);\n$dom-attribute-name-color: rgb(153, 69, 0);\n$dom-attribute-value-color: rgb(26, 26, 166);\n$dom-link-color: rgb(17, 85, 204);\n\n/* font */\n$primary-font-size: 15px;\n$secondary-font-size: 13px;\n$source-code-font-size: 14px;\n$font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n"
  },
  {
    "path": "src/utils/EventBus.js",
    "content": "import Vue from \"vue\";\nimport Logger from \"./Logger\";\n\nconst logger = new Logger(\"[EventBus]\");\nclass EventBus {\n  constructor(events = {}) {\n    this._vue = new Vue();\n\n    Object.assign(this, events);\n    // this._eventNameList = Object.keys(events).map(name => events[name]);\n  }\n  emit(eventName, ...args) {\n    // if (!this._eventNameList.some(v => v === eventName)) {\n    //   logger.warn(\"unregistered event name:\", eventName);\n    //   return;\n    // }\n\n    logger.log('emit: \"%s\"', eventName, ...args);\n    // freeze 一下避免 Vue 添加一些额外字段，同时也确保事件数据传递时不被修改\n    this._vue.$emit(eventName, ...args.map(v => Object.freeze(v)));\n  }\n  on(eventName, callback) {\n    // if (!this._eventNameList.some(v => v === eventName)) {\n    //   logger.warn(\"unregistered event name:\", eventName);\n    //   return;\n    // }\n\n    // logger.log('on: \"%s\"', eventName);\n    this._vue.$on(eventName, callback);\n  }\n}\n\nexport { EventBus };\n\n/*\n * 预定义事件类型\n * 目标触发了事件：<target>:<event_name>\n * 请求目标触发事件：<request>:<target>:<event_name>\n */\nexport default new EventBus({\n  POPUP_VISIBILITY_CHANGE: \"popup_visibility_change\",\n  SETTINGS_CHANGE: \"settings_change\",\n  SETTINGS_LOADED: \"settings_loaded\",\n  WEB_CONSOLE_SHOW: \"web-console:show\",\n  WEB_CONSOLE_HIDE: \"web-console:hide\",\n  REQUEST_WEB_CONSOLE_HIDE: \"request:web-console:hide\"\n});\n"
  },
  {
    "path": "src/utils/Logger.js",
    "content": "import { isDev, isString } from \"./miscs\";\n\n/* 原始的 console 方法  */\nconst _error = window.console.error;\nconst _log = window.console.log;\nconst _warn = window.console.warn;\n\n/**\n * 日志类\n * 调用原始的 console 方法打印日志\n */\nexport default class Logger {\n  constructor(prefix) {\n    this._prefix = prefix ? prefix + \" \" : \"\";\n  }\n\n  error(...args) {\n    if (!isDev) return;\n\n    if (isString(args[0])) {\n      args[0] = this._prefix + args[0];\n    } else {\n      args.unshift(this._prefix);\n    }\n    _error.apply(this, args);\n  }\n  warn(...args) {\n    if (!isDev) return;\n\n    if (isString(args[0])) {\n      args[0] = this._prefix + args[0];\n    } else {\n      args.unshift(this._prefix);\n    }\n    _warn.apply(this, args);\n  }\n  log(...args) {\n    if (!isDev) return;\n\n    if (isString(args[0])) {\n      args[0] = this._prefix + args[0];\n    } else {\n      args.unshift(this._prefix);\n    }\n    _log.apply(this, args);\n  }\n}\n"
  },
  {
    "path": "src/utils/TaskScheduler.js",
    "content": "import { isFunction } from \"./miscs\";\n// import Logger from \"./Logger\";\n\n// const logger = new Logger(\"[TaskScheduler]\");\n/**\n * 任务调度器\n * 用途：按任意速率添加任务，之后这些任务按指定速率按序执行，避免阻塞交互\n *\n * 第一版实现方式：新增任务放入队列，调度器间隔一定时间执行 1 个任务，这种方式问题在于每次执行的任务数量\n * 只有一个，导致任务执行的总时间变长，总时间 = 任务数量 * 间隔时间\n *\n * T T T T T T T  <-- push task\n * T -> wait -> T -> wait -> T\n *\n * 第二版实现方式：新增任务放入队列，调度器间隔一定时间执行 N 个任务，总时间 = 任务数量 / N * 间隔时间，\n * 大大缩短了任务执行的总时间\n *\n * T T T T T T T  <-- push task\n * T...T -> wait -> T...T -> wait -> T...T\n *\n * 示例：\n * const scheduler = new TaskScheduler(interval)\n * scheduler.addAndStart(task1)\n * scheduler.addAndStart(task2)\n */\nexport default class TaskScheduler {\n  constructor({ interval = 200, taskBundleSize = 100 } = {}) {\n    this._interval = interval;\n    this._isRunning = false;\n    this._tasks = [];\n    this._timerId = null;\n    this._taskBundleSize = taskBundleSize;\n  }\n\n  get length() {\n    return this._tasks.length;\n  }\n\n  add(task) {\n    if (!isFunction(task)) return;\n\n    this._tasks.push(task);\n  }\n\n  addAndStart(task) {\n    this.add(task);\n    if (this.length > 0) {\n      this.start({ immediate: true });\n    }\n  }\n\n  /**\n   * 启动调度\n   * @param {Object} params\n   * @param {Boolean} params.immediate 是否立即执行队列任务\n   */\n  start({ immediate = true } = {}) {\n    this._runTask(immediate);\n  }\n\n  // 停止调度，并清空任务队列\n  stop() {\n    if (this._timerId !== null) {\n      clearTimeout(this._timerId);\n      this._timerId = null;\n    }\n    this._tasks = [];\n    this._isRunning = false;\n  }\n\n  _shiftTaskBunlde() {\n    const count = Math.min(this._tasks.length, this._taskBundleSize);\n    const taskBundle = this._tasks.slice(0, count);\n    this._tasks = this._tasks.slice(count);\n\n    // logger.log(\"_shiftTaskBunlde remove:\", count, \"rest:\", this.length);\n    return taskBundle;\n  }\n\n  _runTask(immediate = false) {\n    if (this.length <= 0 || this._isRunning) return;\n\n    this._isRunning = true;\n\n    if (immediate) {\n      const task = this._tasks.shift();\n      task.call(null);\n      // logger.log(\"_runTask immediate size:\", 1);\n    }\n    this._timerId = setTimeout(() => {\n      const tasks = this._shiftTaskBunlde();\n      tasks.forEach(task => task.call(null));\n\n      this._isRunning = false;\n      this._runTask();\n    }, this._interval);\n  }\n}\n"
  },
  {
    "path": "src/utils/consoleHooks.js",
    "content": "import { uuid, createStack } from \"./miscs\";\n\n// hook console\n// install 前的 console 接口\nconst originConsole = {};\n// install 后的 console 接口（之后第三方可能会再次重写改接口)\nconst currentConsole = {};\nconst names = [\"log\", \"info\", \"error\", \"warn\", \"debug\"];\nconst msgList = [];\nlet active = false;\n\nconst install = () => {\n  active = true;\n  names.forEach(name => {\n    originConsole[name] = window.console[name];\n\n    currentConsole[name] = function(...args) {\n      if (active) {\n        const logArgs = args.map(v => {\n          if (v instanceof Error) {\n            createStack(v, currentConsole[name]);\n          }\n          return v;\n        });\n        const msg = {\n          id: uuid(),\n          type: name,\n          timestamps: Date.now(),\n          logArgs\n        };\n        // 冻结计算结果，避免 Vue 添加额外属性\n        msgList.push(Object.freeze(msg));\n      }\n      originConsole[name].apply(this, args);\n    };\n\n    window.console[name] = currentConsole[name];\n  });\n};\n\n// 卸载\nconst uninstall = () => {\n  active = false;\n  // 如果第三方没有重写 console 接口，则还原 console 接口为原生的 console 接口\n  names.forEach(name => {\n    if (currentConsole[name] === window.console[name]) {\n      window.console[name] = originConsole[name];\n    }\n  });\n};\n\nconst getMsgList = () => msgList;\n\nexport default { install, uninstall, getMsgList };\n"
  },
  {
    "path": "src/utils/filters.js",
    "content": "export default {\n  lowerCase: input => {\n    if (typeof input !== \"string\") return input;\n    else return input.toLowerCase();\n  }\n};\n"
  },
  {
    "path": "src/utils/index.js",
    "content": "export { default as Style } from \"./style\";\nexport { default as filters } from \"./filters\";\nexport { default as Logger } from \"./Logger\";\nexport { default as consoleHooks } from \"./consoleHooks\";\nexport { default as TaskScheduler } from \"./TaskScheduler\";\nexport { default as eventBus, EventBus } from \"./eventBus\";\nexport * from \"./miscs\";\n"
  },
  {
    "path": "src/utils/miscs.js",
    "content": "export { default as cloneDeep } from \"lodash.clonedeep\";\n\nexport const isNull = v => v === null;\nexport const isUndefined = v => v === undefined;\nexport const isString = v => typeof v === \"string\";\nexport const isFunction = v => typeof v === \"function\";\nexport const isArray = v => Array.isArray(v);\nexport const isNumber = v => typeof v === \"number\";\nexport const isSymbol = v => typeof v === \"symbol\";\nexport const isBoolean = v => typeof v === \"boolean\";\nexport const isObject = v => typeof v === \"object\" && v !== null;\n\nexport const isDev = process.env.NODE_ENV !== \"production\";\n\nexport const noop = function() {};\n\nexport const nextTick = cb => {\n  if (typeof window.Promise === \"function\") {\n    Promise.resolve().then(cb);\n  } else {\n    setTimeout(cb, 0);\n  }\n};\n\n// 将 HTTP content-type 类型转换成 highlight.js 所支持的语法高亮类型\nexport const mimeType2Language = mimeType => {\n  switch (true) {\n    case /javascript/.test(mimeType):\n      return \"javascript\";\n    case /json/.test(mimeType):\n      return \"json\";\n    case /html/.test(mimeType):\n      return \"html\";\n    case /css/.test(mimeType):\n      return \"css\";\n    default:\n      return \"\";\n  }\n};\n\n/**\n * generate an uuid\n * @returns string\n */\nexport const uuid = () => {\n  let id = \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function(c) {\n    let r = (Math.random() * 16) | 0;\n    let v = c === \"x\" ? r : (r & 0x3) | 0x8;\n    return v.toString(16);\n  });\n  return id;\n};\n\nexport const flatMap = (arr, callback) => {\n  let r = [];\n  arr.forEach((v, i) => {\n    const r2 = callback(v, i);\n    r2.forEach(v2 => r.push(v2));\n  });\n  return r;\n};\n\nexport const formatFileName = (s = \"\") => s.replace(/https?:\\/\\/.*\\/(.*)/g, \"$1\");\n\n/**\n * 在目标对象上创建一个 stack 属性表示调用堆栈\n * @param {*} targetObject\n * @param {*} constructorOpt 该函数（含自身）以上的堆栈都会被忽略\n */\nexport const createStack = (targetObject, constructorOpt) => {\n  // Chrome 提供 captureStackTrace 方法记录堆栈信息到目标对象的 stack 属性，参考<https://github.com/dwqs/blog/issues/49>\n  if (typeof Error.captureStackTrace === \"function\") {\n    Error.captureStackTrace(targetObject, constructorOpt);\n  }\n  // safari 创建的 Error 对象包含 stack 属性记录堆栈信息\n  if (typeof targetObject.stack === \"string\") {\n    targetObject.stack = formatFileName(targetObject.stack);\n  }\n};\n\n// 获取 URL 中的文件名(含扩展名)\nexport const getURLFileName = url => {\n  if (typeof url !== \"string\") return \"\";\n\n  const index = url.lastIndexOf(\"/\");\n  return url.slice(index + 1);\n};\n"
  },
  {
    "path": "src/utils/style.js",
    "content": "/**\n * 处理 CSS 样式的工具方法\n */\nimport { compare } from \"specificity\";\nimport Logger from \"./Logger\";\n\nconst logger = new Logger(\"[Style.js]\");\n\n/**\n * 获取与指定元素匹配的所有样式规则\n *\n * ----- index.html\n * <style>\n * @import stylesheet_import1.css\n *\n * #id {\n *  color: red;\n * }\n * </style>\n * <link rel=\"stylesheet\" href=\"stylesheet_link.css\" />\n * <div id=\"id\"></div>\n *\n * ----- stylesheet_import1.css\n * @import stylesheet_import2.css\n * #id {\n *  color: blue;\n * }\n *\n * ----- stylesheet_import2.css\n * #id {\n *  color: green;\n * }\n *\n * ----- stylesheet_link.css\n * #id {\n *  color: yellow;\n * }\n *\n * Chrome DevTools 审查元素 div#id 的样式规则优先级从高到低为：\n * #id {color: yellow;} // from stylesheet_link.css\n * #id {color: red;}  // from <style>...</style>\n * #id {color: blue;} // from stylesheet_import1.css\n * #id {color: green;} // from stylesheet_import2.css\n *\n * 相同 specificity 的选择器按照出现的先后顺序层叠，后面的元素覆盖前面元素\n * 通过 @import 导入的元素视作原地展开后按出现顺序层叠。\n * document.styleSheets 返回的列表是按声明顺序排序，如果有 @import 则是递归嵌套\n *\n * @param {Element} el\n * @param {Array<StyleSheet>} styleSheets\n * @returns {Array<CSSRule>} 匹配的规则列表，按出现先后顺序排列\n */\nfunction getMatchedCSSRules(el, styleSheets = [].slice.call(document.styleSheets)) {\n  if (!(el instanceof Element)) {\n    throw new Error(\"invalid params type\");\n  }\n\n  const rules = [];\n  // 深度优先遍历 样式表和 CSS 规则\n  // stylesheet\n  //  import rule\n  //    stylesheet\n  //      style rule\n  //  style rule\n  // stylesheet\n  while (styleSheets.length > 0) {\n    const styleSheet = styleSheets.shift();\n    try {\n      const cssRuleArr = [].slice.call(styleSheet.cssRules);\n      while (cssRuleArr.length > 0) {\n        const rule = cssRuleArr.shift();\n        switch (rule.type) {\n          case CSSRule.IMPORT_RULE:\n            // 根据层叠规则，导入样式视作在当前样式表所有规则之前\n            cssRuleArr.unshift(...getMatchedCSSRules(el, [rule.styleSheet]));\n            break;\n          case CSSRule.STYLE_RULE:\n            if (el.matches(rule.selectorText)) {\n              // 设置出现顺序，值越小表示越先出现\n              rule.order = rules.length;\n              rules.push(rule);\n              // logger.log(rule.type, rule)\n            }\n            break;\n          default:\n            // TODO: 处理其他类型的 CSSRule\n            logger.error(\"unknow CSSRule type:\", rule.type);\n            break;\n        }\n      }\n    } catch (e) {\n      logger.warn(e);\n      continue;\n    }\n  }\n  return rules;\n}\n\n/**\n * 根据特殊性排序，如果特殊性相同则按出现顺序排序，排在数组越前的特殊性越高\n * @param {CSSRule} a\n * @param {CSSRule} b\n * @returns {Number}\n */\nfunction compareCSSRule(a, b) {\n  const selectorA = findMaxSpecificity(a.selectorText);\n  const selectorB = findMaxSpecificity(b.selectorText);\n  const c = compare(selectorA, selectorB);\n  return c === 0 ? b.order - a.order : -c;\n}\n\n/**\n * 找出择器中特殊性最高的选择器\n *\n * 例如：\n * findMaxSpecificity('.a')  // '.a'\n * findMaxSpecificity('.a, #b')  // '#b'\n * findMaxSpecificity('.a, #b, #b.a')  // '#b.a'\n */\nfunction findMaxSpecificity(selector) {\n  let _selector;\n  if (selector.indexOf(\",\") !== -1) {\n    const selectorArr = selector.split(\",\");\n    _selector = selectorArr[0];\n    selectorArr.forEach(v => {\n      if (compare(v, _selector) === 1) {\n        _selector = v;\n      }\n    });\n  } else {\n    _selector = selector;\n  }\n  return _selector;\n}\n\n/**\n * 返回一个匹配 CSS 有效颜色值的 RegExp 对象\n */\nlet _cachedColorRegExp = null;\nfunction getColorRegExp() {\n  if (_cachedColorRegExp) {\n    return _cachedColorRegExp;\n  }\n\n  const basicColor = [\n    \"black\",\n    \"silver\",\n    \"gray\",\n    \"white\",\n    \"maroon\",\n    \"red\",\n    \"purple\",\n    \"fuchsia\",\n    \"green\",\n    \"lime\",\n    \"olive\",\n    \"yellow\",\n    \"navy\",\n    \"blue\",\n    \"teal\",\n    \"aqua\"\n  ].join(\"|\");\n  const extendColor = [\n    \"aliceblue\",\n    \"antiquewhite\",\n    \"aqua\",\n    \"aquamarine\",\n    \"azure\",\n    \"beige\",\n    \"bisque\",\n    \"black\",\n    \"blanchedalmond\",\n    \"blue\",\n    \"blueviolet\",\n    \"brown\",\n    \"burlywood\",\n    \"cadetblue\",\n    \"chartreuse\",\n    \"chocolate\",\n    \"coral\",\n    \"cornflowerblue\",\n    \"cornsilk\",\n    \"crimson\",\n    \"cyan\",\n    \"darkblue\",\n    \"darkcyan\",\n    \"darkgoldenrod\",\n    \"darkgray\",\n    \"darkgreen\",\n    \"darkgrey\",\n    \"darkkhaki\",\n    \"darkmagenta\",\n    \"darkorange\",\n    \"darkorchid\",\n    \"darkred\",\n    \"darksalmon\",\n    \"darkseagreen\",\n    \"darkslateblue\",\n    \"darkslategray\",\n    \"darkslategrey\",\n    \"darkturquoise\",\n    \"darkviolet\",\n    \"deeppink\",\n    \"deepskyblue\",\n    \"dimgray\",\n    \"dimgrey\",\n    \"dodgerblue\",\n    \"firebrick\",\n    \"floralwhite\",\n    \"forestgreen\",\n    \"fuchsia\",\n    \"gainsboro\",\n    \"ghostwhite\",\n    \"gold\",\n    \"goldenrod\",\n    \"gray\",\n    \"green\",\n    \"greenyellow\",\n    \"grey\",\n    \"honeydew\",\n    \"hotpink\",\n    \"indianred\",\n    \"indigo\",\n    \"ivory\",\n    \"khaki\",\n    \"lavender\",\n    \"lavenderblush\",\n    \"lawngreen\",\n    \"lemonchiffon\",\n    \"lightblue\",\n    \"lightcoral\",\n    \"lightcyan\",\n    \"lightgoldenrodyellow\",\n    \"lightgray\",\n    \"lightgreen\",\n    \"lightgrey\",\n    \"lightpink\",\n    \"lightsalmon\",\n    \"lightseagreen\",\n    \"lightskyblue\",\n    \"lightslategray\",\n    \"lightslategrey\",\n    \"lightsteelblue\",\n    \"lightyellow\",\n    \"lime\",\n    \"limegreen\",\n    \"linen\",\n    \"magenta\",\n    \"maroon\",\n    \"mediumaquamarine\",\n    \"mediumblue\",\n    \"mediumorchid\",\n    \"mediumpurple\",\n    \"mediumseagreen\",\n    \"mediumslateblue\",\n    \"mediumspringgreen\",\n    \"mediumturquoise\",\n    \"mediumvioletred\",\n    \"midnightblue\",\n    \"mintcream\",\n    \"mistyrose\",\n    \"moccasin\",\n    \"navajowhite\",\n    \"navy\",\n    \"oldlace\",\n    \"olive\",\n    \"olivedrab\",\n    \"orange\",\n    \"orangered\",\n    \"orchid\",\n    \"palegoldenrod\",\n    \"palegreen\",\n    \"paleturquoise\",\n    \"palevioletred\",\n    \"papayawhip\",\n    \"peachpuff\",\n    \"peru\",\n    \"pink\",\n    \"plum\",\n    \"powderblue\",\n    \"purple\",\n    \"red\",\n    \"rosybrown\",\n    \"royalblue\",\n    \"saddlebrown\",\n    \"salmon\",\n    \"sandybrown\",\n    \"seagreen\",\n    \"seashell\",\n    \"sienna\",\n    \"silver\",\n    \"skyblue\",\n    \"slateblue\",\n    \"slategray\",\n    \"slategrey\",\n    \"snow\",\n    \"springgreen\",\n    \"steelblue\",\n    \"tan\",\n    \"teal\",\n    \"thistle\",\n    \"tomato\",\n    \"turquoise\",\n    \"violet\",\n    \"wheat\",\n    \"white\",\n    \"whitesmoke\",\n    \"yellow\",\n    \"yellowgreen\"\n  ].join(\"|\");\n  const rgbColor = \"rgba?([^)]+)\";\n  const hslaColor = \"hsla?([^)]+)\";\n  const hexColor = \"#[0-9a-fA-F]{1,8}\";\n\n  const colorRegExp = new RegExp([hexColor, rgbColor, hslaColor, basicColor, extendColor].join(\"|\"), \"i\");\n  _cachedColorRegExp = colorRegExp;\n  return colorRegExp;\n}\n\nexport default {\n  getMatchedCSSRules,\n  compareCSSRule,\n  getColorRegExp\n};\n"
  },
  {
    "path": "tests/api/data/response.css",
    "content": "@import \"aa\";\n\n.aa {\n  color: red;\n  background: rgb(50%, 50%, 50%);\n}\n"
  },
  {
    "path": "tests/api/data/response.html",
    "content": "<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n<body>\n  <!-- This is a comment -->\n  <h1>Hello World</h1>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/response.js",
    "content": "function sayHello(params) {\n  console.log(params);\n  Array.prototype.every.call(this, 1, 2);\n  var a = {\n    1: 100,\n    2: true,\n    3: null,\n    4: undefined,\n    5: [100, 200],\n    6: \"stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring\",\n    7: {\n      a: 1\n    },\n    8: Symbol(\"a\")\n  };\n}\n"
  },
  {
    "path": "tests/api/data/response.json",
    "content": "{\n  \"a\": 1,\n  \"b\": true,\n  \"c\": \"c\",\n  \"d\": [1.23, true, \"c\", { \"a\": 1, \"b\": 2 }],\n  \"e\": {\n    \"a\": 1,\n    \"b\": true,\n    \"c\": \"c\",\n    \"d\": null,\n    \"e1\": 1,\n    \"e2\": 1,\n    \"e3\": 1\n  }\n}\n"
  },
  {
    "path": "tests/api/data/response.txt",
    "content": "plain text data"
  },
  {
    "path": "tests/api/index.js",
    "content": "var http = require(\"http\");\nvar fs = require(\"fs\");\nconst url = require(\"url\");\n\nconst routes = {\n  \"/get_data\": function(req, res) {\n    const queryParams = url.parse(req.url, true).query;\n    const mimeType = queryParams[\"mime_type\"];\n    const responseHeaders = {\n      \"Access-Control-Allow-Origin\": \"*\",\n      \"Content-Type\": mimeType || \"text/plain\"\n    };\n    res.writeHead(200, responseHeaders);\n\n    // 参考 http://devdocs.io/http/basics_of_http/mime_types\n    switch (mimeType) {\n      case \"application/octet-stream\":\n        break;\n      case \"text/javascript\":\n      case \"application/javascript\":\n        fs.readFile(\"./data/response.js\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"application/json\":\n        fs.readFile(\"./data/response.json\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"image/jpeg\":\n        fs.readFile(\"./data/response.jpg\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"image/png\":\n        fs.readFile(\"./data/response.png\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"image/gif\":\n        fs.readFile(\"./data/response.gif\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"image/svg+xml\":\n        fs.readFile(\"./data/response.svg\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"text/html\":\n        fs.readFile(\"./data/response.html\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"text/css\":\n        fs.readFile(\"./data/response.css\", function(err, data) {\n          res.end(data);\n        });\n        break;\n      case \"text/plain\":\n      default:\n        fs.readFile(\"./data/response.txt\", function(err, data) {\n          res.end(data);\n        });\n        break;\n    }\n  },\n  // 获取不同的 HTTP 状态码\n  \"/get_status/\\\\d+\": function(req, res) {\n    const pathname = url.parse(req.url).pathname;\n    const status = new RegExp(\"/get_status/(\\\\d+)\").exec(pathname)[1];\n    res.writeHead(status, {\n      \"Access-Control-Allow-Method\": \"GET,POST,OPTIONS\",\n      \"Access-Control-Allow-Headers\": \"Content-Type\",\n      \"Access-Control-Allow-Origin\": \"*\",\n      \"Content-Type\": \"text/plain; charset=utf-8\"\n    });\n    res.end(\"response status: \" + status);\n  },\n  \"/get\": function(req, res) {\n    res.writeHead(200, {\n      \"Access-Control-Allow-Origin\": \"*\",\n      \"Content-Type\": \"text/plain; charset=utf-8\"\n    });\n    res.end(\"get hello\");\n  },\n  \"/post\": function(req, res) {\n    res.writeHead(200, {\n      \"Access-Control-Allow-Method\": \"GET,POST,OPTIONS\",\n      \"Access-Control-Allow-Headers\": \"Content-Type\",\n      \"Access-Control-Allow-Origin\": \"*\",\n      \"Content-Type\": \"text/plain; charset=utf-8\"\n    });\n    res.end(\"post\");\n  }\n};\n\nconst httpServer = http\n  .createServer(function(req, res) {\n    const urlObj = url.parse(req.url);\n    const pathname = urlObj.pathname;\n    console.log(\"received request:\", pathname + \"?\" + urlObj.query);\n\n    const key = Object.keys(routes).find(route => new RegExp(route).test(pathname));\n    if (key) {\n      routes[key](req, res);\n    } else {\n      res.writeHead(404, { \"Content-Type\": \"text/html\" });\n      res.end(\"404 Not Found\");\n    }\n\n    //  if (typeof routes[pathname] === 'function') {\n    //    routes[pathname](req, res)\n    //  } else {\n    //    res.writeHead(404, {'Content-Type': 'text/html'})\n    //    res.end('404 Not Found')\n    //  }\n  })\n  .listen(8090);\n\n// 控制台会输出以下信息\nconsole.log(\"Server running at http://localhost:\" + httpServer.address().port);\n"
  },
  {
    "path": "tests/e2e/.eslintrc",
    "content": "{\n  \"plugins\": [\n    \"cypress\"\n  ],\n  \"env\": {\n    \"mocha\": true,\n    \"cypress/globals\": true\n  },\n  \"rules\": {\n    \"strict\": \"off\"\n  }\n}\n"
  },
  {
    "path": "tests/e2e/plugins/index.js",
    "content": "// https://docs.cypress.io/guides/guides/plugins-guide.html\n\nmodule.exports = (on, config) => {\n  return Object.assign({}, config, {\n    fixturesFolder: 'tests/e2e/fixtures',\n    integrationFolder: 'tests/e2e/specs',\n    screenshotsFolder: 'tests/e2e/screenshots',\n    videosFolder: 'tests/e2e/videos',\n    supportFile: 'tests/e2e/support/index.js'\n  })\n}\n"
  },
  {
    "path": "tests/e2e/specs/test.js",
    "content": "// https://docs.cypress.io/api/introduction/api.html\n\ndescribe('My First Test', () => {\n  it('Visits the Kitchen Sink', () => {\n    cy.visit('/')\n    cy.contains('h1', 'Welcome to Your Vue.js App')\n  })\n})\n"
  },
  {
    "path": "tests/e2e/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\n"
  },
  {
    "path": "tests/e2e/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands'\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "tests/unit/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jest\": true\n  },\n  \"rules\": {\n    \"import/no-extraneous-dependencies\": \"off\"\n  }\n}"
  },
  {
    "path": "tests/unit/HelloWorld.spec.js",
    "content": "import { shallow } from '@vue/test-utils'\nimport HelloWorld from '@/components/HelloWorld.vue'\n\ndescribe('HelloWorld.vue', () => {\n  it('renders props.msg when passed', () => {\n    const msg = 'new message'\n    const wrapper = shallow(HelloWorld, {\n      propsData: { msg }\n    })\n    expect(wrapper.text()).toMatch(msg)\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@\": [\"./src\"],\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}"
  },
  {
    "path": "vue.config.js",
    "content": "const path = require(\"path\");\n\n/** 以 VUE_APP_* 开头的环境变量会替换代码中相应的变量 */\nconst npmPkg = require(\"./package.json\");\nprocess.env.VUE_APP_NAME = npmPkg.name;\nprocess.env.VUE_APP_VERSION = npmPkg.version;\nprocess.env.VUE_APP_DATE = new Date(Date.now() - new Date().getTimezoneOffset() * 60 * 1000).toISOString();\n\nmodule.exports = {\n  css: {\n    extract: false\n  },\n  // 部署和测试时用，打包成 lib 时不需要\n  publicPath: process.env.NODE_ENV === \"production\" ? \"./\" : \"/\",\n  configureWebpack: {\n    // 将入口的导出作为默认导出\n    output: {\n      libraryExport: \"default\"\n    },\n    resolve: {\n      alias: {\n        \"@\": path.resolve(__dirname, \"src\")\n      }\n    }\n  }\n};\n"
  }
]