Ver código fonte

refactor 2.0

有田 7 anos atrás
commit
2e02ebd00e
100 arquivos alterados com 25260 adições e 0 exclusões
  1. 15 0
      .babelrc
  2. 20 0
      .editorconfig
  3. 7 0
      .eslintignore
  4. 40 0
      .eslintrc
  5. 72 0
      .gitignore
  6. 77 0
      .npmignore
  7. 25 0
      .torch.compile.opts.js
  8. 24 0
      .travis.yml
  9. 286 0
      CHANGELOG.md
  10. 142 0
      CONTRIBUTING.md
  11. 148 0
      CONTRIBUTING.zh-CN.md
  12. 21 0
      LICENSE
  13. 66 0
      README-zh_CN.md
  14. 64 0
      README.md
  15. 7 0
      bin/cp-dist.js
  16. 7 0
      bin/mkdir-dist.js
  17. 82 0
      bin/screenshot.js
  18. 14 0
      bin/win-dev.js
  19. 107 0
      demos/InteractionBehaviourMode.html
  20. 58 0
      demos/InteractionPanNode.html
  21. 134 0
      demos/app.js
  22. 7 0
      demos/assets/bootstrap-4.1.0/bootstrap-grid.min.css
  23. 7 0
      demos/assets/bootstrap-4.1.0/bootstrap.min.css
  24. 7 0
      demos/assets/bootstrap-4.1.0/bootstrap.min.js
  25. 7 0
      demos/assets/clipboard-1.7.1.min.js
  26. 8 0
      demos/assets/codemirror-5.27.4/.gitattributes
  27. 11 0
      demos/assets/codemirror-5.27.4/.npmignore
  28. 4 0
      demos/assets/codemirror-5.27.4/.travis.yml
  29. 105 0
      demos/assets/codemirror-5.27.4/addon/fold/brace-fold.js
  30. 59 0
      demos/assets/codemirror-5.27.4/addon/fold/comment-fold.js
  31. 150 0
      demos/assets/codemirror-5.27.4/addon/fold/foldcode.js
  32. 20 0
      demos/assets/codemirror-5.27.4/addon/fold/foldgutter.css
  33. 146 0
      demos/assets/codemirror-5.27.4/addon/fold/foldgutter.js
  34. 182 0
      demos/assets/codemirror-5.27.4/addon/fold/xml-fold.js
  35. 41 0
      demos/assets/codemirror-5.27.4/addon/hint/anyword-hint.js
  36. 60 0
      demos/assets/codemirror-5.27.4/addon/hint/css-hint.js
  37. 348 0
      demos/assets/codemirror-5.27.4/addon/hint/html-hint.js
  38. 155 0
      demos/assets/codemirror-5.27.4/addon/hint/javascript-hint.js
  39. 36 0
      demos/assets/codemirror-5.27.4/addon/hint/show-hint.css
  40. 438 0
      demos/assets/codemirror-5.27.4/addon/hint/show-hint.js
  41. 110 0
      demos/assets/codemirror-5.27.4/addon/hint/xml-hint.js
  42. 830 0
      demos/assets/codemirror-5.27.4/mode/css/css.js
  43. 152 0
      demos/assets/codemirror-5.27.4/mode/htmlmixed/htmlmixed.js
  44. 818 0
      demos/assets/codemirror-5.27.4/mode/javascript/javascript.js
  45. 394 0
      demos/assets/codemirror-5.27.4/mode/xml/xml.js
  46. 1 0
      demos/assets/codemirror-5.29.0/codemirror-merged.min.css
  47. 1 0
      demos/assets/codemirror-5.29.0/codemirror-merged.min.js
  48. 6 0
      demos/assets/common.css
  49. 2 0
      demos/assets/d3-4.13.0.min.js
  50. 6133 0
      demos/assets/d3-legend-2.25.6.js
  51. 3 0
      demos/assets/d3-legend-2.25.6.min.js
  52. 1915 0
      demos/assets/data/g6-index.json
  53. 59 0
      demos/assets/data/modeling-methods.json
  54. 1262 0
      demos/assets/data/university.json
  55. 7337 0
      demos/assets/data/xiaomi.json
  56. 2 0
      demos/assets/file-saver-1.3.8.min.js
  57. 106 0
      demos/assets/index.css
  58. 105 0
      demos/assets/index.js
  59. 4 0
      demos/assets/jquery-3.2.1.min.js
  60. 164 0
      demos/assets/jquery.resizable-0.20.0.js
  61. 2 0
      demos/assets/lazyload-2.0.0-beta.2.min.js
  62. 136 0
      demos/assets/lodash-4.17.4.min.js
  63. 5 0
      demos/assets/popper.js-1.12.5/popper-utils.min.js
  64. 5 0
      demos/assets/popper.js-1.12.5/popper.min.js
  65. 8 0
      demos/assets/routie-0.3.2.min.js
  66. 77 0
      demos/customAnchor.html
  67. 54 0
      demos/customDraw.html
  68. 75 0
      demos/customEnterLeaveAnimate.html
  69. 49 0
      demos/customFlowingEdge.html
  70. 72 0
      demos/customInherit.html
  71. 81 0
      demos/defaultEdge.html
  72. 62 0
      demos/defaultGroup.html
  73. 41 0
      demos/defaultNode.html
  74. 56 0
      demos/galleryCodeTree.html
  75. 133 0
      demos/galleryUniversity.html
  76. 98 0
      demos/galleryWebIndex.html
  77. 170 0
      demos/galleryXiaomi.html
  78. 47 0
      demos/graphAnimate.html
  79. 49 0
      demos/graphChangeSize.html
  80. 59 0
      demos/graphZIndex.html
  81. 62 0
      demos/index.njk
  82. 81 0
      demos/layout.html
  83. 43 0
      demos/layoutSimple.html
  84. 32 0
      demos/pluginArchimeddeanSpiral.html
  85. 32 0
      demos/pluginCircle.html
  86. 185 0
      demos/pluginMaxSpanningForest.html
  87. 63 0
      demos/pluginMinimap.html
  88. 49 0
      demos/pluginQuadraticCurve.html
  89. 38 0
      demos/quickNet.html
  90. 39 0
      demos/quickTree.html
  91. 100 0
      demos/tree-compact-box.html
  92. 105 0
      demos/tree-dendrogram.html
  93. 89 0
      demos/tree-indented.html
  94. 112 0
      demos/tree-mindmap.html
  95. 58 0
      demos/viewportFitView.html
  96. 51 0
      demos/viewportFocus.html
  97. 52 0
      demos/viewportTranslate.html
  98. 52 0
      demos/viewportZoom.html
  99. 127 0
      package.json
  100. 0 0
      plugins/behaviour.analysis/index.js

+ 15 - 0
.babelrc

@@ -0,0 +1,15 @@
+{
+  "plugins": [
+    "transform-object-rest-spread",
+    "transform-remove-strict-mode"
+  ],
+  "presets": [
+    [
+      "env",
+      {
+        "loose": true,
+        "modules": false
+      }
+    ]
+  ]
+}

+ 20 - 0
.editorconfig

@@ -0,0 +1,20 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+charset = utf-8
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size  = 2
+
+[Makefile]
+indent_style = tab
+indent_size  = 1
+
+[*.md]
+trim_trailing_whitespace = false

+ 7 - 0
.eslintignore

@@ -0,0 +1,7 @@
+build/
+coverage/
+lib/
+dist/
+mocks/
+node_modules/
+demos/assets/

+ 40 - 0
.eslintrc

@@ -0,0 +1,40 @@
+{
+  "extends": [
+    "egg"
+  ],
+  "globals": {
+    "$": true,
+    "_": true
+  },
+  "parser": "babel-eslint",
+  "parserOptions": {
+    "sourceType": "module"
+  },
+  "plugins": [
+    "html"
+  ],
+  "rules": {
+    "no-console": [
+      "error",
+      {
+        "allow": [
+          "warn",
+          "error"
+        ]
+      }
+    ],
+    "no-bitwise": [
+      0
+    ],
+    "experimentalDecorators": [
+      0
+    ],
+    "comma-dangle": [
+      "error",
+      "never"
+    ],
+    "linebreak-style": [
+      0
+    ]
+  }
+}

+ 72 - 0
.gitignore

@@ -0,0 +1,72 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# lock
+package-lock.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+build
+dist
+temp
+.DS_Store
+.idea
+demos/assets/screenshots
+lib
+
+*.sw*
+*.un~

+ 77 - 0
.npmignore

@@ -0,0 +1,77 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# lock
+package-lock.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+.DS_Store
+
+# npmignore - content above this line is automatically generated and modifications may be omitted
+# see npmjs.com/npmignore for more details.
+test
+
+*.sw*
+*.un~
+.idea
+bin
+demos
+docs
+temp
+webpack-dev.config.js
+webpack.config.js

+ 25 - 0
.torch.compile.opts.js

@@ -0,0 +1,25 @@
+module.exports = {
+  babelrc: {
+    plugins: [
+      'transform-object-rest-spread',
+      'transform-remove-strict-mode'
+    ],
+    presets: [
+      [
+        'env',
+        {
+          'loose': true,
+          'modules': false
+        }
+      ]
+    ],
+    sourceMaps: 'inline'
+  },
+  extensions: ['.js'],
+  include: [
+    'node_modules/**/src/gl-matrix/**/*.js '
+  ],
+  exclude: [
+    'bower_components/**/*.js',
+  ]
+}

+ 24 - 0
.travis.yml

@@ -0,0 +1,24 @@
+language: node_js
+
+node_js:
+  - "8"
+
+env:
+  matrix:
+    - TEST_TYPE=ci
+
+addons:
+  apt:
+    packages:
+      - xvfb
+
+install:
+  - export DISPLAY=':99.0'
+  - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
+  - npm install
+
+script:
+  - |
+    if [ "$TEST_TYPE" = ci ]; then
+      npm run ci
+    fi

+ 286 - 0
CHANGELOG.md

@@ -0,0 +1,286 @@
+# ChangeLog
+
+---
+
+## v1.2.1
+
+* feat:    新增 layout 接口
+
+## v1.2.0
+
+* fix:     修复 nodeActivedBoxStyle 拼写错误
+* fix:     修复 删除环时报错
+* fix:     修复 右键后再移动鼠标触发 dragstart
+* feat:    新增 统一布局机制到 Graph
+* feat:    新增 插件机制
+* feat:    新增 数据过滤机制
+* feat:    新增 激活态接口
+* feat:    新增 行为 wheelZoomAutoLabel
+* feat:    新增 graph 配置项 preciseAnchor
+* remove:  移除 Global.preciseAnchor
+* remove:  移除 Layout.Flow、Layout.Force
+* improve: 改进 html 容器策略
+
+## v1.1.6
+
+* fix:     修复 布局算法包中的打包问题
+
+## v1.1.5
+
+* fix:     修复 dragCanvas 在 mousemove 才设置拾取实效,否则会影响点击事件
+* fix:     修复 将 node 的 activeRectBox 的拾取设置为false
+
+## v1.1.4
+
+`2017-08-15`
+
+* feat:    新增 graph.invertPoint() 转置点
+* feat:    新增 锚点第三个配置参数 此后锚点支持设置样式、悬浮样式、是否可以连接
+* feat:    新增 item.getGroup()
+* feat:    新增 事件 afteritemrender、itemremove、itemadd
+* feat:    新增 行为信号量 behaviourSignal
+* improve: 改进 画布聚焦时 mouseWheel 才生效
+
+## v1.1.3
+
+`2017-08-8`
+
+* feat:    新增 Graph 配置项 useNodeSortGroup,用于配置节点组是否使用排序组
+* feat:    新增 Global.nodeDelegationStyle, Global.edgeDelegationStyle 边和节点可独立配置委托图形
+* fix:     修复 事件 itemremove 销毁前触发
+
+## v1.1.2
+
+`2017-08-01`
+
+* feat:    新增 行为 dragBlankX dragBlankY
+
+## v1.1.1
+`2017-07-18`
+
+* improve: 改进 添加 dragNode 保护机制
+
+## v1.1.0
+
+`2017-07-05`
+
+* feat:    新增 HTML 节点
+* feat:    新增 映射支持直接传入 callback
+* feat:    新增 Graph 接口 updateMatrix、changeSize、showAnchor、hideAnchor、updataNodesPosition
+* feat:    新增 工具方法 Util.isNode()、Util.isEdge()
+* feat:    新增 Shape polyLineFlow
+* feat:    新增 行为 dragEdgeEndHideAnchor、dragNodeEndHideAnchor、hoverAnchorSetActived、hoverNodeShowAnchor
+
+## v1.0.7
+
+`2017-06-21`
+
+* fix:     修复 第一次 draw 时 16ms 内多画一下
+* improve: 改进 edit 模式下怎家滚轮缩放限制
+
+## v1.0.6
+
+`2017-06-15`
+
+* fix:     修复 兼容部分 机型 window 下 chrome 首次点击出发 mousemove 导致click事件不正确
+* feat:    新增 支持尺寸不变图形
+* feat:    新增 analysis 分析模式
+* feat:    新增 updateNodesPositon 批量更新节点位置方法
+* improve: 改进 useAnchor 改为 边 edge 的配置项
+
+## v1.0.5
+
+`2017-06-01`
+
+* feat:    新增 downloadImage 支持传入保存的文件名
+* feat:    新增 tooltip 边距自动检测
+* improve: 改进 拖拽移出画布时拖拽行为中止
+
+## v1.0.4
+
+`2017-05-20`
+
+* fix:     修复 tree changeData Bug
+* fix:     修复 getAnchorPoints 返回 auto 时锚点取直线与包围盒的截取点
+* fix:     修复 node label 无法生成的判定条件为 isNull
+* feat:    新增 增加视口参数 tl、tc、tr、rc、br、bc、bl、lc、cc
+* improve: 改进 减小 tolerance 数值,以提高线段截取精准度
+* improve: 改进 tooltip 事件机制,提升性能
+
+## v1.0.3
+
+`2017-05-10`
+
+* feat:    新增 graph.guide().link() 添加 link 辅助元素
+
+## v1.0.2
+
+`2017-05-10`
+
+* fix:     修复 Object.values => Util.getObjectValues
+* fix:     修复 anchorPoints 为 auto 时,edge上就算传了anchorpoint,也返回截取点
+* fix:     修复 tree 更新接口 Bug
+* improve: 改进 位置信息用 group.transfrom() 来表示
+
+## v1.0.1
+
+`2017-04-22`
+
+* fix:     修复 复制、粘贴 bug
+* feat:    新增 控制 16s 内只 draw 一次
+* feat:    新增 事件 itemactived itemunactived itemhover itemupdate itemmouseenter itemmouseleave
+* improve: 改进 框选行为激活图形前,应清楚之前图形的激活
+* improve: 改进 dragAddEdge 行为,可链接到锚点
+* improve: 改进 优化动画性能
+
+## v1.0.0
+
+`2017-03-31`
+
+* feat:    新增 fitView 视口配置项
+* feat:    新增 graph.zoom() 缩放接口
+* feat:    新增 wheelZoomHideEdges 滚轮缩放边隐藏行为
+* feat:    新增 dragHideEdges 拖动边隐藏行为
+* feat:    新增 graph.filterBehaviour() 过滤行为接口
+* feat:    新增 graph.addBehaviour() 增加行为接口
+* feat:    新增 graph.changeLayout() 更改布局
+* feat:    新增 read 读接口,重定义 save 保存接口
+* feat:    新增 graph.snapshot 快照功能 graph.downloadImage 导出图片功能
+* feat:    新增 graph.autoSize() 自动缩放画布尺寸
+* feat:    新增 graph.focusPoint() 聚焦某点
+* feat:    新增 tree 树图、net 网图
+* feat:    新增 交互机制 事件 => 行为 => 模式
+* feat:    新增 动画机制
+* feat:    新增 itemmouseleave、itemmouseenter 事件
+* remove:  去除 graph.refresh()
+* remove:  去除 graph.changeNodes()
+* remove:  去除 graph属性 zoomable、dragable、resizeable、selectable
+* improve: 改进 锚点机制,更快、更强
+* improve: 改进 不再抛出 G6.GraphUtil 方法,全部统一成 G6.Util
+* improve: 改进 替换 g-canvas-core => g-canvas 性能提升
+* improve: 改进 移除 Global.nodeBoxStyle 改用 Global.nodeAcitveBoxStyle
+* improve: 改进 小写事件名 afterAdd => afteradd
+* improve: 改进 G6.Graph 沉为抽象类
+
+## v0.2.3
+
+`2017-03-2`
+
+* fix:     修复 dragable 可用于控制默认模式下是否可拖动画布
+* feat:    新增 graph.converPoint() 反置点方法
+* feat:    新增 graph.autoSize()
+* feat:    新增 rightmousedown leftmousedown wheeldown 事件
+* improve: 改进 用tyr catch 防御getPoint由于path长度为零出错
+
+## v0.2.2
+
+`2017-02-24`
+
+* fix:     修复 tooltip css padding 加px
+* fix:     修复 tooltip 单子段映射错误
+* fix:     修复 精确截取点错误
+* fix:     修复 双精屏下缩放点错误
+* fix:     修复 包围盒应该 apply keyShape 父元素
+* feat:    新增 afterAdd 事件
+* feat:    新增 dblclick 事件
+* improve: 改进 width、height 默认null
+* improve: 改进 节点不再使用hovershape
+* improve: 改进 tooltip 增加防御机制
+
+## v0.2.1
+
+`2017-02-14`
+
+* fix:     新增 添加节点时增加rollback
+* fix:     新增 计算包围盒apply父容器变换
+* feat:    新增 waterPath 水波路径算法
+* feat:    新增 tooltip 提示信息
+* feat:    新增 mouseover 事件
+* feat:    新增 multiSelectable 配置项,默认关闭,关闭时选择模式开启空白处拖动功能
+* feat:    新增 width 没设置时自动forceFit 为 true
+* improve: 改进 zoomable、dragable、resizeable、selectable 默认为 true
+
+## v0.2.0
+
+`2017-02-07`
+
+* feat:    新增 精确逼近锚点机制
+* feat:    新增 GraphUtil.getEllipsePath 点集到椭圆Path
+* feat:    新增 GraphUtil.pointsToPolygon 点集到多边形Path
+* feat:    新增 GraphUtil.pointsToBezier 点集到贝塞尔曲线Path
+* feat:    新增 GraphUtil.snapPreciseAnchor 获取精准锚点方法
+* feat:    新增 GraphUtil.arrowTo  箭头旋转方法
+* feat:    新增 GraphUtil.drawEdge 画边方法
+* feat:    新增 bezierQuadratic 二阶贝塞尔曲线
+* feat:    新增 node.show 显示节点
+* feat:    新增 node.hide 隐藏节点
+* feat:    新增 node.getLinkNodes 获取相连的节点
+* feat:    新增 node.getUnLinkNodes 获取不相连的节点
+* feat:    新增 node.getRelativeItems 获取相关的子项
+* feat:    新增 node.getUnRelativeItems 获取不相关的子项
+* feat:    新增 edge.show 显示边
+* feat:    新增 edge.hide 隐藏边
+* feat:    新增 Shape afterDraw 接口
+* improve: 改进 改进贝塞尔曲线控制点位置
+* improve: 改进 grpah.delete => graph.del
+* improve: 改进 添加 id 重复抛出异常
+
+## v0.1.4
+
+`2017-01-17`
+
+* fix:     修复 拖动节点时委托中心应是bbox中心
+* fix:     修复 所有排序算法采用基数排序,保持层级稳定
+* fix:     修复 边的id自动生成机制才用随机数
+* feat:    新增 边支持层级排序,边文本会排在最上层
+* feat:    新增 注册边时,如果不指定继承形,则尝试从注册形获取继承形
+
+## v0.1.3
+
+`2017-01-15`
+
+* fix:     修复 所有assistGrid在进行操作时,判断该对象是否存在
+* feat:    新增 rollback 增加判断,默认不开启回退机制
+* feat:    新增 新增style 映射通道
+* feat:    新增 getAnchorPoints 不存在 或者 返回为假时 取线段交点
+* feat:    新增 新增边水平曲线bezierHorizontal、竖直曲线bezierVertical
+* improve: 改进 默认交互行为结束后触发 'eventEnd' 事件
+
+## v0.1.2
+
+`2017-01-12`
+
+* fix:  修复 更新网格时需要判定配置项grid
+* fix:  修复 graphContainer 不设置自己的宽高 由内部canvas撑大
+* fix:  修复 添加edges时若无target或source则不添加edge
+* fix:  修复 changeSize() 设置最大限容错
+* feat: 新增 graph.get('el') 获取最上层canvas DOM
+* feat: 新增 事件抛出当前shape
+
+## v0.1.1
+
+`2017-01-09`
+
+* feat: 新增 graph 的入口为 G6.Graph
+
+## v0.1.0
+
+`2017-01-07`
+
+* feat: 新增 抛出颜色计算库
+* feat: 新增 添加快捷键操作
+* feat: 新增 updo、redo
+* feat: 新增 复制、粘贴
+* feat: 新增 重置缩放、自动缩放
+* feat: 新增 树图、线性图、桑基图、流图布局
+* feat: 新增 业务流程图封装
+* feat: 新增 时序图封装
+* feat: 新增 鼠标交互:单选、框选
+* feat: 新增 鼠标交互:节点变形
+* feat: 新增 鼠标交互:边变形
+* feat: 新增 鼠标交互:节点、边拖拽交互
+* feat: 新增 鼠标交互:边链接到锚点
+* feat: 新增 鼠标交互:画布拖拽
+* feat: 新增 鼠标交互:画布缩放
+* feat: 新增 添加模式选择
+* feat: 新增 集成 g-graph

+ 142 - 0
CONTRIBUTING.md

@@ -0,0 +1,142 @@
+# Contribution Guide
+
+If you have any comment or advice, please report your [issue](https://github.com/antvis/g2/issues),
+or make any change as you wish and submit an [PR](https://github.com/antvis/g2/pulls).
+
+## Reporting New Issues
+
+- Please specify what kind of issue it is.
+- Before you report an issue, please search for related issues. Make sure you are not going to open a duplicate issue.
+- Explain your purpose clearly in tags(see **Useful Tags**), title, or content.
+
+AntV group members will confirm the purpose of the issue, replace more accurate tags for it, identify related milestone, and assign developers working on it.
+
+## Submitting Code
+
+### Pull Request Guide
+
+If you are developer of AntV repo and you are willing to contribute, feel free to create a new branch, finish your modification and submit a PR. AntV group will review your work and merge it to master branch.
+
+```bash
+# Create a new branch for development. The name of branch should be semantic, avoiding words like 'update' or 'tmp'. We suggest to use feature/xxx, if the modification is about to implement a new feature.
+$ git checkout -b branch-name
+
+# Run the test after you finish your modification. Add new test cases or change old ones if you feel necessary
+$ npm test
+
+# If your modification pass the tests, congradulations it's time to push your work back to us. Notice that the commit message should be wirtten in the following format.
+$ git add . # git add -u to delete files
+$ git commit -m "fix(role): role.use must xxx"
+$ git push origin branch-name
+```
+
+Then you can create a Pull Request at [g2](https://github.com/antvis/g2/pulls).
+
+No one can guarantee how much will be remembered about certain PR after some time. To make sure we can easily recap what happened previously, please provide the following information in your PR.
+
+1. Need: What function you want to achieve (Generally, please point out which issue is related).
+2. Updating Reason: Different with issue. Briefly describe your reason and logic about why you need to make such modification.
+3. Related Testing: Briefly describe what part of testing is relevant to your modification.
+4. User Tips: Notice for g2 users. You can skip this part, if the PR is not about update in API or potential compatibility problem.
+
+### Style Guide
+
+Eslint can help to identify styling issues that may exist in your code. Your code is required to pass the test from eslint. Run the test locally by `$ npm run lint`.
+
+### Commit Message Format
+
+You are encouraged to use [angular commit-message-format](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) to write commit message. In this way, we could have a more trackable history and an automatically generated changelog.
+
+```xml
+<type>(<scope>): <subject>
+<BLANK LINE>
+<body>
+<BLANK LINE>
+<footer>
+```
+
+(1)type
+
+Must be one of the following:
+
+- feat: A new feature
+- fix: A bug fix
+- docs: Documentation-only changes
+- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
+- refactor: A code change that neither fixes a bug nor adds a feature
+- perf: A code change that improves performance
+- test: Adding missing tests
+- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
+- deps: Updates about dependencies
+
+(2)scope
+
+The scope could be anything specifying place of the commit change.
+
+(3)subject
+
+Use succinct words to describe what did you do in the commit change.
+
+(4)body
+
+Feel free to add more content in the body, if you think subject is not self-explanatory enough, such as what it is the purpose or reasons of you commit.
+
+(5)footer
+
+- **If the commit is a Breaking Change, please note it clearly in this part.**
+- related issues, like `Closes #1, Closes #2, #3`
+
+e.g.
+
+```
+fix($compile): [BREAKING_CHANGE] couple of unit tests for IE9
+
+Older IEs serialize html uppercased, but IE9 does not...
+Would be better to expect case insensitive, unfortunately jasmine does
+not allow to user regexps for throw expectations.
+
+Document change on antvis/g2#123
+
+Closes #392
+
+BREAKING CHANGE:
+
+  Breaks foo.bar api, foo.baz should be used instead
+```
+
+Look at [these files](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) for more details.
+
+## Release
+
+g2 uses semantic versioning in release process based on [semver].
+
+### Branch Strategy
+
+`master` branch is the latest stable version. 
+
+- just checkout develop branch from `master`
+- All new features will be added into `master` or `next` branch as well as all bug-fix except security issues. In such way, we can motivate developers to update to the latest stable version.
+
+
+### Release Strategy
+
+In the release of every stable version, there will be a PM who has the following responsibilities in different stages of the release.
+
+#### Preparation
+
+- Set up milestone. Confirm that request is related to milestone.
+
+#### Before Release
+
+- Confirm that performance test is passed and all issues in current Milestone are either closed or can be delayed to later versions.
+- Open a new [Release Proposal MR], and write `History` as [node CHANGELOG]. Don't forget to correct content in documentation which is related to the releasing version. Commits can be generated automatically.
+    ```
+    $ npm run commits
+    ```
+- Nominate PM for next stable version.
+
+
+[semver]: http://semver.org/lang/zh-CN/
+[Release proposal MR]: https://github.com/nodejs/node/pull/4181
+[node CHANGELOG]: https://github.com/nodejs/node/blob/master/CHANGELOG.md
+[『我是如何发布一个 npm 包的』]: https://fengmk2.com/blog/2016/how-i-publish-a-npm-package

+ 148 - 0
CONTRIBUTING.zh-CN.md

@@ -0,0 +1,148 @@
+# 代码贡献规范
+
+有任何疑问,欢迎提交 [issue](https://github.com/antvis/g2/issues),
+或者直接修改提交 [PR](https://github.com/antvis/g2/pulls)!
+
+## 提交 issue
+
+- 请确定 issue 的类型。
+- 请避免提交重复的 issue,在提交之前搜索现有的 issue。
+- 在标签(分类参考**标签分类**), 标题 或者内容中体现明确的意图。
+
+随后 AntV 负责人会确认 issue 意图,更新合适的标签,关联 milestone,指派开发者。
+
+## 提交代码
+
+### 提交 Pull Request
+
+如果你有仓库的开发者权限,而且希望贡献代码,那么你可以创建分支修改代码提交 PR,AntV 开发团队会 review 代码合并到主干。
+
+```bash
+# 先创建开发分支开发,分支名应该有含义,避免使用 update、tmp 之类的
+$ git checkout -b branch-name
+
+# 开发完成后跑下测试是否通过,必要时需要新增或修改测试用例
+$ npm test
+
+# 测试通过后,提交代码,message 见下面的规范
+
+$ git add . # git add -u 删除文件
+$ git commit -m "fix(role): role.use must xxx"
+$ git push origin branch-name
+```
+
+提交后就可以在 [g2](https://github.com/antvis/g2/pulls) 创建 Pull Request 了。
+
+由于谁也无法保证过了多久之后还记得多少,为了后期回溯历史的方便,请在提交 MR 时确保提供了以下信息。
+
+1. 需求点(一般关联 issue 或者注释都算)
+2. 升级原因(不同于 issue,可以简要描述下为什么要处理)
+3. 框架测试点(可以关联到测试文件,不用详细描述,关键点即可)
+4. 关注点(针对用户而言,可以没有,一般是不兼容更新等,需要额外提示)
+
+### 代码风格
+
+你的代码风格必须通过 eslint,你可以运行 `$ npm run lint` 本地测试。
+
+### Commit 提交规范
+
+根据 [angular 规范](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format)提交 commit,
+这样 history 看起来更加清晰,还可以自动生成 changelog。
+
+```xml
+<type>(<scope>): <subject>
+<BLANK LINE>
+<body>
+<BLANK LINE>
+<footer>
+```
+
+(1)type
+
+提交 commit 的类型,包括以下几种
+
+- feat: 新功能
+- fix: 修复问题
+- docs: 修改文档
+- style: 修改代码格式,不影响代码逻辑
+- refactor: 重构代码,理论上不影响现有功能
+- perf: 提升性能
+- test: 增加修改测试用例
+- chore: 修改工具相关(包括但不限于文档、代码生成等)
+- deps: 升级依赖
+
+(2)scope
+
+修改文件的范围
+
+(3)subject
+
+用一句话清楚的描述这次提交做了什么
+
+(4)body
+
+补充 subject,适当增加原因、目的等相关因素,也可不写。
+
+(5)footer
+
+- **当有非兼容修改(Breaking Change)时必须在这里描述清楚**
+- 关联相关 issue,如 `Closes #1, Closes #2, #3`
+
+示例
+
+```
+fix($compile): [BREAKING_CHANGE] couple of unit tests for IE9
+
+Older IEs serialize html uppercased, but IE9 does not...
+Would be better to expect case insensitive, unfortunately jasmine does
+not allow to user regexps for throw expectations.
+
+Document change on antvis/g2#12
+
+Closes #392
+
+BREAKING CHANGE:
+
+  Breaks foo.bar api, foo.baz should be used instead
+```
+
+查看具体[文档](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)
+
+## 发布管理
+
+g2 基于 [semver] 语义化版本号进行发布。
+
+`master` 分支为当前稳定发布的版本。
+
+- 直接从 `master` 切出开发分支
+- 所有 API 的废弃都需要在当前的稳定版本上 `deprecate` 提示,并保证在当前的稳定版本上一直兼容到新版本的发布。
+
+### 发布策略
+
+每个大版本都有一个发布经理管理(PM),他/她要做的事情
+
+#### 准备工作:
+
+- 建立 milestone,确认需求关联 milestone,指派和更新 issues。
+
+#### 发布前:
+
+- 确认当前 Milestone 所有的 issue 都已关闭或可延期,完成性能测试。
+- 发起一个新的 [Release Proposal MR],按照 [node CHANGELOG] 进行 `History` 的编写,修正文档中与版本相关的内容,commits 可以自动生成。
+    ```bash
+    $ npm run commits
+    ```
+- 指定下一个大版本的 PM。
+
+#### 发布时:
+
+- 将老的稳定版本(master)备份到以当前大版本为名字的分支上(例如 `1.x`),并设置 tag 为 {v}.x`( v 为当前版本,例如 `1.x`)。
+- 发布新的稳定版本到 [npm],并通知上层框架进行更新。
+- `npm publish` 之前,请先阅读[『我是如何发布一个 npm 包的』]。
+
+
+[semver]: http://semver.org/lang/zh-CN/
+[Release proposal MR]: https://github.com/nodejs/node/pull/4181
+[node CHANGELOG]: https://github.com/nodejs/node/blob/master/CHANGELOG.md
+[npm]: http://npmjs.com/
+[『我是如何发布一个 npm 包的』]: https://fengmk2.com/blog/2016/how-i-publish-a-npm-package

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Alipay.inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 66 - 0
README-zh_CN.md

@@ -0,0 +1,66 @@
+# G6: JavaScript 图可视化引擎.
+
+![](https://img.shields.io/badge/language-javascript-red.svg)
+
+[English README](README.md)
+
+G6 是一个图可视化框架。它提供了一套图可视化的基础设置,能帮助开发者搭建属于自己的图 **图分析** 应用或是 **图编辑器** 应用。
+
+[详情见 G6 官方站点](https://antv.alipay.com/zh-cn/g6/1.x/index.html)
+
+<img src="https://gw.alipayobjects.com/zos/rmsportal/HQxYguinFOMIXrGQOABY.gif" width="300"/><img src="https://gw.alipayobjects.com/zos/rmsportal/JoZlKzpKunychGozAWKz.gif" width="370"/><img src="https://gw.alipayobjects.com/zos/rmsportal/nzmycBewjfxKDbepTDlT.gif" width="670"/><img src="https://gw.alipayobjects.com/zos/rmsportal/WVqnbgJmamdahbAuDpBL.gif" width="670"/>
+
+## 安装
+
+```bash
+$ npm install @antv/g6
+```
+
+<img src="https://gw.alipayobjects.com/zos/rmsportal/qSUOQUhnRrHCLvEjhZGP.png" />
+
+## Usage
+```js
+import G6 from '@antv/g6';
+
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200
+  }],
+  edges: [{
+    target: 'node2',
+    source: 'node1'
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  width: 500,
+  height: 500
+});
+graph.read(data);
+```
+
+## 开发
+
+```bash
+$ npm install
+
+# run test case
+$ npm run test-live
+
+# build watching file changes and run demos
+$ npm run dev
+```
+
+## 如何贡献
+
+Please let us know how can we help. Do check out [issues](https://github.com/antvis/g6/issues) for bug reports or suggestions first.
+
+请让我们知道您要解决或贡献什么,所以在贡献之前请先提交 [issues](https://github.com/antvis/g6/issues) 描述 bug 或建议。
+
+成为一个贡献者前请阅读 [代码贡献规范](https://github.com/antvis/g6/blob/master/CONTRIBUTING.zh-CN.md).

+ 64 - 0
README.md

@@ -0,0 +1,64 @@
+# G6: The Graph visualization framework in JavaScript.
+
+![](https://img.shields.io/badge/language-javascript-red.svg)
+
+[中文 README](README-zh_CN.md)
+
+G6 is a graph visualization framework. It provides a set of base mechanisms, help developers to build their own graph visualization **analysis** application or graph visualization **edit** application.
+
+[More details about G6.](https://antv.alipay.com/zh-cn/g6/1.x/index.html)
+
+<img src="https://gw.alipayobjects.com/zos/rmsportal/HQxYguinFOMIXrGQOABY.gif" width="300"/><img src="https://gw.alipayobjects.com/zos/rmsportal/JoZlKzpKunychGozAWKz.gif" width="370"/><img src="https://gw.alipayobjects.com/zos/rmsportal/nzmycBewjfxKDbepTDlT.gif" width="670"/><img src="https://gw.alipayobjects.com/zos/rmsportal/WVqnbgJmamdahbAuDpBL.gif" width="670"/>
+
+## Installation
+
+```bash
+$ npm install @antv/g6
+```
+
+<img src="https://gw.alipayobjects.com/zos/rmsportal/qSUOQUhnRrHCLvEjhZGP.png" />
+
+## Usage
+```js
+import G6 from '@antv/g6';
+
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200
+  }],
+  edges: [{
+    target: 'node2',
+    source: 'node1'
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  width: 500,
+  height: 500
+});
+graph.read(data);
+```
+
+## Development
+
+```bash
+$ npm install
+
+# run test case
+$ npm run test-live
+
+# build watching file changes and run demos
+$ npm run dev
+```
+
+## How to Contribute
+
+Please let us know how can we help. Do check out [issues](https://github.com/antvis/g6/issues) for bug reports or suggestions first.
+
+To become a contributor, please follow our [contributing guide](https://github.com/antvis/g6/blob/master/CONTRIBUTING.md).

+ 7 - 0
bin/cp-dist.js

@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+const path = require('path');
+const shelljs = require('shelljs');
+
+const src = path.join(process.cwd(), './build/*.js');
+const dist = path.join(process.cwd(), './dist');
+shelljs.cp(src, dist);

+ 7 - 0
bin/mkdir-dist.js

@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+const path = require('path');
+const shelljs = require('shelljs');
+
+const pathname = path.join(process.cwd(), './dist');
+shelljs.rm('-rf', pathname);
+shelljs.mkdir('-p', pathname);

+ 82 - 0
bin/screenshot.js

@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+process.env.DEBUG = 'app:*';
+const debug = require('debug')('app:screenshot');
+const MAX_POOL_SIZE = require('os').cpus().length;
+const Nightmare = require('nightmare');
+const _ = require('lodash');
+const commander = require('commander');
+const connect = require('connect');
+const getPort = require('get-port');
+const http = require('http');
+const path = require('path');
+const basename = path.basename;
+const extname = path.extname;
+const join = path.join;
+const queue = require('d3-queue').queue;
+const serveStatic = require('serve-static');
+const shelljs = require('shelljs');
+const ls = shelljs.ls;
+const mkdir = shelljs.mkdir;
+const pkg = require('../package.json');
+
+commander
+  .version(pkg.version)
+  .option('-p, --port <port>', 'specify a port number to run on', parseInt)
+  .option('-n, --name <name>', 'specify the name for demos')
+  .parse(process.argv);
+
+// assets
+const src = join(process.cwd(), './demos');
+const dest = join(process.cwd(), './demos/assets/screenshots');
+mkdir('-p', dest);
+
+const app = connect();
+app.use('/', serveStatic(process.cwd()));
+
+const DELAY = 10000;
+
+getPort().then(port => {
+  http.createServer(app).listen(port);
+  const url = 'http://127.0.0.1:' + port;
+  debug('server is ready on port ' + port + '! url: ' + url);
+
+  const q = queue(MAX_POOL_SIZE > 2 ? MAX_POOL_SIZE - 1 : MAX_POOL_SIZE);
+  const files = ls(src).filter(filename => (extname(filename) === '.html'));
+  files.forEach(filename => {
+    const name = basename(filename, '.html');
+    if (_.isString(commander.name) && filename.indexOf(commander.name) === -1) {
+      debug(`>>>>>>>>> skipping because filename not matched: ${name}`);
+      return;
+    }
+    q.defer(callback => {
+      const t0 = Date.now();
+      const nightmare = Nightmare({
+        gotoTimeout: 600000,
+        show: false
+      });
+      const url = `http://127.0.0.1:${port}/demos/${name}.html`;
+      const target = join(dest, `./${name}.png`);
+      nightmare.viewport(800, 450) // 16 x 9
+        .goto(url)
+        .wait(DELAY)
+        .screenshot(target, () => {
+          debug(name + ' took ' + (Date.now() - t0) + ' to take a screenshot.');
+          callback(null);
+        })
+        .end()
+        .catch(e => {
+          debug(url);
+          debug(target);
+          debug(name + ' failed to take a screenshot: ' + e);
+        });
+    });
+  });
+  q.awaitAll(error => {
+    if (error) {
+      debug(error);
+      process.exit(1);
+    }
+    debug('screenshots are all captured!');
+    process.exit();
+  });
+});

+ 14 - 0
bin/win-dev.js

@@ -0,0 +1,14 @@
+#!/usr/bin/env node
+const shelljs = require('shelljs');
+const exec = shelljs.exec;
+
+const childWatch = exec('npm run watch', {
+  async: true
+});
+childWatch.stdout.on('data', data => {
+  if (data.indexOf('Hash') === 0) {
+    exec('npm run demos-web', {
+      async: true
+    });
+  }
+});

+ 107 - 0
demos/InteractionBehaviourMode.html

@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>交互-模式与行为</title>
+</head>
+
+<body>
+  <button id="changeMode">切换模式</button>
+  <div id="mountNode"></div>
+  说明:事件在 G6 中是粒度最小的交互接口,一般最简单的交互,我们可以直接通过写事件完成。但在一些交互非常复杂的场景里,我们往往比较难管理各个事件之间信号量的传递。针对复杂交互的场景,G6 总结了一套行为模式的解决方案。
+  </br>
+  </br>
+  这套方案简单介绍就是:事件组成行为,行为构成模式。我们可以通过事件实现任何粒度的交互组成行为,再通过不同行为的组合,得出模式。通过模式的切换,我们可以将不同的交互很轻易分离开来,从而规避大量信号量的传递。
+  <script src="../build/g6.js"></script>
+  <script>
+    // 注册鼠标进入节点变红的行为
+    G6.registerBehaviour('mouseEnterFillRed', graph => {
+      graph.behaviourOn('node:mouseenter', ev => {
+        graph.update(ev.item, {
+          color: 'red'
+        });
+      });
+    });
+
+    // 注册鼠标进入节点变绿的行为
+    G6.registerBehaviour('mouseEnterFillGreen', graph => {
+      graph.behaviourOn('node:mouseenter', ev => {
+        graph.update(ev.item, {
+          color: 'green'
+        });
+      });
+    });
+
+    // 注册鼠标移出节点变原色的行为
+    G6.registerBehaviour('mouseLeaveResetFill', graph => {
+      graph.behaviourOn('node:mouseleave', ev => {
+        graph.update(ev.item, {
+          color: '#1890FF'
+        });
+      });
+    });
+    const data = {
+      nodes: [{
+        id: '事件',
+        x: 80,
+        y: 150,
+      }, {
+        id: '行为',
+        x: 200,
+        y: 150
+      }, {
+        id: '模式',
+        x: 320,
+        y: 150
+      }],
+      edges: [{
+        source: '事件',
+        target: '行为',
+        label: '组成'
+      }, {
+        source: '行为',
+        target: '模式',
+        label: '组成'
+      }]
+    };
+    let mode = 'red';
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      modes: {
+        red: ['mouseEnterFillRed', 'mouseLeaveResetFill'],
+        green: ['mouseEnterFillGreen', 'mouseLeaveResetFill']
+      },
+      mode,
+    });
+    graph.node({
+      label(model) {
+        return model.id;
+      }
+    });
+    graph.edge({
+      style() {
+        return {
+          endArrow: true
+        };
+      }
+    });
+    graph.read(data);
+
+    // 点击按钮切换模式
+    document.getElementById('changeMode').onclick = () => {
+      if (mode === 'red') {
+        graph.changeMode('green');
+        mode = 'green';
+      } else {
+        graph.changeMode('red');
+        mode = 'red';
+      }
+    };
+  </script>
+</body>
+</html>

+ 58 - 0
demos/InteractionPanNode.html

@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>交互-拖拽节点</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+    });
+    graph.read(data);
+    let node;
+    let dx;
+    let dy;
+    graph.on('node:dragstart', ev => {
+      const { item } = ev;
+      const model = item.getModel();
+      node = item;
+      dx = model.x - ev.x;
+      dy = model.y - ev.y;
+    });
+    graph.on('node:drag', ev => {
+      node && graph.update(node, {
+        x: ev.x + dx,
+        y: ev.y + dy
+      });
+    });
+    graph.on('node:dragend', ev => {
+      node = undefined;
+    });
+  </script>
+</body>
+</html>

+ 134 - 0
demos/app.js

@@ -0,0 +1,134 @@
+process.env.DEBUG = 'app:*';
+const debug = require('debug')('app:demos');
+const commander = require('commander');
+const connect = require('connect');
+const getPort = require('get-port');
+const http = require('http');
+const open = require('open');
+const serveStatic = require('serve-static');
+const parseurl = require('parseurl');
+const assign = require('lodash').assign;
+const path = require('path');
+const resolve = path.resolve;
+const extname = path.extname;
+const basename = path.basename;
+const join = path.join;
+const fs = require('fs');
+const statSync = fs.statSync;
+const lstatSync = fs.lstatSync;
+const readdirSync = fs.readdirSync;
+const readFileSync = fs.readFileSync;
+const mkdirSync = fs.mkdirSync;
+const nunjucks = require('nunjucks');
+const renderString = nunjucks.renderString;
+const pkg = require('../package.json');
+
+function isFile(source) {
+  return lstatSync(source).isFile();
+}
+
+function getFiles(source) {
+  return readdirSync(source).map(function(name) {
+    return join(source, name);
+  }).filter(isFile);
+}
+
+const screenshotsPath = join(process.cwd(), './demos/assets/screenshots');
+try {
+  statSync(screenshotsPath);
+} catch (e) {
+  mkdirSync(screenshotsPath);
+}
+
+commander
+  .version(pkg.version)
+  .option('-w, --web')
+  .option('-p, --port <port>', 'specify a port number to run on', parseInt)
+  .parse(process.argv);
+
+function startService(port) {
+  const server = connect();
+  server.use((req, res, next) => {
+    if (req.method === 'GET') {
+      const pathname = parseurl(req).pathname;
+      if (pathname === '/demos/index.html') {
+        const demoFiles = getFiles(__dirname)
+          .filter(filename => {
+            return extname(filename) === '.html';
+          })
+          .map(filename => {
+            const bn = basename(filename, '.html');
+            const file = {
+              screenshot: `/demos/assets/screenshots/${bn}.png`,
+              basename: bn,
+              content: readFileSync(filename),
+              filename
+            };
+            return file;
+          });
+        const template = readFileSync(join(__dirname, './index.njk'), 'utf8');
+        res.end(renderString(template, {
+          demoFiles
+        }));
+      } else {
+        next();
+      }
+    } else {
+      next();
+    }
+  });
+  server.use(serveStatic(process.cwd()));
+  http.createServer(server).listen(port);
+
+  const url = `http://127.0.0.1:${port}/demos/index.html`;
+  debug(`server started, demos available! ${url}`);
+
+  if (commander.web) {
+    debug('running on web!');
+    open(url);
+  } else {
+    debug('running on electron!');
+    const app = require('electron').app;
+    const BrowserWindow = require('electron').BrowserWindow;
+    const watch = require('torchjs/lib/watch');
+    const windowBoundsConfig = require('torchjs/lib/windowBoundsConfig')(
+      resolve(app.getPath('userData'), './g6-demos-config.json')
+    );
+
+    let win;
+    app.once('ready', () => {
+      win = new BrowserWindow(assign({
+        // transparent: true
+        webPreferences: {
+          nodeIntegration: false
+        }
+      }, windowBoundsConfig.get('demos')));
+      win.loadURL(url);
+      win.openDevTools();
+
+      win.on('close', () => {
+        windowBoundsConfig.set('demos', win.getBounds());
+      });
+      win.on('closed', () => {
+        win = null;
+      });
+      watch([
+        'demos/**/*.*',
+        'src/**/*.*'
+      ], () => {
+        win.webContents.reloadIgnoringCache();
+      });
+    });
+    app.on('window-all-closed', () => {
+      app.quit();
+    });
+  }
+}
+
+if (commander.port) {
+  startService(commander.port);
+} else {
+  getPort().then(port => {
+    startService(port);
+  });
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 0
demos/assets/bootstrap-4.1.0/bootstrap-grid.min.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 0
demos/assets/bootstrap-4.1.0/bootstrap.min.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 0
demos/assets/bootstrap-4.1.0/bootstrap.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 0
demos/assets/clipboard-1.7.1.min.js


+ 8 - 0
demos/assets/codemirror-5.27.4/.gitattributes

@@ -0,0 +1,8 @@
+*.txt   text
+*.js    text
+*.html  text
+*.md    text
+*.json  text
+*.yml   text
+*.css   text
+*.svg   text

+ 11 - 0
demos/assets/codemirror-5.27.4/.npmignore

@@ -0,0 +1,11 @@
+/node_modules
+/demo
+/doc
+/test
+/test*.html
+/index.html
+/mode/*/*test.js
+/mode/*/*.html
+/mode/index.html
+.*
+bin

+ 4 - 0
demos/assets/codemirror-5.27.4/.travis.yml

@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - stable
+sudo: false

+ 105 - 0
demos/assets/codemirror-5.27.4/addon/fold/brace-fold.js

@@ -0,0 +1,105 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerHelper("fold", "brace", function(cm, start) {
+  var line = start.line, lineText = cm.getLine(line);
+  var tokenType;
+
+  function findOpening(openCh) {
+    for (var at = start.ch, pass = 0;;) {
+      var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
+      if (found == -1) {
+        if (pass == 1) break;
+        pass = 1;
+        at = lineText.length;
+        continue;
+      }
+      if (pass == 1 && found < start.ch) break;
+      tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
+      if (!/^(comment|string)/.test(tokenType)) return found + 1;
+      at = found - 1;
+    }
+  }
+
+  var startToken = "{", endToken = "}", startCh = findOpening("{");
+  if (startCh == null) {
+    startToken = "[", endToken = "]";
+    startCh = findOpening("[");
+  }
+
+  if (startCh == null) return;
+  var count = 1, lastLine = cm.lastLine(), end, endCh;
+  outer: for (var i = line; i <= lastLine; ++i) {
+    var text = cm.getLine(i), pos = i == line ? startCh : 0;
+    for (;;) {
+      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
+      if (nextOpen < 0) nextOpen = text.length;
+      if (nextClose < 0) nextClose = text.length;
+      pos = Math.min(nextOpen, nextClose);
+      if (pos == text.length) break;
+      if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
+        if (pos == nextOpen) ++count;
+        else if (!--count) { end = i; endCh = pos; break outer; }
+      }
+      ++pos;
+    }
+  }
+  if (end == null || line == end && endCh == startCh) return;
+  return {from: CodeMirror.Pos(line, startCh),
+          to: CodeMirror.Pos(end, endCh)};
+});
+
+CodeMirror.registerHelper("fold", "import", function(cm, start) {
+  function hasImport(line) {
+    if (line < cm.firstLine() || line > cm.lastLine()) return null;
+    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
+    if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
+    if (start.type != "keyword" || start.string != "import") return null;
+    // Now find closing semicolon, return its position
+    for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
+      var text = cm.getLine(i), semi = text.indexOf(";");
+      if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
+    }
+  }
+
+  var startLine = start.line, has = hasImport(startLine), prev;
+  if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
+    return null;
+  for (var end = has.end;;) {
+    var next = hasImport(end.line + 1);
+    if (next == null) break;
+    end = next.end;
+  }
+  return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
+});
+
+CodeMirror.registerHelper("fold", "include", function(cm, start) {
+  function hasInclude(line) {
+    if (line < cm.firstLine() || line > cm.lastLine()) return null;
+    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
+    if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
+    if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
+  }
+
+  var startLine = start.line, has = hasInclude(startLine);
+  if (has == null || hasInclude(startLine - 1) != null) return null;
+  for (var end = startLine;;) {
+    var next = hasInclude(end + 1);
+    if (next == null) break;
+    ++end;
+  }
+  return {from: CodeMirror.Pos(startLine, has + 1),
+          to: cm.clipPos(CodeMirror.Pos(end))};
+});
+
+});

+ 59 - 0
demos/assets/codemirror-5.27.4/addon/fold/comment-fold.js

@@ -0,0 +1,59 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
+  return mode.blockCommentStart && mode.blockCommentEnd;
+}, function(cm, start) {
+  var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
+  if (!startToken || !endToken) return;
+  var line = start.line, lineText = cm.getLine(line);
+
+  var startCh;
+  for (var at = start.ch, pass = 0;;) {
+    var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
+    if (found == -1) {
+      if (pass == 1) return;
+      pass = 1;
+      at = lineText.length;
+      continue;
+    }
+    if (pass == 1 && found < start.ch) return;
+    if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
+        (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
+         !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
+      startCh = found + startToken.length;
+      break;
+    }
+    at = found - 1;
+  }
+
+  var depth = 1, lastLine = cm.lastLine(), end, endCh;
+  outer: for (var i = line; i <= lastLine; ++i) {
+    var text = cm.getLine(i), pos = i == line ? startCh : 0;
+    for (;;) {
+      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
+      if (nextOpen < 0) nextOpen = text.length;
+      if (nextClose < 0) nextClose = text.length;
+      pos = Math.min(nextOpen, nextClose);
+      if (pos == text.length) break;
+      if (pos == nextOpen) ++depth;
+      else if (!--depth) { end = i; endCh = pos; break outer; }
+      ++pos;
+    }
+  }
+  if (end == null || line == end && endCh == startCh) return;
+  return {from: CodeMirror.Pos(line, startCh),
+          to: CodeMirror.Pos(end, endCh)};
+});
+
+});

+ 150 - 0
demos/assets/codemirror-5.27.4/addon/fold/foldcode.js

@@ -0,0 +1,150 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  function doFold(cm, pos, options, force) {
+    if (options && options.call) {
+      var finder = options;
+      options = null;
+    } else {
+      var finder = getOption(cm, options, "rangeFinder");
+    }
+    if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
+    var minSize = getOption(cm, options, "minFoldSize");
+
+    function getRange(allowFolded) {
+      var range = finder(cm, pos);
+      if (!range || range.to.line - range.from.line < minSize) return null;
+      var marks = cm.findMarksAt(range.from);
+      for (var i = 0; i < marks.length; ++i) {
+        if (marks[i].__isFold && force !== "fold") {
+          if (!allowFolded) return null;
+          range.cleared = true;
+          marks[i].clear();
+        }
+      }
+      return range;
+    }
+
+    var range = getRange(true);
+    if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
+      pos = CodeMirror.Pos(pos.line - 1, 0);
+      range = getRange(false);
+    }
+    if (!range || range.cleared || force === "unfold") return;
+
+    var myWidget = makeWidget(cm, options);
+    CodeMirror.on(myWidget, "mousedown", function(e) {
+      myRange.clear();
+      CodeMirror.e_preventDefault(e);
+    });
+    var myRange = cm.markText(range.from, range.to, {
+      replacedWith: myWidget,
+      clearOnEnter: getOption(cm, options, "clearOnEnter"),
+      __isFold: true
+    });
+    myRange.on("clear", function(from, to) {
+      CodeMirror.signal(cm, "unfold", cm, from, to);
+    });
+    CodeMirror.signal(cm, "fold", cm, range.from, range.to);
+  }
+
+  function makeWidget(cm, options) {
+    var widget = getOption(cm, options, "widget");
+    if (typeof widget == "string") {
+      var text = document.createTextNode(widget);
+      widget = document.createElement("span");
+      widget.appendChild(text);
+      widget.className = "CodeMirror-foldmarker";
+    }
+    return widget;
+  }
+
+  // Clumsy backwards-compatible interface
+  CodeMirror.newFoldFunction = function(rangeFinder, widget) {
+    return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
+  };
+
+  // New-style interface
+  CodeMirror.defineExtension("foldCode", function(pos, options, force) {
+    doFold(this, pos, options, force);
+  });
+
+  CodeMirror.defineExtension("isFolded", function(pos) {
+    var marks = this.findMarksAt(pos);
+    for (var i = 0; i < marks.length; ++i)
+      if (marks[i].__isFold) return true;
+  });
+
+  CodeMirror.commands.toggleFold = function(cm) {
+    cm.foldCode(cm.getCursor());
+  };
+  CodeMirror.commands.fold = function(cm) {
+    cm.foldCode(cm.getCursor(), null, "fold");
+  };
+  CodeMirror.commands.unfold = function(cm) {
+    cm.foldCode(cm.getCursor(), null, "unfold");
+  };
+  CodeMirror.commands.foldAll = function(cm) {
+    cm.operation(function() {
+      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
+        cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
+    });
+  };
+  CodeMirror.commands.unfoldAll = function(cm) {
+    cm.operation(function() {
+      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
+        cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
+    });
+  };
+
+  CodeMirror.registerHelper("fold", "combine", function() {
+    var funcs = Array.prototype.slice.call(arguments, 0);
+    return function(cm, start) {
+      for (var i = 0; i < funcs.length; ++i) {
+        var found = funcs[i](cm, start);
+        if (found) return found;
+      }
+    };
+  });
+
+  CodeMirror.registerHelper("fold", "auto", function(cm, start) {
+    var helpers = cm.getHelpers(start, "fold");
+    for (var i = 0; i < helpers.length; i++) {
+      var cur = helpers[i](cm, start);
+      if (cur) return cur;
+    }
+  });
+
+  var defaultOptions = {
+    rangeFinder: CodeMirror.fold.auto,
+    widget: "\u2194",
+    minFoldSize: 0,
+    scanUp: false,
+    clearOnEnter: true
+  };
+
+  CodeMirror.defineOption("foldOptions", null);
+
+  function getOption(cm, options, name) {
+    if (options && options[name] !== undefined)
+      return options[name];
+    var editorOptions = cm.options.foldOptions;
+    if (editorOptions && editorOptions[name] !== undefined)
+      return editorOptions[name];
+    return defaultOptions[name];
+  }
+
+  CodeMirror.defineExtension("foldOption", function(options, name) {
+    return getOption(this, options, name);
+  });
+});

+ 20 - 0
demos/assets/codemirror-5.27.4/addon/fold/foldgutter.css

@@ -0,0 +1,20 @@
+.CodeMirror-foldmarker {
+  color: blue;
+  text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+  font-family: arial;
+  line-height: .3;
+  cursor: pointer;
+}
+.CodeMirror-foldgutter {
+  width: .7em;
+}
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+  cursor: pointer;
+}
+.CodeMirror-foldgutter-open:after {
+  content: "\25BE";
+}
+.CodeMirror-foldgutter-folded:after {
+  content: "\25B8";
+}

+ 146 - 0
demos/assets/codemirror-5.27.4/addon/fold/foldgutter.js

@@ -0,0 +1,146 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"), require("./foldcode"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror", "./foldcode"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
+    if (old && old != CodeMirror.Init) {
+      cm.clearGutter(cm.state.foldGutter.options.gutter);
+      cm.state.foldGutter = null;
+      cm.off("gutterClick", onGutterClick);
+      cm.off("change", onChange);
+      cm.off("viewportChange", onViewportChange);
+      cm.off("fold", onFold);
+      cm.off("unfold", onFold);
+      cm.off("swapDoc", onChange);
+    }
+    if (val) {
+      cm.state.foldGutter = new State(parseOptions(val));
+      updateInViewport(cm);
+      cm.on("gutterClick", onGutterClick);
+      cm.on("change", onChange);
+      cm.on("viewportChange", onViewportChange);
+      cm.on("fold", onFold);
+      cm.on("unfold", onFold);
+      cm.on("swapDoc", onChange);
+    }
+  });
+
+  var Pos = CodeMirror.Pos;
+
+  function State(options) {
+    this.options = options;
+    this.from = this.to = 0;
+  }
+
+  function parseOptions(opts) {
+    if (opts === true) opts = {};
+    if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
+    if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
+    if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
+    return opts;
+  }
+
+  function isFolded(cm, line) {
+    var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
+    for (var i = 0; i < marks.length; ++i)
+      if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
+  }
+
+  function marker(spec) {
+    if (typeof spec == "string") {
+      var elt = document.createElement("div");
+      elt.className = spec + " CodeMirror-guttermarker-subtle";
+      return elt;
+    } else {
+      return spec.cloneNode(true);
+    }
+  }
+
+  function updateFoldInfo(cm, from, to) {
+    var opts = cm.state.foldGutter.options, cur = from;
+    var minSize = cm.foldOption(opts, "minFoldSize");
+    var func = cm.foldOption(opts, "rangeFinder");
+    cm.eachLine(from, to, function(line) {
+      var mark = null;
+      if (isFolded(cm, cur)) {
+        mark = marker(opts.indicatorFolded);
+      } else {
+        var pos = Pos(cur, 0);
+        var range = func && func(cm, pos);
+        if (range && range.to.line - range.from.line >= minSize)
+          mark = marker(opts.indicatorOpen);
+      }
+      cm.setGutterMarker(line, opts.gutter, mark);
+      ++cur;
+    });
+  }
+
+  function updateInViewport(cm) {
+    var vp = cm.getViewport(), state = cm.state.foldGutter;
+    if (!state) return;
+    cm.operation(function() {
+      updateFoldInfo(cm, vp.from, vp.to);
+    });
+    state.from = vp.from; state.to = vp.to;
+  }
+
+  function onGutterClick(cm, line, gutter) {
+    var state = cm.state.foldGutter;
+    if (!state) return;
+    var opts = state.options;
+    if (gutter != opts.gutter) return;
+    var folded = isFolded(cm, line);
+    if (folded) folded.clear();
+    else cm.foldCode(Pos(line, 0), opts.rangeFinder);
+  }
+
+  function onChange(cm) {
+    var state = cm.state.foldGutter;
+    if (!state) return;
+    var opts = state.options;
+    state.from = state.to = 0;
+    clearTimeout(state.changeUpdate);
+    state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
+  }
+
+  function onViewportChange(cm) {
+    var state = cm.state.foldGutter;
+    if (!state) return;
+    var opts = state.options;
+    clearTimeout(state.changeUpdate);
+    state.changeUpdate = setTimeout(function() {
+      var vp = cm.getViewport();
+      if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
+        updateInViewport(cm);
+      } else {
+        cm.operation(function() {
+          if (vp.from < state.from) {
+            updateFoldInfo(cm, vp.from, state.from);
+            state.from = vp.from;
+          }
+          if (vp.to > state.to) {
+            updateFoldInfo(cm, state.to, vp.to);
+            state.to = vp.to;
+          }
+        });
+      }
+    }, opts.updateViewportTimeSpan || 400);
+  }
+
+  function onFold(cm, from) {
+    var state = cm.state.foldGutter;
+    if (!state) return;
+    var line = from.line;
+    if (line >= state.from && line < state.to)
+      updateFoldInfo(cm, line, line + 1);
+  }
+});

+ 182 - 0
demos/assets/codemirror-5.27.4/addon/fold/xml-fold.js

@@ -0,0 +1,182 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var Pos = CodeMirror.Pos;
+  function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }
+
+  var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
+  var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
+  var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g");
+
+  function Iter(cm, line, ch, range) {
+    this.line = line; this.ch = ch;
+    this.cm = cm; this.text = cm.getLine(line);
+    this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
+    this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
+  }
+
+  function tagAt(iter, ch) {
+    var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));
+    return type && /\btag\b/.test(type);
+  }
+
+  function nextLine(iter) {
+    if (iter.line >= iter.max) return;
+    iter.ch = 0;
+    iter.text = iter.cm.getLine(++iter.line);
+    return true;
+  }
+  function prevLine(iter) {
+    if (iter.line <= iter.min) return;
+    iter.text = iter.cm.getLine(--iter.line);
+    iter.ch = iter.text.length;
+    return true;
+  }
+
+  function toTagEnd(iter) {
+    for (;;) {
+      var gt = iter.text.indexOf(">", iter.ch);
+      if (gt == -1) { if (nextLine(iter)) continue; else return; }
+      if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }
+      var lastSlash = iter.text.lastIndexOf("/", gt);
+      var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
+      iter.ch = gt + 1;
+      return selfClose ? "selfClose" : "regular";
+    }
+  }
+  function toTagStart(iter) {
+    for (;;) {
+      var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1;
+      if (lt == -1) { if (prevLine(iter)) continue; else return; }
+      if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }
+      xmlTagStart.lastIndex = lt;
+      iter.ch = lt;
+      var match = xmlTagStart.exec(iter.text);
+      if (match && match.index == lt) return match;
+    }
+  }
+
+  function toNextTag(iter) {
+    for (;;) {
+      xmlTagStart.lastIndex = iter.ch;
+      var found = xmlTagStart.exec(iter.text);
+      if (!found) { if (nextLine(iter)) continue; else return; }
+      if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }
+      iter.ch = found.index + found[0].length;
+      return found;
+    }
+  }
+  function toPrevTag(iter) {
+    for (;;) {
+      var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1;
+      if (gt == -1) { if (prevLine(iter)) continue; else return; }
+      if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }
+      var lastSlash = iter.text.lastIndexOf("/", gt);
+      var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
+      iter.ch = gt + 1;
+      return selfClose ? "selfClose" : "regular";
+    }
+  }
+
+  function findMatchingClose(iter, tag) {
+    var stack = [];
+    for (;;) {
+      var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);
+      if (!next || !(end = toTagEnd(iter))) return;
+      if (end == "selfClose") continue;
+      if (next[1]) { // closing tag
+        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {
+          stack.length = i;
+          break;
+        }
+        if (i < 0 && (!tag || tag == next[2])) return {
+          tag: next[2],
+          from: Pos(startLine, startCh),
+          to: Pos(iter.line, iter.ch)
+        };
+      } else { // opening tag
+        stack.push(next[2]);
+      }
+    }
+  }
+  function findMatchingOpen(iter, tag) {
+    var stack = [];
+    for (;;) {
+      var prev = toPrevTag(iter);
+      if (!prev) return;
+      if (prev == "selfClose") { toTagStart(iter); continue; }
+      var endLine = iter.line, endCh = iter.ch;
+      var start = toTagStart(iter);
+      if (!start) return;
+      if (start[1]) { // closing tag
+        stack.push(start[2]);
+      } else { // opening tag
+        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {
+          stack.length = i;
+          break;
+        }
+        if (i < 0 && (!tag || tag == start[2])) return {
+          tag: start[2],
+          from: Pos(iter.line, iter.ch),
+          to: Pos(endLine, endCh)
+        };
+      }
+    }
+  }
+
+  CodeMirror.registerHelper("fold", "xml", function(cm, start) {
+    var iter = new Iter(cm, start.line, 0);
+    for (;;) {
+      var openTag = toNextTag(iter), end;
+      if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
+      if (!openTag[1] && end != "selfClose") {
+        var startPos = Pos(iter.line, iter.ch);
+        var endPos = findMatchingClose(iter, openTag[2]);
+        return endPos && {from: startPos, to: endPos.from};
+      }
+    }
+  });
+  CodeMirror.findMatchingTag = function(cm, pos, range) {
+    var iter = new Iter(cm, pos.line, pos.ch, range);
+    if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
+    var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
+    var start = end && toTagStart(iter);
+    if (!end || !start || cmp(iter, pos) > 0) return;
+    var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
+    if (end == "selfClose") return {open: here, close: null, at: "open"};
+
+    if (start[1]) { // closing tag
+      return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
+    } else { // opening tag
+      iter = new Iter(cm, to.line, to.ch, range);
+      return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
+    }
+  };
+
+  CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {
+    var iter = new Iter(cm, pos.line, pos.ch, range);
+    for (;;) {
+      var open = findMatchingOpen(iter, tag);
+      if (!open) break;
+      var forward = new Iter(cm, pos.line, pos.ch, range);
+      var close = findMatchingClose(forward, open.tag);
+      if (close) return {open: open, close: close};
+    }
+  };
+
+  // Used by addon/edit/closetag.js
+  CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
+    var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
+    return findMatchingClose(iter, name);
+  };
+});

+ 41 - 0
demos/assets/codemirror-5.27.4/addon/hint/anyword-hint.js

@@ -0,0 +1,41 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var WORD = /[\w$]+/, RANGE = 500;
+
+  CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
+    var word = options && options.word || WORD;
+    var range = options && options.range || RANGE;
+    var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
+    var end = cur.ch, start = end;
+    while (start && word.test(curLine.charAt(start - 1))) --start;
+    var curWord = start != end && curLine.slice(start, end);
+
+    var list = options && options.list || [], seen = {};
+    var re = new RegExp(word.source, "g");
+    for (var dir = -1; dir <= 1; dir += 2) {
+      var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
+      for (; line != endLine; line += dir) {
+        var text = editor.getLine(line), m;
+        while (m = re.exec(text)) {
+          if (line == cur.line && m[0] === curWord) continue;
+          if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
+            seen[m[0]] = true;
+            list.push(m[0]);
+          }
+        }
+      }
+    }
+    return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
+  });
+});

+ 60 - 0
demos/assets/codemirror-5.27.4/addon/hint/css-hint.js

@@ -0,0 +1,60 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"), require("../../mode/css/css"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror", "../../mode/css/css"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
+                       "first-letter": 1, "first-line": 1, "first-child": 1,
+                       before: 1, after: 1, lang: 1};
+
+  CodeMirror.registerHelper("hint", "css", function(cm) {
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    var inner = CodeMirror.innerMode(cm.getMode(), token.state);
+    if (inner.mode.name != "css") return;
+
+    if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
+      return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
+              to: CodeMirror.Pos(cur.line, token.end)};
+
+    var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
+    if (/[^\w$_-]/.test(word)) {
+      word = ""; start = end = cur.ch;
+    }
+
+    var spec = CodeMirror.resolveMode("text/css");
+
+    var result = [];
+    function add(keywords) {
+      for (var name in keywords)
+        if (!word || name.lastIndexOf(word, 0) == 0)
+          result.push(name);
+    }
+
+    var st = inner.state.state;
+    if (st == "pseudo" || token.type == "variable-3") {
+      add(pseudoClasses);
+    } else if (st == "block" || st == "maybeprop") {
+      add(spec.propertyKeywords);
+    } else if (st == "prop" || st == "parens" || st == "at" || st == "params") {
+      add(spec.valueKeywords);
+      add(spec.colorKeywords);
+    } else if (st == "media" || st == "media_parens") {
+      add(spec.mediaTypes);
+      add(spec.mediaFeatures);
+    }
+
+    if (result.length) return {
+      list: result,
+      from: CodeMirror.Pos(cur.line, start),
+      to: CodeMirror.Pos(cur.line, end)
+    };
+  });
+});

Diferenças do arquivo suprimidas por serem muito extensas
+ 348 - 0
demos/assets/codemirror-5.27.4/addon/hint/html-hint.js


+ 155 - 0
demos/assets/codemirror-5.27.4/addon/hint/javascript-hint.js

@@ -0,0 +1,155 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  var Pos = CodeMirror.Pos;
+
+  function forEach(arr, f) {
+    for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
+  }
+
+  function arrayContains(arr, item) {
+    if (!Array.prototype.indexOf) {
+      var i = arr.length;
+      while (i--) {
+        if (arr[i] === item) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return arr.indexOf(item) != -1;
+  }
+
+  function scriptHint(editor, keywords, getToken, options) {
+    // Find the token at the cursor
+    var cur = editor.getCursor(), token = getToken(editor, cur);
+    if (/\b(?:string|comment)\b/.test(token.type)) return;
+    token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
+
+    // If it's not a 'word-style' token, ignore the token.
+    if (!/^[\w$_]*$/.test(token.string)) {
+      token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
+               type: token.string == "." ? "property" : null};
+    } else if (token.end > cur.ch) {
+      token.end = cur.ch;
+      token.string = token.string.slice(0, cur.ch - token.start);
+    }
+
+    var tprop = token;
+    // If it is a property, find out what it is a property of.
+    while (tprop.type == "property") {
+      tprop = getToken(editor, Pos(cur.line, tprop.start));
+      if (tprop.string != ".") return;
+      tprop = getToken(editor, Pos(cur.line, tprop.start));
+      if (!context) var context = [];
+      context.push(tprop);
+    }
+    return {list: getCompletions(token, context, keywords, options),
+            from: Pos(cur.line, token.start),
+            to: Pos(cur.line, token.end)};
+  }
+
+  function javascriptHint(editor, options) {
+    return scriptHint(editor, javascriptKeywords,
+                      function (e, cur) {return e.getTokenAt(cur);},
+                      options);
+  };
+  CodeMirror.registerHelper("hint", "javascript", javascriptHint);
+
+  function getCoffeeScriptToken(editor, cur) {
+  // This getToken, it is for coffeescript, imitates the behavior of
+  // getTokenAt method in javascript.js, that is, returning "property"
+  // type and treat "." as indepenent token.
+    var token = editor.getTokenAt(cur);
+    if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
+      token.end = token.start;
+      token.string = '.';
+      token.type = "property";
+    }
+    else if (/^\.[\w$_]*$/.test(token.string)) {
+      token.type = "property";
+      token.start++;
+      token.string = token.string.replace(/\./, '');
+    }
+    return token;
+  }
+
+  function coffeescriptHint(editor, options) {
+    return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
+  }
+  CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
+
+  var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
+                     "toUpperCase toLowerCase split concat match replace search").split(" ");
+  var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
+                    "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
+  var funcProps = "prototype apply call bind".split(" ");
+  var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
+                  "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
+  var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
+                  "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
+
+  function forAllProps(obj, callback) {
+    if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
+      for (var name in obj) callback(name)
+    } else {
+      for (var o = obj; o; o = Object.getPrototypeOf(o))
+        Object.getOwnPropertyNames(o).forEach(callback)
+    }
+  }
+
+  function getCompletions(token, context, keywords, options) {
+    var found = [], start = token.string, global = options && options.globalScope || window;
+    function maybeAdd(str) {
+      if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
+    }
+    function gatherCompletions(obj) {
+      if (typeof obj == "string") forEach(stringProps, maybeAdd);
+      else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
+      else if (obj instanceof Function) forEach(funcProps, maybeAdd);
+      forAllProps(obj, maybeAdd)
+    }
+
+    if (context && context.length) {
+      // If this is a property, see if it belongs to some object we can
+      // find in the current environment.
+      var obj = context.pop(), base;
+      if (obj.type && obj.type.indexOf("variable") === 0) {
+        if (options && options.additionalContext)
+          base = options.additionalContext[obj.string];
+        if (!options || options.useGlobalScope !== false)
+          base = base || global[obj.string];
+      } else if (obj.type == "string") {
+        base = "";
+      } else if (obj.type == "atom") {
+        base = 1;
+      } else if (obj.type == "function") {
+        if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
+            (typeof global.jQuery == 'function'))
+          base = global.jQuery();
+        else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
+          base = global._();
+      }
+      while (base != null && context.length)
+        base = base[context.pop().string];
+      if (base != null) gatherCompletions(base);
+    } else {
+      // If not, just look in the global object and any local scope
+      // (reading into JS mode internals to get at the local and global variables)
+      for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
+      for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
+      if (!options || options.useGlobalScope !== false)
+        gatherCompletions(global);
+      forEach(keywords, maybeAdd);
+    }
+    return found;
+  }
+});

+ 36 - 0
demos/assets/codemirror-5.27.4/addon/hint/show-hint.css

@@ -0,0 +1,36 @@
+.CodeMirror-hints {
+  position: absolute;
+  z-index: 10;
+  overflow: hidden;
+  list-style: none;
+
+  margin: 0;
+  padding: 2px;
+
+  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  border-radius: 3px;
+  border: 1px solid silver;
+
+  background: white;
+  font-size: 90%;
+  font-family: monospace;
+
+  max-height: 20em;
+  overflow-y: auto;
+}
+
+.CodeMirror-hint {
+  margin: 0;
+  padding: 0 4px;
+  border-radius: 2px;
+  white-space: pre;
+  color: black;
+  cursor: pointer;
+}
+
+li.CodeMirror-hint-active {
+  background: #08f;
+  color: white;
+}

+ 438 - 0
demos/assets/codemirror-5.27.4/addon/hint/show-hint.js

@@ -0,0 +1,438 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var HINT_ELEMENT_CLASS        = "CodeMirror-hint";
+  var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
+
+  // This is the old interface, kept around for now to stay
+  // backwards-compatible.
+  CodeMirror.showHint = function(cm, getHints, options) {
+    if (!getHints) return cm.showHint(options);
+    if (options && options.async) getHints.async = true;
+    var newOpts = {hint: getHints};
+    if (options) for (var prop in options) newOpts[prop] = options[prop];
+    return cm.showHint(newOpts);
+  };
+
+  CodeMirror.defineExtension("showHint", function(options) {
+    options = parseOptions(this, this.getCursor("start"), options);
+    var selections = this.listSelections()
+    if (selections.length > 1) return;
+    // By default, don't allow completion when something is selected.
+    // A hint function can have a `supportsSelection` property to
+    // indicate that it can handle selections.
+    if (this.somethingSelected()) {
+      if (!options.hint.supportsSelection) return;
+      // Don't try with cross-line selections
+      for (var i = 0; i < selections.length; i++)
+        if (selections[i].head.line != selections[i].anchor.line) return;
+    }
+
+    if (this.state.completionActive) this.state.completionActive.close();
+    var completion = this.state.completionActive = new Completion(this, options);
+    if (!completion.options.hint) return;
+
+    CodeMirror.signal(this, "startCompletion", this);
+    completion.update(true);
+  });
+
+  function Completion(cm, options) {
+    this.cm = cm;
+    this.options = options;
+    this.widget = null;
+    this.debounce = 0;
+    this.tick = 0;
+    this.startPos = this.cm.getCursor("start");
+    this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
+
+    var self = this;
+    cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
+  }
+
+  var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+    return setTimeout(fn, 1000/60);
+  };
+  var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
+  Completion.prototype = {
+    close: function() {
+      if (!this.active()) return;
+      this.cm.state.completionActive = null;
+      this.tick = null;
+      this.cm.off("cursorActivity", this.activityFunc);
+
+      if (this.widget && this.data) CodeMirror.signal(this.data, "close");
+      if (this.widget) this.widget.close();
+      CodeMirror.signal(this.cm, "endCompletion", this.cm);
+    },
+
+    active: function() {
+      return this.cm.state.completionActive == this;
+    },
+
+    pick: function(data, i) {
+      var completion = data.list[i];
+      if (completion.hint) completion.hint(this.cm, data, completion);
+      else this.cm.replaceRange(getText(completion), completion.from || data.from,
+                                completion.to || data.to, "complete");
+      CodeMirror.signal(data, "pick", completion);
+      this.close();
+    },
+
+    cursorActivity: function() {
+      if (this.debounce) {
+        cancelAnimationFrame(this.debounce);
+        this.debounce = 0;
+      }
+
+      var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
+      if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
+          pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
+          (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
+        this.close();
+      } else {
+        var self = this;
+        this.debounce = requestAnimationFrame(function() {self.update();});
+        if (this.widget) this.widget.disable();
+      }
+    },
+
+    update: function(first) {
+      if (this.tick == null) return
+      var self = this, myTick = ++this.tick
+      fetchHints(this.options.hint, this.cm, this.options, function(data) {
+        if (self.tick == myTick) self.finishUpdate(data, first)
+      })
+    },
+
+    finishUpdate: function(data, first) {
+      if (this.data) CodeMirror.signal(this.data, "update");
+
+      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
+      if (this.widget) this.widget.close();
+
+      if (data && this.data && isNewCompletion(this.data, data)) return;
+      this.data = data;
+
+      if (data && data.list.length) {
+        if (picked && data.list.length == 1) {
+          this.pick(data, 0);
+        } else {
+          this.widget = new Widget(this, data);
+          CodeMirror.signal(data, "shown");
+        }
+      }
+    }
+  };
+
+  function isNewCompletion(old, nw) {
+    var moved = CodeMirror.cmpPos(nw.from, old.from)
+    return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
+  }
+
+  function parseOptions(cm, pos, options) {
+    var editor = cm.options.hintOptions;
+    var out = {};
+    for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
+    if (editor) for (var prop in editor)
+      if (editor[prop] !== undefined) out[prop] = editor[prop];
+    if (options) for (var prop in options)
+      if (options[prop] !== undefined) out[prop] = options[prop];
+    if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
+    return out;
+  }
+
+  function getText(completion) {
+    if (typeof completion == "string") return completion;
+    else return completion.text;
+  }
+
+  function buildKeyMap(completion, handle) {
+    var baseMap = {
+      Up: function() {handle.moveFocus(-1);},
+      Down: function() {handle.moveFocus(1);},
+      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
+      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
+      Home: function() {handle.setFocus(0);},
+      End: function() {handle.setFocus(handle.length - 1);},
+      Enter: handle.pick,
+      Tab: handle.pick,
+      Esc: handle.close
+    };
+    var custom = completion.options.customKeys;
+    var ourMap = custom ? {} : baseMap;
+    function addBinding(key, val) {
+      var bound;
+      if (typeof val != "string")
+        bound = function(cm) { return val(cm, handle); };
+      // This mechanism is deprecated
+      else if (baseMap.hasOwnProperty(val))
+        bound = baseMap[val];
+      else
+        bound = val;
+      ourMap[key] = bound;
+    }
+    if (custom)
+      for (var key in custom) if (custom.hasOwnProperty(key))
+        addBinding(key, custom[key]);
+    var extra = completion.options.extraKeys;
+    if (extra)
+      for (var key in extra) if (extra.hasOwnProperty(key))
+        addBinding(key, extra[key]);
+    return ourMap;
+  }
+
+  function getHintElement(hintsElement, el) {
+    while (el && el != hintsElement) {
+      if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
+      el = el.parentNode;
+    }
+  }
+
+  function Widget(completion, data) {
+    this.completion = completion;
+    this.data = data;
+    this.picked = false;
+    var widget = this, cm = completion.cm;
+
+    var hints = this.hints = document.createElement("ul");
+    hints.className = "CodeMirror-hints";
+    this.selectedHint = data.selectedHint || 0;
+
+    var completions = data.list;
+    for (var i = 0; i < completions.length; ++i) {
+      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
+      if (cur.className != null) className = cur.className + " " + className;
+      elt.className = className;
+      if (cur.render) cur.render(elt, data, cur);
+      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+      elt.hintId = i;
+    }
+
+    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
+    var left = pos.left, top = pos.bottom, below = true;
+    hints.style.left = left + "px";
+    hints.style.top = top + "px";
+    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
+    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
+    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+    (completion.options.container || document.body).appendChild(hints);
+    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+    var scrolls = hints.scrollHeight > hints.clientHeight + 1
+    var startScroll = cm.getScrollInfo();
+
+    if (overlapY > 0) {
+      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
+      if (curTop - height > 0) { // Fits above cursor
+        hints.style.top = (top = pos.top - height) + "px";
+        below = false;
+      } else if (height > winH) {
+        hints.style.height = (winH - 5) + "px";
+        hints.style.top = (top = pos.bottom - box.top) + "px";
+        var cursor = cm.getCursor();
+        if (data.from.ch != cursor.ch) {
+          pos = cm.cursorCoords(cursor);
+          hints.style.left = (left = pos.left) + "px";
+          box = hints.getBoundingClientRect();
+        }
+      }
+    }
+    var overlapX = box.right - winW;
+    if (overlapX > 0) {
+      if (box.right - box.left > winW) {
+        hints.style.width = (winW - 5) + "px";
+        overlapX -= (box.right - box.left) - winW;
+      }
+      hints.style.left = (left = pos.left - overlapX) + "px";
+    }
+    if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
+      node.style.paddingRight = cm.display.nativeBarWidth + "px"
+
+    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
+      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
+      setFocus: function(n) { widget.changeActive(n); },
+      menuSize: function() { return widget.screenAmount(); },
+      length: completions.length,
+      close: function() { completion.close(); },
+      pick: function() { widget.pick(); },
+      data: data
+    }));
+
+    if (completion.options.closeOnUnfocus) {
+      var closingOnBlur;
+      cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
+      cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
+    }
+
+    cm.on("scroll", this.onScroll = function() {
+      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+      var newTop = top + startScroll.top - curScroll.top;
+      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+      if (!below) point += hints.offsetHeight;
+      if (point <= editor.top || point >= editor.bottom) return completion.close();
+      hints.style.top = newTop + "px";
+      hints.style.left = (left + startScroll.left - curScroll.left) + "px";
+    });
+
+    CodeMirror.on(hints, "dblclick", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
+    });
+
+    CodeMirror.on(hints, "click", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {
+        widget.changeActive(t.hintId);
+        if (completion.options.completeOnSingleClick) widget.pick();
+      }
+    });
+
+    CodeMirror.on(hints, "mousedown", function() {
+      setTimeout(function(){cm.focus();}, 20);
+    });
+
+    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+    return true;
+  }
+
+  Widget.prototype = {
+    close: function() {
+      if (this.completion.widget != this) return;
+      this.completion.widget = null;
+      this.hints.parentNode.removeChild(this.hints);
+      this.completion.cm.removeKeyMap(this.keyMap);
+
+      var cm = this.completion.cm;
+      if (this.completion.options.closeOnUnfocus) {
+        cm.off("blur", this.onBlur);
+        cm.off("focus", this.onFocus);
+      }
+      cm.off("scroll", this.onScroll);
+    },
+
+    disable: function() {
+      this.completion.cm.removeKeyMap(this.keyMap);
+      var widget = this;
+      this.keyMap = {Enter: function() { widget.picked = true; }};
+      this.completion.cm.addKeyMap(this.keyMap);
+    },
+
+    pick: function() {
+      this.completion.pick(this.data, this.selectedHint);
+    },
+
+    changeActive: function(i, avoidWrap) {
+      if (i >= this.data.list.length)
+        i = avoidWrap ? this.data.list.length - 1 : 0;
+      else if (i < 0)
+        i = avoidWrap ? 0  : this.data.list.length - 1;
+      if (this.selectedHint == i) return;
+      var node = this.hints.childNodes[this.selectedHint];
+      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+      node = this.hints.childNodes[this.selectedHint = i];
+      node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
+      if (node.offsetTop < this.hints.scrollTop)
+        this.hints.scrollTop = node.offsetTop - 3;
+      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+      CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
+    },
+
+    screenAmount: function() {
+      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+    }
+  };
+
+  function applicableHelpers(cm, helpers) {
+    if (!cm.somethingSelected()) return helpers
+    var result = []
+    for (var i = 0; i < helpers.length; i++)
+      if (helpers[i].supportsSelection) result.push(helpers[i])
+    return result
+  }
+
+  function fetchHints(hint, cm, options, callback) {
+    if (hint.async) {
+      hint(cm, callback, options)
+    } else {
+      var result = hint(cm, options)
+      if (result && result.then) result.then(callback)
+      else callback(result)
+    }
+  }
+
+  function resolveAutoHints(cm, pos) {
+    var helpers = cm.getHelpers(pos, "hint"), words
+    if (helpers.length) {
+      var resolved = function(cm, callback, options) {
+        var app = applicableHelpers(cm, helpers);
+        function run(i) {
+          if (i == app.length) return callback(null)
+          fetchHints(app[i], cm, options, function(result) {
+            if (result && result.list.length > 0) callback(result)
+            else run(i + 1)
+          })
+        }
+        run(0)
+      }
+      resolved.async = true
+      resolved.supportsSelection = true
+      return resolved
+    } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
+      return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
+    } else if (CodeMirror.hint.anyword) {
+      return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
+    } else {
+      return function() {}
+    }
+  }
+
+  CodeMirror.registerHelper("hint", "auto", {
+    resolve: resolveAutoHints
+  });
+
+  CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    var to = CodeMirror.Pos(cur.line, token.end);
+    if (token.string && /\w/.test(token.string[token.string.length - 1])) {
+      var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
+    } else {
+      var term = "", from = to;
+    }
+    var found = [];
+    for (var i = 0; i < options.words.length; i++) {
+      var word = options.words[i];
+      if (word.slice(0, term.length) == term)
+        found.push(word);
+    }
+
+    if (found.length) return {list: found, from: from, to: to};
+  });
+
+  CodeMirror.commands.autocomplete = CodeMirror.showHint;
+
+  var defaultOptions = {
+    hint: CodeMirror.hint.auto,
+    completeSingle: true,
+    alignWithWord: true,
+    closeCharacters: /[\s()\[\]{};:>,]/,
+    closeOnUnfocus: true,
+    completeOnSingleClick: true,
+    container: null,
+    customKeys: null,
+    extraKeys: null
+  };
+
+  CodeMirror.defineOption("hintOptions", null);
+});

+ 110 - 0
demos/assets/codemirror-5.27.4/addon/hint/xml-hint.js

@@ -0,0 +1,110 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var Pos = CodeMirror.Pos;
+
+  function getHints(cm, options) {
+    var tags = options && options.schemaInfo;
+    var quote = (options && options.quoteChar) || '"';
+    if (!tags) return;
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    if (token.end > cur.ch) {
+      token.end = cur.ch;
+      token.string = token.string.slice(0, cur.ch - token.start);
+    }
+    var inner = CodeMirror.innerMode(cm.getMode(), token.state);
+    if (inner.mode.name != "xml") return;
+    var result = [], replaceToken = false, prefix;
+    var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
+    var tagName = tag && /^\w/.test(token.string), tagStart;
+
+    if (tagName) {
+      var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
+      var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
+      if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1);
+    } else if (tag && token.string == "<") {
+      tagType = "open";
+    } else if (tag && token.string == "</") {
+      tagType = "close";
+    }
+
+    if (!tag && !inner.state.tagName || tagType) {
+      if (tagName)
+        prefix = token.string;
+      replaceToken = tagType;
+      var cx = inner.state.context, curTag = cx && tags[cx.tagName];
+      var childList = cx ? curTag && curTag.children : tags["!top"];
+      if (childList && tagType != "close") {
+        for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
+          result.push("<" + childList[i]);
+      } else if (tagType != "close") {
+        for (var name in tags)
+          if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
+            result.push("<" + name);
+      }
+      if (cx && (!prefix || tagType == "close" && cx.tagName.lastIndexOf(prefix, 0) == 0))
+        result.push("</" + cx.tagName + ">");
+    } else {
+      // Attribute completion
+      var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs;
+      var globalAttrs = tags["!attrs"];
+      if (!attrs && !globalAttrs) return;
+      if (!attrs) {
+        attrs = globalAttrs;
+      } else if (globalAttrs) { // Combine tag-local and global attributes
+        var set = {};
+        for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];
+        for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];
+        attrs = set;
+      }
+      if (token.type == "string" || token.string == "=") { // A value
+        var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),
+                                 Pos(cur.line, token.type == "string" ? token.start : token.end));
+        var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues;
+        if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;
+        if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget
+        if (token.type == "string") {
+          prefix = token.string;
+          var n = 0;
+          if (/['"]/.test(token.string.charAt(0))) {
+            quote = token.string.charAt(0);
+            prefix = token.string.slice(1);
+            n++;
+          }
+          var len = token.string.length;
+          if (/['"]/.test(token.string.charAt(len - 1))) {
+            quote = token.string.charAt(len - 1);
+            prefix = token.string.substr(n, len - 2);
+          }
+          replaceToken = true;
+        }
+        for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0)
+          result.push(quote + atValues[i] + quote);
+      } else { // An attribute name
+        if (token.type == "attribute") {
+          prefix = token.string;
+          replaceToken = true;
+        }
+        for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0))
+          result.push(attr);
+      }
+    }
+    return {
+      list: result,
+      from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
+      to: replaceToken ? Pos(cur.line, token.end) : cur
+    };
+  }
+
+  CodeMirror.registerHelper("hint", "xml", getHints);
+});

+ 830 - 0
demos/assets/codemirror-5.27.4/mode/css/css.js

@@ -0,0 +1,830 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.defineMode("css", function(config, parserConfig) {
+  var inline = parserConfig.inline
+  if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
+
+  var indentUnit = config.indentUnit,
+      tokenHooks = parserConfig.tokenHooks,
+      documentTypes = parserConfig.documentTypes || {},
+      mediaTypes = parserConfig.mediaTypes || {},
+      mediaFeatures = parserConfig.mediaFeatures || {},
+      mediaValueKeywords = parserConfig.mediaValueKeywords || {},
+      propertyKeywords = parserConfig.propertyKeywords || {},
+      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
+      fontProperties = parserConfig.fontProperties || {},
+      counterDescriptors = parserConfig.counterDescriptors || {},
+      colorKeywords = parserConfig.colorKeywords || {},
+      valueKeywords = parserConfig.valueKeywords || {},
+      allowNested = parserConfig.allowNested,
+      lineComment = parserConfig.lineComment,
+      supportsAtComponent = parserConfig.supportsAtComponent === true;
+
+  var type, override;
+  function ret(style, tp) { type = tp; return style; }
+
+  // Tokenizers
+
+  function tokenBase(stream, state) {
+    var ch = stream.next();
+    if (tokenHooks[ch]) {
+      var result = tokenHooks[ch](stream, state);
+      if (result !== false) return result;
+    }
+    if (ch == "@") {
+      stream.eatWhile(/[\w\\\-]/);
+      return ret("def", stream.current());
+    } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
+      return ret(null, "compare");
+    } else if (ch == "\"" || ch == "'") {
+      state.tokenize = tokenString(ch);
+      return state.tokenize(stream, state);
+    } else if (ch == "#") {
+      stream.eatWhile(/[\w\\\-]/);
+      return ret("atom", "hash");
+    } else if (ch == "!") {
+      stream.match(/^\s*\w*/);
+      return ret("keyword", "important");
+    } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
+      stream.eatWhile(/[\w.%]/);
+      return ret("number", "unit");
+    } else if (ch === "-") {
+      if (/[\d.]/.test(stream.peek())) {
+        stream.eatWhile(/[\w.%]/);
+        return ret("number", "unit");
+      } else if (stream.match(/^-[\w\\\-]+/)) {
+        stream.eatWhile(/[\w\\\-]/);
+        if (stream.match(/^\s*:/, false))
+          return ret("variable-2", "variable-definition");
+        return ret("variable-2", "variable");
+      } else if (stream.match(/^\w+-/)) {
+        return ret("meta", "meta");
+      }
+    } else if (/[,+>*\/]/.test(ch)) {
+      return ret(null, "select-op");
+    } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
+      return ret("qualifier", "qualifier");
+    } else if (/[:;{}\[\]\(\)]/.test(ch)) {
+      return ret(null, ch);
+    } else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) ||
+               (ch == "d" && stream.match("omain(")) ||
+               (ch == "r" && stream.match("egexp("))) {
+      stream.backUp(1);
+      state.tokenize = tokenParenthesized;
+      return ret("property", "word");
+    } else if (/[\w\\\-]/.test(ch)) {
+      stream.eatWhile(/[\w\\\-]/);
+      return ret("property", "word");
+    } else {
+      return ret(null, null);
+    }
+  }
+
+  function tokenString(quote) {
+    return function(stream, state) {
+      var escaped = false, ch;
+      while ((ch = stream.next()) != null) {
+        if (ch == quote && !escaped) {
+          if (quote == ")") stream.backUp(1);
+          break;
+        }
+        escaped = !escaped && ch == "\\";
+      }
+      if (ch == quote || !escaped && quote != ")") state.tokenize = null;
+      return ret("string", "string");
+    };
+  }
+
+  function tokenParenthesized(stream, state) {
+    stream.next(); // Must be '('
+    if (!stream.match(/\s*[\"\')]/, false))
+      state.tokenize = tokenString(")");
+    else
+      state.tokenize = null;
+    return ret(null, "(");
+  }
+
+  // Context management
+
+  function Context(type, indent, prev) {
+    this.type = type;
+    this.indent = indent;
+    this.prev = prev;
+  }
+
+  function pushContext(state, stream, type, indent) {
+    state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
+    return type;
+  }
+
+  function popContext(state) {
+    if (state.context.prev)
+      state.context = state.context.prev;
+    return state.context.type;
+  }
+
+  function pass(type, stream, state) {
+    return states[state.context.type](type, stream, state);
+  }
+  function popAndPass(type, stream, state, n) {
+    for (var i = n || 1; i > 0; i--)
+      state.context = state.context.prev;
+    return pass(type, stream, state);
+  }
+
+  // Parser
+
+  function wordAsValue(stream) {
+    var word = stream.current().toLowerCase();
+    if (valueKeywords.hasOwnProperty(word))
+      override = "atom";
+    else if (colorKeywords.hasOwnProperty(word))
+      override = "keyword";
+    else
+      override = "variable";
+  }
+
+  var states = {};
+
+  states.top = function(type, stream, state) {
+    if (type == "{") {
+      return pushContext(state, stream, "block");
+    } else if (type == "}" && state.context.prev) {
+      return popContext(state);
+    } else if (supportsAtComponent && /@component/.test(type)) {
+      return pushContext(state, stream, "atComponentBlock");
+    } else if (/^@(-moz-)?document$/.test(type)) {
+      return pushContext(state, stream, "documentTypes");
+    } else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
+      return pushContext(state, stream, "atBlock");
+    } else if (/^@(font-face|counter-style)/.test(type)) {
+      state.stateArg = type;
+      return "restricted_atBlock_before";
+    } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
+      return "keyframes";
+    } else if (type && type.charAt(0) == "@") {
+      return pushContext(state, stream, "at");
+    } else if (type == "hash") {
+      override = "builtin";
+    } else if (type == "word") {
+      override = "tag";
+    } else if (type == "variable-definition") {
+      return "maybeprop";
+    } else if (type == "interpolation") {
+      return pushContext(state, stream, "interpolation");
+    } else if (type == ":") {
+      return "pseudo";
+    } else if (allowNested && type == "(") {
+      return pushContext(state, stream, "parens");
+    }
+    return state.context.type;
+  };
+
+  states.block = function(type, stream, state) {
+    if (type == "word") {
+      var word = stream.current().toLowerCase();
+      if (propertyKeywords.hasOwnProperty(word)) {
+        override = "property";
+        return "maybeprop";
+      } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
+        override = "string-2";
+        return "maybeprop";
+      } else if (allowNested) {
+        override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
+        return "block";
+      } else {
+        override += " error";
+        return "maybeprop";
+      }
+    } else if (type == "meta") {
+      return "block";
+    } else if (!allowNested && (type == "hash" || type == "qualifier")) {
+      override = "error";
+      return "block";
+    } else {
+      return states.top(type, stream, state);
+    }
+  };
+
+  states.maybeprop = function(type, stream, state) {
+    if (type == ":") return pushContext(state, stream, "prop");
+    return pass(type, stream, state);
+  };
+
+  states.prop = function(type, stream, state) {
+    if (type == ";") return popContext(state);
+    if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
+    if (type == "}" || type == "{") return popAndPass(type, stream, state);
+    if (type == "(") return pushContext(state, stream, "parens");
+
+    if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
+      override += " error";
+    } else if (type == "word") {
+      wordAsValue(stream);
+    } else if (type == "interpolation") {
+      return pushContext(state, stream, "interpolation");
+    }
+    return "prop";
+  };
+
+  states.propBlock = function(type, _stream, state) {
+    if (type == "}") return popContext(state);
+    if (type == "word") { override = "property"; return "maybeprop"; }
+    return state.context.type;
+  };
+
+  states.parens = function(type, stream, state) {
+    if (type == "{" || type == "}") return popAndPass(type, stream, state);
+    if (type == ")") return popContext(state);
+    if (type == "(") return pushContext(state, stream, "parens");
+    if (type == "interpolation") return pushContext(state, stream, "interpolation");
+    if (type == "word") wordAsValue(stream);
+    return "parens";
+  };
+
+  states.pseudo = function(type, stream, state) {
+    if (type == "meta") return "pseudo";
+
+    if (type == "word") {
+      override = "variable-3";
+      return state.context.type;
+    }
+    return pass(type, stream, state);
+  };
+
+  states.documentTypes = function(type, stream, state) {
+    if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
+      override = "tag";
+      return state.context.type;
+    } else {
+      return states.atBlock(type, stream, state);
+    }
+  };
+
+  states.atBlock = function(type, stream, state) {
+    if (type == "(") return pushContext(state, stream, "atBlock_parens");
+    if (type == "}" || type == ";") return popAndPass(type, stream, state);
+    if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
+
+    if (type == "interpolation") return pushContext(state, stream, "interpolation");
+
+    if (type == "word") {
+      var word = stream.current().toLowerCase();
+      if (word == "only" || word == "not" || word == "and" || word == "or")
+        override = "keyword";
+      else if (mediaTypes.hasOwnProperty(word))
+        override = "attribute";
+      else if (mediaFeatures.hasOwnProperty(word))
+        override = "property";
+      else if (mediaValueKeywords.hasOwnProperty(word))
+        override = "keyword";
+      else if (propertyKeywords.hasOwnProperty(word))
+        override = "property";
+      else if (nonStandardPropertyKeywords.hasOwnProperty(word))
+        override = "string-2";
+      else if (valueKeywords.hasOwnProperty(word))
+        override = "atom";
+      else if (colorKeywords.hasOwnProperty(word))
+        override = "keyword";
+      else
+        override = "error";
+    }
+    return state.context.type;
+  };
+
+  states.atComponentBlock = function(type, stream, state) {
+    if (type == "}")
+      return popAndPass(type, stream, state);
+    if (type == "{")
+      return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
+    if (type == "word")
+      override = "error";
+    return state.context.type;
+  };
+
+  states.atBlock_parens = function(type, stream, state) {
+    if (type == ")") return popContext(state);
+    if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
+    return states.atBlock(type, stream, state);
+  };
+
+  states.restricted_atBlock_before = function(type, stream, state) {
+    if (type == "{")
+      return pushContext(state, stream, "restricted_atBlock");
+    if (type == "word" && state.stateArg == "@counter-style") {
+      override = "variable";
+      return "restricted_atBlock_before";
+    }
+    return pass(type, stream, state);
+  };
+
+  states.restricted_atBlock = function(type, stream, state) {
+    if (type == "}") {
+      state.stateArg = null;
+      return popContext(state);
+    }
+    if (type == "word") {
+      if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
+          (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
+        override = "error";
+      else
+        override = "property";
+      return "maybeprop";
+    }
+    return "restricted_atBlock";
+  };
+
+  states.keyframes = function(type, stream, state) {
+    if (type == "word") { override = "variable"; return "keyframes"; }
+    if (type == "{") return pushContext(state, stream, "top");
+    return pass(type, stream, state);
+  };
+
+  states.at = function(type, stream, state) {
+    if (type == ";") return popContext(state);
+    if (type == "{" || type == "}") return popAndPass(type, stream, state);
+    if (type == "word") override = "tag";
+    else if (type == "hash") override = "builtin";
+    return "at";
+  };
+
+  states.interpolation = function(type, stream, state) {
+    if (type == "}") return popContext(state);
+    if (type == "{" || type == ";") return popAndPass(type, stream, state);
+    if (type == "word") override = "variable";
+    else if (type != "variable" && type != "(" && type != ")") override = "error";
+    return "interpolation";
+  };
+
+  return {
+    startState: function(base) {
+      return {tokenize: null,
+              state: inline ? "block" : "top",
+              stateArg: null,
+              context: new Context(inline ? "block" : "top", base || 0, null)};
+    },
+
+    token: function(stream, state) {
+      if (!state.tokenize && stream.eatSpace()) return null;
+      var style = (state.tokenize || tokenBase)(stream, state);
+      if (style && typeof style == "object") {
+        type = style[1];
+        style = style[0];
+      }
+      override = style;
+      state.state = states[state.state](type, stream, state);
+      return override;
+    },
+
+    indent: function(state, textAfter) {
+      var cx = state.context, ch = textAfter && textAfter.charAt(0);
+      var indent = cx.indent;
+      if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
+      if (cx.prev) {
+        if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
+                          cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
+          // Resume indentation from parent context.
+          cx = cx.prev;
+          indent = cx.indent;
+        } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
+            ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
+          // Dedent relative to current context.
+          indent = Math.max(0, cx.indent - indentUnit);
+        }
+      }
+      return indent;
+    },
+
+    electricChars: "}",
+    blockCommentStart: "/*",
+    blockCommentEnd: "*/",
+    lineComment: lineComment,
+    fold: "brace"
+  };
+});
+
+  function keySet(array) {
+    var keys = {};
+    for (var i = 0; i < array.length; ++i) {
+      keys[array[i].toLowerCase()] = true;
+    }
+    return keys;
+  }
+
+  var documentTypes_ = [
+    "domain", "regexp", "url", "url-prefix"
+  ], documentTypes = keySet(documentTypes_);
+
+  var mediaTypes_ = [
+    "all", "aural", "braille", "handheld", "print", "projection", "screen",
+    "tty", "tv", "embossed"
+  ], mediaTypes = keySet(mediaTypes_);
+
+  var mediaFeatures_ = [
+    "width", "min-width", "max-width", "height", "min-height", "max-height",
+    "device-width", "min-device-width", "max-device-width", "device-height",
+    "min-device-height", "max-device-height", "aspect-ratio",
+    "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
+    "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
+    "max-color", "color-index", "min-color-index", "max-color-index",
+    "monochrome", "min-monochrome", "max-monochrome", "resolution",
+    "min-resolution", "max-resolution", "scan", "grid", "orientation",
+    "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
+    "pointer", "any-pointer", "hover", "any-hover"
+  ], mediaFeatures = keySet(mediaFeatures_);
+
+  var mediaValueKeywords_ = [
+    "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
+    "interlace", "progressive"
+  ], mediaValueKeywords = keySet(mediaValueKeywords_);
+
+  var propertyKeywords_ = [
+    "align-content", "align-items", "align-self", "alignment-adjust",
+    "alignment-baseline", "anchor-point", "animation", "animation-delay",
+    "animation-direction", "animation-duration", "animation-fill-mode",
+    "animation-iteration-count", "animation-name", "animation-play-state",
+    "animation-timing-function", "appearance", "azimuth", "backface-visibility",
+    "background", "background-attachment", "background-blend-mode", "background-clip",
+    "background-color", "background-image", "background-origin", "background-position",
+    "background-repeat", "background-size", "baseline-shift", "binding",
+    "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
+    "bookmark-target", "border", "border-bottom", "border-bottom-color",
+    "border-bottom-left-radius", "border-bottom-right-radius",
+    "border-bottom-style", "border-bottom-width", "border-collapse",
+    "border-color", "border-image", "border-image-outset",
+    "border-image-repeat", "border-image-slice", "border-image-source",
+    "border-image-width", "border-left", "border-left-color",
+    "border-left-style", "border-left-width", "border-radius", "border-right",
+    "border-right-color", "border-right-style", "border-right-width",
+    "border-spacing", "border-style", "border-top", "border-top-color",
+    "border-top-left-radius", "border-top-right-radius", "border-top-style",
+    "border-top-width", "border-width", "bottom", "box-decoration-break",
+    "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
+    "caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count",
+    "column-fill", "column-gap", "column-rule", "column-rule-color",
+    "column-rule-style", "column-rule-width", "column-span", "column-width",
+    "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
+    "cue-after", "cue-before", "cursor", "direction", "display",
+    "dominant-baseline", "drop-initial-after-adjust",
+    "drop-initial-after-align", "drop-initial-before-adjust",
+    "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
+    "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
+    "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
+    "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
+    "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
+    "font-stretch", "font-style", "font-synthesis", "font-variant",
+    "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
+    "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
+    "font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
+    "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap",
+    "grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap",
+    "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns",
+    "grid-template-rows", "hanging-punctuation", "height", "hyphens",
+    "icon", "image-orientation", "image-rendering", "image-resolution",
+    "inline-box-align", "justify-content", "justify-items", "justify-self", "left", "letter-spacing",
+    "line-break", "line-height", "line-stacking", "line-stacking-ruby",
+    "line-stacking-shift", "line-stacking-strategy", "list-style",
+    "list-style-image", "list-style-position", "list-style-type", "margin",
+    "margin-bottom", "margin-left", "margin-right", "margin-top",
+    "marks", "marquee-direction", "marquee-loop",
+    "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
+    "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
+    "nav-left", "nav-right", "nav-up", "object-fit", "object-position",
+    "opacity", "order", "orphans", "outline",
+    "outline-color", "outline-offset", "outline-style", "outline-width",
+    "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
+    "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
+    "page", "page-break-after", "page-break-before", "page-break-inside",
+    "page-policy", "pause", "pause-after", "pause-before", "perspective",
+    "perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position",
+    "presentation-level", "punctuation-trim", "quotes", "region-break-after",
+    "region-break-before", "region-break-inside", "region-fragment",
+    "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
+    "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
+    "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
+    "shape-outside", "size", "speak", "speak-as", "speak-header",
+    "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
+    "tab-size", "table-layout", "target", "target-name", "target-new",
+    "target-position", "text-align", "text-align-last", "text-decoration",
+    "text-decoration-color", "text-decoration-line", "text-decoration-skip",
+    "text-decoration-style", "text-emphasis", "text-emphasis-color",
+    "text-emphasis-position", "text-emphasis-style", "text-height",
+    "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
+    "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
+    "text-wrap", "top", "transform", "transform-origin", "transform-style",
+    "transition", "transition-delay", "transition-duration",
+    "transition-property", "transition-timing-function", "unicode-bidi",
+    "user-select", "vertical-align", "visibility", "voice-balance", "voice-duration",
+    "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
+    "voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break",
+    "word-spacing", "word-wrap", "z-index",
+    // SVG-specific
+    "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
+    "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
+    "color-interpolation", "color-interpolation-filters",
+    "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
+    "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
+    "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
+    "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
+    "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
+    "glyph-orientation-vertical", "text-anchor", "writing-mode"
+  ], propertyKeywords = keySet(propertyKeywords_);
+
+  var nonStandardPropertyKeywords_ = [
+    "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
+    "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
+    "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
+    "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
+    "searchfield-results-decoration", "zoom"
+  ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
+
+  var fontProperties_ = [
+    "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
+    "font-stretch", "font-weight", "font-style"
+  ], fontProperties = keySet(fontProperties_);
+
+  var counterDescriptors_ = [
+    "additive-symbols", "fallback", "negative", "pad", "prefix", "range",
+    "speak-as", "suffix", "symbols", "system"
+  ], counterDescriptors = keySet(counterDescriptors_);
+
+  var colorKeywords_ = [
+    "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
+    "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
+    "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
+    "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
+    "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
+    "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
+    "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
+    "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
+    "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
+    "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
+    "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
+    "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
+    "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
+    "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
+    "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
+    "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
+    "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
+    "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
+    "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
+    "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
+    "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
+    "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
+    "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
+    "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
+    "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
+    "whitesmoke", "yellow", "yellowgreen"
+  ], colorKeywords = keySet(colorKeywords_);
+
+  var valueKeywords_ = [
+    "above", "absolute", "activeborder", "additive", "activecaption", "afar",
+    "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
+    "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
+    "arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
+    "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
+    "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
+    "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
+    "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
+    "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
+    "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
+    "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
+    "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
+    "compact", "condensed", "contain", "content", "contents",
+    "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
+    "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
+    "decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
+    "destination-in", "destination-out", "destination-over", "devanagari", "difference",
+    "disc", "discard", "disclosure-closed", "disclosure-open", "document",
+    "dot-dash", "dot-dot-dash",
+    "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
+    "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
+    "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
+    "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
+    "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
+    "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
+    "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
+    "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
+    "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
+    "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
+    "forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
+    "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
+    "help", "hidden", "hide", "higher", "highlight", "highlighttext",
+    "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
+    "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
+    "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
+    "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
+    "italic", "japanese-formal", "japanese-informal", "justify", "kannada",
+    "katakana", "katakana-iroha", "keep-all", "khmer",
+    "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
+    "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
+    "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
+    "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
+    "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
+    "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
+    "media-controls-background", "media-current-time-display",
+    "media-fullscreen-button", "media-mute-button", "media-play-button",
+    "media-return-to-realtime-button", "media-rewind-button",
+    "media-seek-back-button", "media-seek-forward-button", "media-slider",
+    "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
+    "media-volume-slider-container", "media-volume-sliderthumb", "medium",
+    "menu", "menulist", "menulist-button", "menulist-text",
+    "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
+    "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
+    "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
+    "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
+    "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
+    "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
+    "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
+    "painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
+    "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
+    "progress", "push-button", "radial-gradient", "radio", "read-only",
+    "read-write", "read-write-plaintext-only", "rectangle", "region",
+    "relative", "repeat", "repeating-linear-gradient",
+    "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
+    "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
+    "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
+    "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
+    "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
+    "searchfield-cancel-button", "searchfield-decoration",
+    "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
+    "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
+    "simp-chinese-formal", "simp-chinese-informal", "single",
+    "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
+    "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
+    "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
+    "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
+    "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
+    "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
+    "table-caption", "table-cell", "table-column", "table-column-group",
+    "table-footer-group", "table-header-group", "table-row", "table-row-group",
+    "tamil",
+    "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
+    "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
+    "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
+    "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
+    "trad-chinese-formal", "trad-chinese-informal", "transform",
+    "translate", "translate3d", "translateX", "translateY", "translateZ",
+    "transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
+    "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
+    "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
+    "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
+    "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
+    "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
+    "xx-large", "xx-small"
+  ], valueKeywords = keySet(valueKeywords_);
+
+  var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
+    .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
+    .concat(valueKeywords_);
+  CodeMirror.registerHelper("hintWords", "css", allWords);
+
+  function tokenCComment(stream, state) {
+    var maybeEnd = false, ch;
+    while ((ch = stream.next()) != null) {
+      if (maybeEnd && ch == "/") {
+        state.tokenize = null;
+        break;
+      }
+      maybeEnd = (ch == "*");
+    }
+    return ["comment", "comment"];
+  }
+
+  CodeMirror.defineMIME("text/css", {
+    documentTypes: documentTypes,
+    mediaTypes: mediaTypes,
+    mediaFeatures: mediaFeatures,
+    mediaValueKeywords: mediaValueKeywords,
+    propertyKeywords: propertyKeywords,
+    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+    fontProperties: fontProperties,
+    counterDescriptors: counterDescriptors,
+    colorKeywords: colorKeywords,
+    valueKeywords: valueKeywords,
+    tokenHooks: {
+      "/": function(stream, state) {
+        if (!stream.eat("*")) return false;
+        state.tokenize = tokenCComment;
+        return tokenCComment(stream, state);
+      }
+    },
+    name: "css"
+  });
+
+  CodeMirror.defineMIME("text/x-scss", {
+    mediaTypes: mediaTypes,
+    mediaFeatures: mediaFeatures,
+    mediaValueKeywords: mediaValueKeywords,
+    propertyKeywords: propertyKeywords,
+    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+    colorKeywords: colorKeywords,
+    valueKeywords: valueKeywords,
+    fontProperties: fontProperties,
+    allowNested: true,
+    lineComment: "//",
+    tokenHooks: {
+      "/": function(stream, state) {
+        if (stream.eat("/")) {
+          stream.skipToEnd();
+          return ["comment", "comment"];
+        } else if (stream.eat("*")) {
+          state.tokenize = tokenCComment;
+          return tokenCComment(stream, state);
+        } else {
+          return ["operator", "operator"];
+        }
+      },
+      ":": function(stream) {
+        if (stream.match(/\s*\{/, false))
+          return [null, null]
+        return false;
+      },
+      "$": function(stream) {
+        stream.match(/^[\w-]+/);
+        if (stream.match(/^\s*:/, false))
+          return ["variable-2", "variable-definition"];
+        return ["variable-2", "variable"];
+      },
+      "#": function(stream) {
+        if (!stream.eat("{")) return false;
+        return [null, "interpolation"];
+      }
+    },
+    name: "css",
+    helperType: "scss"
+  });
+
+  CodeMirror.defineMIME("text/x-less", {
+    mediaTypes: mediaTypes,
+    mediaFeatures: mediaFeatures,
+    mediaValueKeywords: mediaValueKeywords,
+    propertyKeywords: propertyKeywords,
+    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+    colorKeywords: colorKeywords,
+    valueKeywords: valueKeywords,
+    fontProperties: fontProperties,
+    allowNested: true,
+    lineComment: "//",
+    tokenHooks: {
+      "/": function(stream, state) {
+        if (stream.eat("/")) {
+          stream.skipToEnd();
+          return ["comment", "comment"];
+        } else if (stream.eat("*")) {
+          state.tokenize = tokenCComment;
+          return tokenCComment(stream, state);
+        } else {
+          return ["operator", "operator"];
+        }
+      },
+      "@": function(stream) {
+        if (stream.eat("{")) return [null, "interpolation"];
+        if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
+        stream.eatWhile(/[\w\\\-]/);
+        if (stream.match(/^\s*:/, false))
+          return ["variable-2", "variable-definition"];
+        return ["variable-2", "variable"];
+      },
+      "&": function() {
+        return ["atom", "atom"];
+      }
+    },
+    name: "css",
+    helperType: "less"
+  });
+
+  CodeMirror.defineMIME("text/x-gss", {
+    documentTypes: documentTypes,
+    mediaTypes: mediaTypes,
+    mediaFeatures: mediaFeatures,
+    propertyKeywords: propertyKeywords,
+    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+    fontProperties: fontProperties,
+    counterDescriptors: counterDescriptors,
+    colorKeywords: colorKeywords,
+    valueKeywords: valueKeywords,
+    supportsAtComponent: true,
+    tokenHooks: {
+      "/": function(stream, state) {
+        if (!stream.eat("*")) return false;
+        state.tokenize = tokenCComment;
+        return tokenCComment(stream, state);
+      }
+    },
+    name: "css",
+    helperType: "gss"
+  });
+
+});

+ 152 - 0
demos/assets/codemirror-5.27.4/mode/htmlmixed/htmlmixed.js

@@ -0,0 +1,152 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var defaultTags = {
+    script: [
+      ["lang", /(javascript|babel)/i, "javascript"],
+      ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
+      ["type", /./, "text/plain"],
+      [null, null, "javascript"]
+    ],
+    style:  [
+      ["lang", /^css$/i, "css"],
+      ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
+      ["type", /./, "text/plain"],
+      [null, null, "css"]
+    ]
+  };
+
+  function maybeBackup(stream, pat, style) {
+    var cur = stream.current(), close = cur.search(pat);
+    if (close > -1) {
+      stream.backUp(cur.length - close);
+    } else if (cur.match(/<\/?$/)) {
+      stream.backUp(cur.length);
+      if (!stream.match(pat, false)) stream.match(cur);
+    }
+    return style;
+  }
+
+  var attrRegexpCache = {};
+  function getAttrRegexp(attr) {
+    var regexp = attrRegexpCache[attr];
+    if (regexp) return regexp;
+    return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
+  }
+
+  function getAttrValue(text, attr) {
+    var match = text.match(getAttrRegexp(attr))
+    return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
+  }
+
+  function getTagRegexp(tagName, anchored) {
+    return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
+  }
+
+  function addTags(from, to) {
+    for (var tag in from) {
+      var dest = to[tag] || (to[tag] = []);
+      var source = from[tag];
+      for (var i = source.length - 1; i >= 0; i--)
+        dest.unshift(source[i])
+    }
+  }
+
+  function findMatchingMode(tagInfo, tagText) {
+    for (var i = 0; i < tagInfo.length; i++) {
+      var spec = tagInfo[i];
+      if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
+    }
+  }
+
+  CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
+    var htmlMode = CodeMirror.getMode(config, {
+      name: "xml",
+      htmlMode: true,
+      multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
+      multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
+    });
+
+    var tags = {};
+    var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
+    addTags(defaultTags, tags);
+    if (configTags) addTags(configTags, tags);
+    if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
+      tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
+
+    function html(stream, state) {
+      var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
+      if (tag && !/[<>\s\/]/.test(stream.current()) &&
+          (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
+          tags.hasOwnProperty(tagName)) {
+        state.inTag = tagName + " "
+      } else if (state.inTag && tag && />$/.test(stream.current())) {
+        var inTag = /^([\S]+) (.*)/.exec(state.inTag)
+        state.inTag = null
+        var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
+        var mode = CodeMirror.getMode(config, modeSpec)
+        var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
+        state.token = function (stream, state) {
+          if (stream.match(endTagA, false)) {
+            state.token = html;
+            state.localState = state.localMode = null;
+            return null;
+          }
+          return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
+        };
+        state.localMode = mode;
+        state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, ""));
+      } else if (state.inTag) {
+        state.inTag += stream.current()
+        if (stream.eol()) state.inTag += " "
+      }
+      return style;
+    };
+
+    return {
+      startState: function () {
+        var state = CodeMirror.startState(htmlMode);
+        return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
+      },
+
+      copyState: function (state) {
+        var local;
+        if (state.localState) {
+          local = CodeMirror.copyState(state.localMode, state.localState);
+        }
+        return {token: state.token, inTag: state.inTag,
+                localMode: state.localMode, localState: local,
+                htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
+      },
+
+      token: function (stream, state) {
+        return state.token(stream, state);
+      },
+
+      indent: function (state, textAfter, line) {
+        if (!state.localMode || /^\s*<\//.test(textAfter))
+          return htmlMode.indent(state.htmlState, textAfter);
+        else if (state.localMode.indent)
+          return state.localMode.indent(state.localState, textAfter, line);
+        else
+          return CodeMirror.Pass;
+      },
+
+      innerMode: function (state) {
+        return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
+      }
+    };
+  }, "xml", "javascript", "css");
+
+  CodeMirror.defineMIME("text/html", "htmlmixed");
+});

+ 818 - 0
demos/assets/codemirror-5.27.4/mode/javascript/javascript.js

@@ -0,0 +1,818 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+function expressionAllowed(stream, state, backUp) {
+  return /^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
+    (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
+}
+
+CodeMirror.defineMode("javascript", function(config, parserConfig) {
+  var indentUnit = config.indentUnit;
+  var statementIndent = parserConfig.statementIndent;
+  var jsonldMode = parserConfig.jsonld;
+  var jsonMode = parserConfig.json || jsonldMode;
+  var isTS = parserConfig.typescript;
+  var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
+
+  // Tokenizer
+
+  var keywords = function(){
+    function kw(type) {return {type: type, style: "keyword"};}
+    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+    var operator = kw("operator"), atom = {type: "atom", style: "atom"};
+
+    var jsKeywords = {
+      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
+      "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
+      "var": kw("var"), "const": kw("var"), "let": kw("var"),
+      "function": kw("function"), "catch": kw("catch"),
+      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
+      "in": operator, "typeof": operator, "instanceof": operator,
+      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
+      "this": kw("this"), "class": kw("class"), "super": kw("atom"),
+      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
+      "await": C
+    };
+
+    // Extend the 'normal' keywords with the TypeScript language extensions
+    if (isTS) {
+      var type = {type: "variable", style: "type"};
+      var tsKeywords = {
+        // object-like things
+        "interface": kw("class"),
+        "implements": C,
+        "namespace": C,
+        "module": kw("module"),
+        "enum": kw("module"),
+
+        // scope modifiers
+        "public": kw("modifier"),
+        "private": kw("modifier"),
+        "protected": kw("modifier"),
+        "abstract": kw("modifier"),
+
+        // types
+        "string": type, "number": type, "boolean": type, "any": type
+      };
+
+      for (var attr in tsKeywords) {
+        jsKeywords[attr] = tsKeywords[attr];
+      }
+    }
+
+    return jsKeywords;
+  }();
+
+  var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
+  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
+
+  function readRegexp(stream) {
+    var escaped = false, next, inSet = false;
+    while ((next = stream.next()) != null) {
+      if (!escaped) {
+        if (next == "/" && !inSet) return;
+        if (next == "[") inSet = true;
+        else if (inSet && next == "]") inSet = false;
+      }
+      escaped = !escaped && next == "\\";
+    }
+  }
+
+  // Used as scratch variables to communicate multiple values without
+  // consing up tons of objects.
+  var type, content;
+  function ret(tp, style, cont) {
+    type = tp; content = cont;
+    return style;
+  }
+  function tokenBase(stream, state) {
+    var ch = stream.next();
+    if (ch == '"' || ch == "'") {
+      state.tokenize = tokenString(ch);
+      return state.tokenize(stream, state);
+    } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
+      return ret("number", "number");
+    } else if (ch == "." && stream.match("..")) {
+      return ret("spread", "meta");
+    } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+      return ret(ch);
+    } else if (ch == "=" && stream.eat(">")) {
+      return ret("=>", "operator");
+    } else if (ch == "0" && stream.eat(/x/i)) {
+      stream.eatWhile(/[\da-f]/i);
+      return ret("number", "number");
+    } else if (ch == "0" && stream.eat(/o/i)) {
+      stream.eatWhile(/[0-7]/i);
+      return ret("number", "number");
+    } else if (ch == "0" && stream.eat(/b/i)) {
+      stream.eatWhile(/[01]/i);
+      return ret("number", "number");
+    } else if (/\d/.test(ch)) {
+      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
+      return ret("number", "number");
+    } else if (ch == "/") {
+      if (stream.eat("*")) {
+        state.tokenize = tokenComment;
+        return tokenComment(stream, state);
+      } else if (stream.eat("/")) {
+        stream.skipToEnd();
+        return ret("comment", "comment");
+      } else if (expressionAllowed(stream, state, 1)) {
+        readRegexp(stream);
+        stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
+        return ret("regexp", "string-2");
+      } else {
+        stream.eatWhile(isOperatorChar);
+        return ret("operator", "operator", stream.current());
+      }
+    } else if (ch == "`") {
+      state.tokenize = tokenQuasi;
+      return tokenQuasi(stream, state);
+    } else if (ch == "#") {
+      stream.skipToEnd();
+      return ret("error", "error");
+    } else if (isOperatorChar.test(ch)) {
+      if (ch != ">" || !state.lexical || state.lexical.type != ">")
+        stream.eatWhile(isOperatorChar);
+      return ret("operator", "operator", stream.current());
+    } else if (wordRE.test(ch)) {
+      stream.eatWhile(wordRE);
+      var word = stream.current()
+      if (state.lastType != ".") {
+        if (keywords.propertyIsEnumerable(word)) {
+          var kw = keywords[word]
+          return ret(kw.type, kw.style, word)
+        }
+        if (word == "async" && stream.match(/^\s*[\(\w]/, false))
+          return ret("async", "keyword", word)
+      }
+      return ret("variable", "variable", word)
+    }
+  }
+
+  function tokenString(quote) {
+    return function(stream, state) {
+      var escaped = false, next;
+      if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
+        state.tokenize = tokenBase;
+        return ret("jsonld-keyword", "meta");
+      }
+      while ((next = stream.next()) != null) {
+        if (next == quote && !escaped) break;
+        escaped = !escaped && next == "\\";
+      }
+      if (!escaped) state.tokenize = tokenBase;
+      return ret("string", "string");
+    };
+  }
+
+  function tokenComment(stream, state) {
+    var maybeEnd = false, ch;
+    while (ch = stream.next()) {
+      if (ch == "/" && maybeEnd) {
+        state.tokenize = tokenBase;
+        break;
+      }
+      maybeEnd = (ch == "*");
+    }
+    return ret("comment", "comment");
+  }
+
+  function tokenQuasi(stream, state) {
+    var escaped = false, next;
+    while ((next = stream.next()) != null) {
+      if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
+        state.tokenize = tokenBase;
+        break;
+      }
+      escaped = !escaped && next == "\\";
+    }
+    return ret("quasi", "string-2", stream.current());
+  }
+
+  var brackets = "([{}])";
+  // This is a crude lookahead trick to try and notice that we're
+  // parsing the argument patterns for a fat-arrow function before we
+  // actually hit the arrow token. It only works if the arrow is on
+  // the same line as the arguments and there's no strange noise
+  // (comments) in between. Fallback is to only notice when we hit the
+  // arrow, and not declare the arguments as locals for the arrow
+  // body.
+  function findFatArrow(stream, state) {
+    if (state.fatArrowAt) state.fatArrowAt = null;
+    var arrow = stream.string.indexOf("=>", stream.start);
+    if (arrow < 0) return;
+
+    if (isTS) { // Try to skip TypeScript return type declarations after the arguments
+      var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
+      if (m) arrow = m.index
+    }
+
+    var depth = 0, sawSomething = false;
+    for (var pos = arrow - 1; pos >= 0; --pos) {
+      var ch = stream.string.charAt(pos);
+      var bracket = brackets.indexOf(ch);
+      if (bracket >= 0 && bracket < 3) {
+        if (!depth) { ++pos; break; }
+        if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
+      } else if (bracket >= 3 && bracket < 6) {
+        ++depth;
+      } else if (wordRE.test(ch)) {
+        sawSomething = true;
+      } else if (/["'\/]/.test(ch)) {
+        return;
+      } else if (sawSomething && !depth) {
+        ++pos;
+        break;
+      }
+    }
+    if (sawSomething && !depth) state.fatArrowAt = pos;
+  }
+
+  // Parser
+
+  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
+
+  function JSLexical(indented, column, type, align, prev, info) {
+    this.indented = indented;
+    this.column = column;
+    this.type = type;
+    this.prev = prev;
+    this.info = info;
+    if (align != null) this.align = align;
+  }
+
+  function inScope(state, varname) {
+    for (var v = state.localVars; v; v = v.next)
+      if (v.name == varname) return true;
+    for (var cx = state.context; cx; cx = cx.prev) {
+      for (var v = cx.vars; v; v = v.next)
+        if (v.name == varname) return true;
+    }
+  }
+
+  function parseJS(state, style, type, content, stream) {
+    var cc = state.cc;
+    // Communicate our context to the combinators.
+    // (Less wasteful than consing up a hundred closures on every call.)
+    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
+
+    if (!state.lexical.hasOwnProperty("align"))
+      state.lexical.align = true;
+
+    while(true) {
+      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
+      if (combinator(type, content)) {
+        while(cc.length && cc[cc.length - 1].lex)
+          cc.pop()();
+        if (cx.marked) return cx.marked;
+        if (type == "variable" && inScope(state, content)) return "variable-2";
+        return style;
+      }
+    }
+  }
+
+  // Combinator utils
+
+  var cx = {state: null, column: null, marked: null, cc: null};
+  function pass() {
+    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
+  }
+  function cont() {
+    pass.apply(null, arguments);
+    return true;
+  }
+  function register(varname) {
+    function inList(list) {
+      for (var v = list; v; v = v.next)
+        if (v.name == varname) return true;
+      return false;
+    }
+    var state = cx.state;
+    cx.marked = "def";
+    if (state.context) {
+      if (inList(state.localVars)) return;
+      state.localVars = {name: varname, next: state.localVars};
+    } else {
+      if (inList(state.globalVars)) return;
+      if (parserConfig.globalVars)
+        state.globalVars = {name: varname, next: state.globalVars};
+    }
+  }
+
+  // Combinators
+
+  var defaultVars = {name: "this", next: {name: "arguments"}};
+  function pushcontext() {
+    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
+    cx.state.localVars = defaultVars;
+  }
+  function popcontext() {
+    cx.state.localVars = cx.state.context.vars;
+    cx.state.context = cx.state.context.prev;
+  }
+  function pushlex(type, info) {
+    var result = function() {
+      var state = cx.state, indent = state.indented;
+      if (state.lexical.type == "stat") indent = state.lexical.indented;
+      else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
+        indent = outer.indented;
+      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
+    };
+    result.lex = true;
+    return result;
+  }
+  function poplex() {
+    var state = cx.state;
+    if (state.lexical.prev) {
+      if (state.lexical.type == ")")
+        state.indented = state.lexical.indented;
+      state.lexical = state.lexical.prev;
+    }
+  }
+  poplex.lex = true;
+
+  function expect(wanted) {
+    function exp(type) {
+      if (type == wanted) return cont();
+      else if (wanted == ";") return pass();
+      else return cont(exp);
+    };
+    return exp;
+  }
+
+  function statement(type, value) {
+    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
+    if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
+    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+    if (type == "{") return cont(pushlex("}"), block, poplex);
+    if (type == ";") return cont();
+    if (type == "if") {
+      if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
+        cx.state.cc.pop()();
+      return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
+    }
+    if (type == "function") return cont(functiondef);
+    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
+    if (type == "variable") {
+      if (isTS && value == "type") {
+        cx.marked = "keyword"
+        return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
+      } else {
+        return cont(pushlex("stat"), maybelabel);
+      }
+    }
+    if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"),
+                                      block, poplex, poplex);
+    if (type == "case") return cont(expression, expect(":"));
+    if (type == "default") return cont(expect(":"));
+    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
+                                     statement, poplex, popcontext);
+    if (type == "class") return cont(pushlex("form"), className, poplex);
+    if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
+    if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
+    if (type == "module") return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
+    if (type == "async") return cont(statement)
+    if (value == "@") return cont(expression, statement)
+    return pass(pushlex("stat"), expression, expect(";"), poplex);
+  }
+  function expression(type) {
+    return expressionInner(type, false);
+  }
+  function expressionNoComma(type) {
+    return expressionInner(type, true);
+  }
+  function parenExpr(type) {
+    if (type != "(") return pass()
+    return cont(pushlex(")"), expression, expect(")"), poplex)
+  }
+  function expressionInner(type, noComma) {
+    if (cx.state.fatArrowAt == cx.stream.start) {
+      var body = noComma ? arrowBodyNoComma : arrowBody;
+      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
+      else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
+    }
+
+    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
+    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
+    if (type == "function") return cont(functiondef, maybeop);
+    if (type == "class") return cont(pushlex("form"), classExpression, poplex);
+    if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
+    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
+    if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
+    if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
+    if (type == "{") return contCommasep(objprop, "}", null, maybeop);
+    if (type == "quasi") return pass(quasi, maybeop);
+    if (type == "new") return cont(maybeTarget(noComma));
+    return cont();
+  }
+  function maybeexpression(type) {
+    if (type.match(/[;\}\)\],]/)) return pass();
+    return pass(expression);
+  }
+  function maybeexpressionNoComma(type) {
+    if (type.match(/[;\}\)\],]/)) return pass();
+    return pass(expressionNoComma);
+  }
+
+  function maybeoperatorComma(type, value) {
+    if (type == ",") return cont(expression);
+    return maybeoperatorNoComma(type, value, false);
+  }
+  function maybeoperatorNoComma(type, value, noComma) {
+    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
+    var expr = noComma == false ? expression : expressionNoComma;
+    if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
+    if (type == "operator") {
+      if (/\+\+|--/.test(value)) return cont(me);
+      if (value == "?") return cont(expression, expect(":"), expr);
+      return cont(expr);
+    }
+    if (type == "quasi") { return pass(quasi, me); }
+    if (type == ";") return;
+    if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
+    if (type == ".") return cont(property, me);
+    if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
+    if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
+  }
+  function quasi(type, value) {
+    if (type != "quasi") return pass();
+    if (value.slice(value.length - 2) != "${") return cont(quasi);
+    return cont(expression, continueQuasi);
+  }
+  function continueQuasi(type) {
+    if (type == "}") {
+      cx.marked = "string-2";
+      cx.state.tokenize = tokenQuasi;
+      return cont(quasi);
+    }
+  }
+  function arrowBody(type) {
+    findFatArrow(cx.stream, cx.state);
+    return pass(type == "{" ? statement : expression);
+  }
+  function arrowBodyNoComma(type) {
+    findFatArrow(cx.stream, cx.state);
+    return pass(type == "{" ? statement : expressionNoComma);
+  }
+  function maybeTarget(noComma) {
+    return function(type) {
+      if (type == ".") return cont(noComma ? targetNoComma : target);
+      else return pass(noComma ? expressionNoComma : expression);
+    };
+  }
+  function target(_, value) {
+    if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
+  }
+  function targetNoComma(_, value) {
+    if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
+  }
+  function maybelabel(type) {
+    if (type == ":") return cont(poplex, statement);
+    return pass(maybeoperatorComma, expect(";"), poplex);
+  }
+  function property(type) {
+    if (type == "variable") {cx.marked = "property"; return cont();}
+  }
+  function objprop(type, value) {
+    if (type == "async") {
+      cx.marked = "property";
+      return cont(objprop);
+    } else if (type == "variable" || cx.style == "keyword") {
+      cx.marked = "property";
+      if (value == "get" || value == "set") return cont(getterSetter);
+      return cont(afterprop);
+    } else if (type == "number" || type == "string") {
+      cx.marked = jsonldMode ? "property" : (cx.style + " property");
+      return cont(afterprop);
+    } else if (type == "jsonld-keyword") {
+      return cont(afterprop);
+    } else if (type == "modifier") {
+      return cont(objprop)
+    } else if (type == "[") {
+      return cont(expression, expect("]"), afterprop);
+    } else if (type == "spread") {
+      return cont(expression, afterprop);
+    } else if (type == ":") {
+      return pass(afterprop)
+    }
+  }
+  function getterSetter(type) {
+    if (type != "variable") return pass(afterprop);
+    cx.marked = "property";
+    return cont(functiondef);
+  }
+  function afterprop(type) {
+    if (type == ":") return cont(expressionNoComma);
+    if (type == "(") return pass(functiondef);
+  }
+  function commasep(what, end, sep) {
+    function proceed(type, value) {
+      if (sep ? sep.indexOf(type) > -1 : type == ",") {
+        var lex = cx.state.lexical;
+        if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
+        return cont(function(type, value) {
+          if (type == end || value == end) return pass()
+          return pass(what)
+        }, proceed);
+      }
+      if (type == end || value == end) return cont();
+      return cont(expect(end));
+    }
+    return function(type, value) {
+      if (type == end || value == end) return cont();
+      return pass(what, proceed);
+    };
+  }
+  function contCommasep(what, end, info) {
+    for (var i = 3; i < arguments.length; i++)
+      cx.cc.push(arguments[i]);
+    return cont(pushlex(end, info), commasep(what, end), poplex);
+  }
+  function block(type) {
+    if (type == "}") return cont();
+    return pass(statement, block);
+  }
+  function maybetype(type, value) {
+    if (isTS) {
+      if (type == ":") return cont(typeexpr);
+      if (value == "?") return cont(maybetype);
+    }
+  }
+  function typeexpr(type) {
+    if (type == "variable") {cx.marked = "type"; return cont(afterType);}
+    if (type == "string" || type == "number" || type == "atom") return cont(afterType);
+    if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
+    if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
+  }
+  function maybeReturnType(type) {
+    if (type == "=>") return cont(typeexpr)
+  }
+  function typeprop(type, value) {
+    if (type == "variable" || cx.style == "keyword") {
+      cx.marked = "property"
+      return cont(typeprop)
+    } else if (value == "?") {
+      return cont(typeprop)
+    } else if (type == ":") {
+      return cont(typeexpr)
+    } else if (type == "[") {
+      return cont(expression, maybetype, expect("]"), typeprop)
+    }
+  }
+  function typearg(type) {
+    if (type == "variable") return cont(typearg)
+    else if (type == ":") return cont(typeexpr)
+  }
+  function afterType(type, value) {
+    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
+    if (value == "|" || type == ".") return cont(typeexpr)
+    if (type == "[") return cont(expect("]"), afterType)
+    if (value == "extends") return cont(typeexpr)
+  }
+  function vardef() {
+    return pass(pattern, maybetype, maybeAssign, vardefCont);
+  }
+  function pattern(type, value) {
+    if (type == "modifier") return cont(pattern)
+    if (type == "variable") { register(value); return cont(); }
+    if (type == "spread") return cont(pattern);
+    if (type == "[") return contCommasep(pattern, "]");
+    if (type == "{") return contCommasep(proppattern, "}");
+  }
+  function proppattern(type, value) {
+    if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
+      register(value);
+      return cont(maybeAssign);
+    }
+    if (type == "variable") cx.marked = "property";
+    if (type == "spread") return cont(pattern);
+    if (type == "}") return pass();
+    return cont(expect(":"), pattern, maybeAssign);
+  }
+  function maybeAssign(_type, value) {
+    if (value == "=") return cont(expressionNoComma);
+  }
+  function vardefCont(type) {
+    if (type == ",") return cont(vardef);
+  }
+  function maybeelse(type, value) {
+    if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
+  }
+  function forspec(type) {
+    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
+  }
+  function forspec1(type) {
+    if (type == "var") return cont(vardef, expect(";"), forspec2);
+    if (type == ";") return cont(forspec2);
+    if (type == "variable") return cont(formaybeinof);
+    return pass(expression, expect(";"), forspec2);
+  }
+  function formaybeinof(_type, value) {
+    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
+    return cont(maybeoperatorComma, forspec2);
+  }
+  function forspec2(type, value) {
+    if (type == ";") return cont(forspec3);
+    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
+    return pass(expression, expect(";"), forspec3);
+  }
+  function forspec3(type) {
+    if (type != ")") cont(expression);
+  }
+  function functiondef(type, value) {
+    if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
+    if (type == "variable") {register(value); return cont(functiondef);}
+    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
+    if (isTS && value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, functiondef)
+  }
+  function funarg(type) {
+    if (type == "spread") return cont(funarg);
+    return pass(pattern, maybetype, maybeAssign);
+  }
+  function classExpression(type, value) {
+    // Class expressions may have an optional name.
+    if (type == "variable") return className(type, value);
+    return classNameAfter(type, value);
+  }
+  function className(type, value) {
+    if (type == "variable") {register(value); return cont(classNameAfter);}
+  }
+  function classNameAfter(type, value) {
+    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, classNameAfter)
+    if (value == "extends" || value == "implements" || (isTS && type == ","))
+      return cont(isTS ? typeexpr : expression, classNameAfter);
+    if (type == "{") return cont(pushlex("}"), classBody, poplex);
+  }
+  function classBody(type, value) {
+    if (type == "variable" || cx.style == "keyword") {
+      if ((value == "async" || value == "static" || value == "get" || value == "set" ||
+           (isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) &&
+          cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) {
+        cx.marked = "keyword";
+        return cont(classBody);
+      }
+      cx.marked = "property";
+      return cont(isTS ? classfield : functiondef, classBody);
+    }
+    if (type == "[")
+      return cont(expression, expect("]"), isTS ? classfield : functiondef, classBody)
+    if (value == "*") {
+      cx.marked = "keyword";
+      return cont(classBody);
+    }
+    if (type == ";") return cont(classBody);
+    if (type == "}") return cont();
+    if (value == "@") return cont(expression, classBody)
+  }
+  function classfield(type, value) {
+    if (value == "?") return cont(classfield)
+    if (type == ":") return cont(typeexpr, maybeAssign)
+    if (value == "=") return cont(expressionNoComma)
+    return pass(functiondef)
+  }
+  function afterExport(type, value) {
+    if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
+    if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
+    if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
+    return pass(statement);
+  }
+  function exportField(type, value) {
+    if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
+    if (type == "variable") return pass(expressionNoComma, exportField);
+  }
+  function afterImport(type) {
+    if (type == "string") return cont();
+    return pass(importSpec, maybeMoreImports, maybeFrom);
+  }
+  function importSpec(type, value) {
+    if (type == "{") return contCommasep(importSpec, "}");
+    if (type == "variable") register(value);
+    if (value == "*") cx.marked = "keyword";
+    return cont(maybeAs);
+  }
+  function maybeMoreImports(type) {
+    if (type == ",") return cont(importSpec, maybeMoreImports)
+  }
+  function maybeAs(_type, value) {
+    if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
+  }
+  function maybeFrom(_type, value) {
+    if (value == "from") { cx.marked = "keyword"; return cont(expression); }
+  }
+  function arrayLiteral(type) {
+    if (type == "]") return cont();
+    return pass(commasep(expressionNoComma, "]"));
+  }
+
+  function isContinuedStatement(state, textAfter) {
+    return state.lastType == "operator" || state.lastType == "," ||
+      isOperatorChar.test(textAfter.charAt(0)) ||
+      /[,.]/.test(textAfter.charAt(0));
+  }
+
+  // Interface
+
+  return {
+    startState: function(basecolumn) {
+      var state = {
+        tokenize: tokenBase,
+        lastType: "sof",
+        cc: [],
+        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
+        localVars: parserConfig.localVars,
+        context: parserConfig.localVars && {vars: parserConfig.localVars},
+        indented: basecolumn || 0
+      };
+      if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
+        state.globalVars = parserConfig.globalVars;
+      return state;
+    },
+
+    token: function(stream, state) {
+      if (stream.sol()) {
+        if (!state.lexical.hasOwnProperty("align"))
+          state.lexical.align = false;
+        state.indented = stream.indentation();
+        findFatArrow(stream, state);
+      }
+      if (state.tokenize != tokenComment && stream.eatSpace()) return null;
+      var style = state.tokenize(stream, state);
+      if (type == "comment") return style;
+      state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
+      return parseJS(state, style, type, content, stream);
+    },
+
+    indent: function(state, textAfter) {
+      if (state.tokenize == tokenComment) return CodeMirror.Pass;
+      if (state.tokenize != tokenBase) return 0;
+      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
+      // Kludge to prevent 'maybelse' from blocking lexical scope pops
+      if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
+        var c = state.cc[i];
+        if (c == poplex) lexical = lexical.prev;
+        else if (c != maybeelse) break;
+      }
+      while ((lexical.type == "stat" || lexical.type == "form") &&
+             (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
+                                   (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
+                                   !/^[,\.=+\-*:?[\(]/.test(textAfter))))
+        lexical = lexical.prev;
+      if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
+        lexical = lexical.prev;
+      var type = lexical.type, closing = firstChar == type;
+
+      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
+      else if (type == "form" && firstChar == "{") return lexical.indented;
+      else if (type == "form") return lexical.indented + indentUnit;
+      else if (type == "stat")
+        return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
+      else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
+        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
+      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
+      else return lexical.indented + (closing ? 0 : indentUnit);
+    },
+
+    electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
+    blockCommentStart: jsonMode ? null : "/*",
+    blockCommentEnd: jsonMode ? null : "*/",
+    lineComment: jsonMode ? null : "//",
+    fold: "brace",
+    closeBrackets: "()[]{}''\"\"``",
+
+    helperType: jsonMode ? "json" : "javascript",
+    jsonldMode: jsonldMode,
+    jsonMode: jsonMode,
+
+    expressionAllowed: expressionAllowed,
+    skipExpression: function(state) {
+      var top = state.cc[state.cc.length - 1]
+      if (top == expression || top == expressionNoComma) state.cc.pop()
+    }
+  };
+});
+
+CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
+
+CodeMirror.defineMIME("text/javascript", "javascript");
+CodeMirror.defineMIME("text/ecmascript", "javascript");
+CodeMirror.defineMIME("application/javascript", "javascript");
+CodeMirror.defineMIME("application/x-javascript", "javascript");
+CodeMirror.defineMIME("application/ecmascript", "javascript");
+CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
+CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
+CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
+CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
+CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
+
+});

+ 394 - 0
demos/assets/codemirror-5.27.4/mode/xml/xml.js

@@ -0,0 +1,394 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+var htmlConfig = {
+  autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
+                    'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
+                    'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
+                    'track': true, 'wbr': true, 'menuitem': true},
+  implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
+                     'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
+                     'th': true, 'tr': true},
+  contextGrabbers: {
+    'dd': {'dd': true, 'dt': true},
+    'dt': {'dd': true, 'dt': true},
+    'li': {'li': true},
+    'option': {'option': true, 'optgroup': true},
+    'optgroup': {'optgroup': true},
+    'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
+          'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
+          'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
+          'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
+          'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
+    'rp': {'rp': true, 'rt': true},
+    'rt': {'rp': true, 'rt': true},
+    'tbody': {'tbody': true, 'tfoot': true},
+    'td': {'td': true, 'th': true},
+    'tfoot': {'tbody': true},
+    'th': {'td': true, 'th': true},
+    'thead': {'tbody': true, 'tfoot': true},
+    'tr': {'tr': true}
+  },
+  doNotIndent: {"pre": true},
+  allowUnquoted: true,
+  allowMissing: true,
+  caseFold: true
+}
+
+var xmlConfig = {
+  autoSelfClosers: {},
+  implicitlyClosed: {},
+  contextGrabbers: {},
+  doNotIndent: {},
+  allowUnquoted: false,
+  allowMissing: false,
+  caseFold: false
+}
+
+CodeMirror.defineMode("xml", function(editorConf, config_) {
+  var indentUnit = editorConf.indentUnit
+  var config = {}
+  var defaults = config_.htmlMode ? htmlConfig : xmlConfig
+  for (var prop in defaults) config[prop] = defaults[prop]
+  for (var prop in config_) config[prop] = config_[prop]
+
+  // Return variables for tokenizers
+  var type, setStyle;
+
+  function inText(stream, state) {
+    function chain(parser) {
+      state.tokenize = parser;
+      return parser(stream, state);
+    }
+
+    var ch = stream.next();
+    if (ch == "<") {
+      if (stream.eat("!")) {
+        if (stream.eat("[")) {
+          if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
+          else return null;
+        } else if (stream.match("--")) {
+          return chain(inBlock("comment", "-->"));
+        } else if (stream.match("DOCTYPE", true, true)) {
+          stream.eatWhile(/[\w\._\-]/);
+          return chain(doctype(1));
+        } else {
+          return null;
+        }
+      } else if (stream.eat("?")) {
+        stream.eatWhile(/[\w\._\-]/);
+        state.tokenize = inBlock("meta", "?>");
+        return "meta";
+      } else {
+        type = stream.eat("/") ? "closeTag" : "openTag";
+        state.tokenize = inTag;
+        return "tag bracket";
+      }
+    } else if (ch == "&") {
+      var ok;
+      if (stream.eat("#")) {
+        if (stream.eat("x")) {
+          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
+        } else {
+          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
+        }
+      } else {
+        ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
+      }
+      return ok ? "atom" : "error";
+    } else {
+      stream.eatWhile(/[^&<]/);
+      return null;
+    }
+  }
+  inText.isInText = true;
+
+  function inTag(stream, state) {
+    var ch = stream.next();
+    if (ch == ">" || (ch == "/" && stream.eat(">"))) {
+      state.tokenize = inText;
+      type = ch == ">" ? "endTag" : "selfcloseTag";
+      return "tag bracket";
+    } else if (ch == "=") {
+      type = "equals";
+      return null;
+    } else if (ch == "<") {
+      state.tokenize = inText;
+      state.state = baseState;
+      state.tagName = state.tagStart = null;
+      var next = state.tokenize(stream, state);
+      return next ? next + " tag error" : "tag error";
+    } else if (/[\'\"]/.test(ch)) {
+      state.tokenize = inAttribute(ch);
+      state.stringStartCol = stream.column();
+      return state.tokenize(stream, state);
+    } else {
+      stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
+      return "word";
+    }
+  }
+
+  function inAttribute(quote) {
+    var closure = function(stream, state) {
+      while (!stream.eol()) {
+        if (stream.next() == quote) {
+          state.tokenize = inTag;
+          break;
+        }
+      }
+      return "string";
+    };
+    closure.isInAttribute = true;
+    return closure;
+  }
+
+  function inBlock(style, terminator) {
+    return function(stream, state) {
+      while (!stream.eol()) {
+        if (stream.match(terminator)) {
+          state.tokenize = inText;
+          break;
+        }
+        stream.next();
+      }
+      return style;
+    };
+  }
+  function doctype(depth) {
+    return function(stream, state) {
+      var ch;
+      while ((ch = stream.next()) != null) {
+        if (ch == "<") {
+          state.tokenize = doctype(depth + 1);
+          return state.tokenize(stream, state);
+        } else if (ch == ">") {
+          if (depth == 1) {
+            state.tokenize = inText;
+            break;
+          } else {
+            state.tokenize = doctype(depth - 1);
+            return state.tokenize(stream, state);
+          }
+        }
+      }
+      return "meta";
+    };
+  }
+
+  function Context(state, tagName, startOfLine) {
+    this.prev = state.context;
+    this.tagName = tagName;
+    this.indent = state.indented;
+    this.startOfLine = startOfLine;
+    if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
+      this.noIndent = true;
+  }
+  function popContext(state) {
+    if (state.context) state.context = state.context.prev;
+  }
+  function maybePopContext(state, nextTagName) {
+    var parentTagName;
+    while (true) {
+      if (!state.context) {
+        return;
+      }
+      parentTagName = state.context.tagName;
+      if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
+          !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
+        return;
+      }
+      popContext(state);
+    }
+  }
+
+  function baseState(type, stream, state) {
+    if (type == "openTag") {
+      state.tagStart = stream.column();
+      return tagNameState;
+    } else if (type == "closeTag") {
+      return closeTagNameState;
+    } else {
+      return baseState;
+    }
+  }
+  function tagNameState(type, stream, state) {
+    if (type == "word") {
+      state.tagName = stream.current();
+      setStyle = "tag";
+      return attrState;
+    } else {
+      setStyle = "error";
+      return tagNameState;
+    }
+  }
+  function closeTagNameState(type, stream, state) {
+    if (type == "word") {
+      var tagName = stream.current();
+      if (state.context && state.context.tagName != tagName &&
+          config.implicitlyClosed.hasOwnProperty(state.context.tagName))
+        popContext(state);
+      if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
+        setStyle = "tag";
+        return closeState;
+      } else {
+        setStyle = "tag error";
+        return closeStateErr;
+      }
+    } else {
+      setStyle = "error";
+      return closeStateErr;
+    }
+  }
+
+  function closeState(type, _stream, state) {
+    if (type != "endTag") {
+      setStyle = "error";
+      return closeState;
+    }
+    popContext(state);
+    return baseState;
+  }
+  function closeStateErr(type, stream, state) {
+    setStyle = "error";
+    return closeState(type, stream, state);
+  }
+
+  function attrState(type, _stream, state) {
+    if (type == "word") {
+      setStyle = "attribute";
+      return attrEqState;
+    } else if (type == "endTag" || type == "selfcloseTag") {
+      var tagName = state.tagName, tagStart = state.tagStart;
+      state.tagName = state.tagStart = null;
+      if (type == "selfcloseTag" ||
+          config.autoSelfClosers.hasOwnProperty(tagName)) {
+        maybePopContext(state, tagName);
+      } else {
+        maybePopContext(state, tagName);
+        state.context = new Context(state, tagName, tagStart == state.indented);
+      }
+      return baseState;
+    }
+    setStyle = "error";
+    return attrState;
+  }
+  function attrEqState(type, stream, state) {
+    if (type == "equals") return attrValueState;
+    if (!config.allowMissing) setStyle = "error";
+    return attrState(type, stream, state);
+  }
+  function attrValueState(type, stream, state) {
+    if (type == "string") return attrContinuedState;
+    if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
+    setStyle = "error";
+    return attrState(type, stream, state);
+  }
+  function attrContinuedState(type, stream, state) {
+    if (type == "string") return attrContinuedState;
+    return attrState(type, stream, state);
+  }
+
+  return {
+    startState: function(baseIndent) {
+      var state = {tokenize: inText,
+                   state: baseState,
+                   indented: baseIndent || 0,
+                   tagName: null, tagStart: null,
+                   context: null}
+      if (baseIndent != null) state.baseIndent = baseIndent
+      return state
+    },
+
+    token: function(stream, state) {
+      if (!state.tagName && stream.sol())
+        state.indented = stream.indentation();
+
+      if (stream.eatSpace()) return null;
+      type = null;
+      var style = state.tokenize(stream, state);
+      if ((style || type) && style != "comment") {
+        setStyle = null;
+        state.state = state.state(type || style, stream, state);
+        if (setStyle)
+          style = setStyle == "error" ? style + " error" : setStyle;
+      }
+      return style;
+    },
+
+    indent: function(state, textAfter, fullLine) {
+      var context = state.context;
+      // Indent multi-line strings (e.g. css).
+      if (state.tokenize.isInAttribute) {
+        if (state.tagStart == state.indented)
+          return state.stringStartCol + 1;
+        else
+          return state.indented + indentUnit;
+      }
+      if (context && context.noIndent) return CodeMirror.Pass;
+      if (state.tokenize != inTag && state.tokenize != inText)
+        return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
+      // Indent the starts of attribute names.
+      if (state.tagName) {
+        if (config.multilineTagIndentPastTag !== false)
+          return state.tagStart + state.tagName.length + 2;
+        else
+          return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
+      }
+      if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
+      var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
+      if (tagAfter && tagAfter[1]) { // Closing tag spotted
+        while (context) {
+          if (context.tagName == tagAfter[2]) {
+            context = context.prev;
+            break;
+          } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
+            context = context.prev;
+          } else {
+            break;
+          }
+        }
+      } else if (tagAfter) { // Opening tag spotted
+        while (context) {
+          var grabbers = config.contextGrabbers[context.tagName];
+          if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
+            context = context.prev;
+          else
+            break;
+        }
+      }
+      while (context && context.prev && !context.startOfLine)
+        context = context.prev;
+      if (context) return context.indent + indentUnit;
+      else return state.baseIndent || 0;
+    },
+
+    electricInput: /<\/[\s\w:]+>$/,
+    blockCommentStart: "<!--",
+    blockCommentEnd: "-->",
+
+    configuration: config.htmlMode ? "html" : "xml",
+    helperType: config.htmlMode ? "html" : "xml",
+
+    skipAttribute: function(state) {
+      if (state.state == attrValueState)
+        state.state = attrState
+    }
+  };
+});
+
+CodeMirror.defineMIME("text/xml", "xml");
+CodeMirror.defineMIME("application/xml", "xml");
+if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
+  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
+
+});

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
demos/assets/codemirror-5.29.0/codemirror-merged.min.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
demos/assets/codemirror-5.29.0/codemirror-merged.min.js


+ 6 - 0
demos/assets/common.css

@@ -0,0 +1,6 @@
+html, body {
+  overflow: hidden;
+}
+::-webkit-scrollbar {
+  display: none;
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
demos/assets/d3-4.13.0.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 6133 - 0
demos/assets/d3-legend-2.25.6.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 3 - 0
demos/assets/d3-legend-2.25.6.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1915 - 0
demos/assets/data/g6-index.json


+ 59 - 0
demos/assets/data/modeling-methods.json

@@ -0,0 +1,59 @@
+{
+  "name": "Modeling Methods",
+  "children": [
+    {
+      "name": "Classification",
+      "children": [
+        { "name": "Logistic regression" },
+        { "name": "Linear discriminant analysis" },
+        { "name": "Rules" },
+        { "name": "Decision trees" },
+        { "name": "Naive Bayes" },
+        { "name": "K nearest neighbor" },
+        { "name": "Probabilistic neural network" },
+        { "name": "Support vector machine" }
+      ]
+    },
+    {
+      "name": "Consensus",
+      "children": [
+        {
+          "name": "Models diversity",
+          "children": [
+            { "name": "Different initializations" },
+            { "name": "Different parameter choices" },
+            { "name": "Different architectures" },
+            { "name": "Different modeling methods" },
+            { "name": "Different training sets" },
+            { "name": "Different feature sets" }
+          ]
+        },
+        {
+          "name": "Methods",
+          "children": [
+            { "name": "Classifier selection" },
+            { "name": "Classifier fusion" }
+          ]
+        },
+        {
+          "name": "Common",
+          "children": [
+            { "name": "Bagging" },
+            { "name": "Boosting" },
+            { "name": "AdaBoost" }
+          ]
+        }
+      ]
+    },
+    {
+      "name": "Regression",
+      "children": [
+        { "name": "Multiple linear regression" },
+        { "name": "Partial least squares" },
+        { "name": "Multi-layer feedforward neural network" },
+        { "name": "General regression neural network" },
+        { "name": "Support vector regression" }
+      ]
+    }
+  ]
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 1262 - 0
demos/assets/data/university.json


Diferenças do arquivo suprimidas por serem muito extensas
+ 7337 - 0
demos/assets/data/xiaomi.json


Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
demos/assets/file-saver-1.3.8.min.js


+ 106 - 0
demos/assets/index.css

@@ -0,0 +1,106 @@
+body {
+  /*overflow: hidden;*/
+}
+::-webkit-scrollbar {
+  display: none;
+}
+
+.filter {
+  padding: 15px;
+}
+.demo-thumbnails {
+  padding: 15px;
+}
+.demo-thumbnail {
+  margin-bottom: 30px;
+}
+.thumbnail-container {
+  overflow: hidden;
+  max-height: 202px;
+}
+.thumbnail-container img {
+  width: 100%;
+}
+.card-body {
+  padding: 8px;
+}
+iframe {
+  border: none;
+}
+.scaled-frame {
+  margin: 0 auto;
+  width: 800px;
+  height: 450px;
+  -webkit-transform: scale(0.45);
+  -webkit-transform-origin: 0 0;
+}
+#doc-container {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-flex: 1;
+  -webkit-flex: 1 1 auto;
+  -ms-flex: 1 1 auto;
+  flex: 1 1 auto;
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  background: white;
+}
+.code-panel {
+  -webkit-overflow-scrolling: touch;
+  -webkit-transform: translateZ(0);
+  background-color: #fff;
+  display: block;
+  width: 400px;
+  height: 100%;
+  overflow: hidden;
+  padding: 0 1rem 0 0;
+  position: relative;
+  right: 0;
+  top: 0;
+  border-right: 0.1rem solid #e8e8e8;
+  transform: translateZ(0);
+  white-space: nowrap;
+  will-change: scroll-position;
+  z-index: 1;
+}
+
+.code-banner {
+  padding: .5rem 0;
+}
+
+.code-panel, .code-editor .CodeMirror {
+  height: 100%;
+}
+
+.code-editor {
+  height: calc(100% - 3.375rem);
+}
+
+
+#chart-panel {
+  flex: 1;
+  overflow: hidden;
+  padding: 1rem;
+  -webkit-transform: translateZ(0);
+  transform: translateZ(0);
+  will-change: scroll-position;
+  -webkit-overflow-scrolling: touch;
+}
+#resize-handler {
+  //background: #DEDEEB;
+  border-right: 2px solid #DEDEEB;
+  cursor: col-resize;
+}
+.chart-frame {
+  height: 100%;
+  width: 100%;
+}
+
+.btn {
+  cursor: pointer;
+}

+ 105 - 0
demos/assets/index.js

@@ -0,0 +1,105 @@
+(function () {
+  // filtering
+  const $query = $('#query');
+  function filter() {
+    const str = $query.val();
+    if (!str) {
+      $('.demo-thumbnail').show();
+    } else {
+      $('.demo-thumbnail').each(function () {
+        const $thumbnail = $(this);
+        const basename = $thumbnail.data('basename');
+        if (basename.indexOf(str) === -1) {
+          $thumbnail.hide();
+        } else {
+          $thumbnail.show();
+        }
+      });
+    }
+  }
+  $query.on('input', _.debounce(filter));
+
+  // router
+  let currentId;
+  const $code = $('#code');
+  const htmlEditor = CodeMirror.fromTextArea($code[0], {
+    mode: "text/html",
+    extraKeys: {
+      'Ctrl-Space': 'autocomplete'
+    },
+    foldGutter: true,
+    gutters: [
+      'CodeMirror-linenumbers',
+      'CodeMirror-foldgutter'
+    ],
+    lineNumbers: true,
+    lineWrapping: false
+  });
+
+  const $docContainer = $('#doc-container');
+  const $chartPanel = $('#chart-panel');
+  const $codePanel = $('#code-panel');
+
+  function syncCode(code) {
+    $chartPanel.html('<iframe class="chart-frame" frameborder="0"></iframe>');
+    $chartPanel.find('iframe')[0].contentWindow.document.write(code);
+    htmlEditor.getDoc().setValue(code);
+  }
+
+  routie({
+    '/:id': id => {
+      $docContainer.show();
+      const $htmlCode = $(`#code-${id}`);
+      const code = $htmlCode.text();
+      syncCode(code)
+    },
+    '': () => {
+      $docContainer.hide();
+    }
+  });
+
+  // resizable
+  $codePanel.resizable({
+    handleSelector: '#resize-handler',
+    resizeWidthFrom: 'right',
+    resizeHeight: false,
+    onDragStart() {
+      $docContainer.css('pointer-events', 'none');
+      $docContainer.css('cursor', 'col-resize');
+      $codePanel.find('.CodeMirror-gutter-elt').css('cursor', 'col-resize');
+    },
+    onDragEnd() {
+      $docContainer.css('pointer-events', 'auto');
+      $docContainer.css('cursor', 'default');
+      $codePanel.find('.CodeMirror-gutter-elt').css('cursor', 'default');
+    },
+  });
+
+  // copy code
+  const BTN_COPY_SELECTOR = '#copy-code';
+  const clipboard = new Clipboard(BTN_COPY_SELECTOR, {
+    text: () => htmlEditor.getValue(),
+  });
+  let timer;
+  clipboard.on('success', e => {
+    e.clearSelection();
+    $(BTN_COPY_SELECTOR).text('Succeed!');
+    clearTimeout(timer);
+    timer = setTimeout(() => {
+      $(BTN_COPY_SELECTOR).text('Copy');
+    }, 2000);
+  });
+  clipboard.on('error', e => {
+    e.clearSelection();
+    $(BTN_COPY_SELECTOR).text('Failed!');
+    clearTimeout(timer);
+    timer = setTimeout(() => {
+      $(BTN_COPY_SELECTOR).text('Copy');
+    }, 2000);
+  });
+
+  // run code
+  $('#execute').on('click', () => {
+    syncCode(htmlEditor.getValue());
+  });
+})();

Diferenças do arquivo suprimidas por serem muito extensas
+ 4 - 0
demos/assets/jquery-3.2.1.min.js


+ 164 - 0
demos/assets/jquery.resizable-0.20.0.js

@@ -0,0 +1,164 @@
+/// <reference path="jquery.js" />
+/*
+jquery-resizable
+Version 0.20 - 3/10/2017
+© 2015-2017 Rick Strahl, West Wind Technologies
+www.west-wind.com
+Licensed under MIT License
+*/
+(function(factory, undefined) {
+	if (typeof define === 'function' && define.amd) {
+		// AMD
+		define(['jquery'], factory);
+	} else if (typeof module === 'object' && typeof module.exports === 'object') {
+		// CommonJS
+		module.exports = factory(require('jquery'));
+	} else {
+		// Global jQuery
+		factory(jQuery);
+	}
+}(function($, undefined) {
+	
+    function getHandle(selector, $el) {
+        if (selector && selector.trim()[0] === ">") {
+            selector = selector.trim().replace(/^>\s*/, "");
+
+            return $el.find(selector);
+        }
+
+        return selector ? $(selector) : $el;
+    }
+
+    if ($.fn.resizable)
+        return;
+
+    $.fn.resizable = function fnResizable(options) {
+        var opt = {
+            // selector for handle that starts dragging
+            handleSelector: null,
+            // resize the width
+            resizeWidth: true,
+            // resize the height
+            resizeHeight: true,            
+            // the side that the width resizing is relative to
+            resizeWidthFrom: 'right',
+            // the side that the height resizing is relative to
+            resizeHeightFrom: 'bottom',            
+            // hook into start drag operation (event passed)
+            onDragStart: null,
+            // hook into stop drag operation (event passed)
+            onDragEnd: null,
+            // hook into each drag operation (event passed)
+            onDrag: null,
+            // disable touch-action on $handle
+            // prevents browser level actions like forward back gestures
+            touchActionNone: true
+        };
+        if (typeof options == "object") opt = $.extend(opt, options);
+
+        return this.each(function () {
+            var startPos, startTransition;
+
+            var $el = $(this);
+
+            var $handle = getHandle(opt.handleSelector, $el);
+
+            if (opt.touchActionNone)
+                $handle.css("touch-action", "none");
+
+            $el.addClass("resizable");
+            $handle.bind('mousedown.rsz touchstart.rsz', startDragging);
+
+            function noop(e) {
+                e.stopPropagation();
+                e.preventDefault();
+            };
+
+            function startDragging(e) {
+                // Prevent dragging a ghost image in HTML5 / Firefox and maybe others    
+                if ( e.preventDefault ) {
+                  e.preventDefault();
+                }
+                
+                startPos = getMousePos(e);
+                startPos.width = parseInt($el.width(), 10);
+                startPos.height = parseInt($el.height(), 10);
+
+                startTransition = $el.css("transition");
+                $el.css("transition", "none");
+
+                if (opt.onDragStart) {
+                    if (opt.onDragStart(e, $el, opt) === false)
+                        return;
+                }
+                opt.dragFunc = doDrag;
+
+                $(document).bind('mousemove.rsz', opt.dragFunc);
+                $(document).bind('mouseup.rsz', stopDragging);
+                if (window.Touch || navigator.maxTouchPoints) {
+                    $(document).bind('touchmove.rsz', opt.dragFunc);
+                    $(document).bind('touchend.rsz', stopDragging);
+                }
+                $(document).bind('selectstart.rsz', noop); // disable selection
+            }
+
+            function doDrag(e) {
+                var pos = getMousePos(e), newWidth, newHeight;
+
+                if (opt.resizeWidthFrom === 'left')
+                    newWidth = startPos.width - pos.x + startPos.x;
+                else
+                    newWidth = startPos.width + pos.x - startPos.x;
+
+                if (opt.resizeHeightFrom === 'top')
+                    newHeight = startPos.height - pos.y + startPos.y;
+                else
+                    newHeight = startPos.height + pos.y - startPos.y;
+
+                if (!opt.onDrag || opt.onDrag(e, $el, newWidth, newHeight, opt) !== false) {
+                    if (opt.resizeHeight)
+                        $el.height(newHeight);                    
+
+                    if (opt.resizeWidth)
+                        $el.width(newWidth);                    
+                }
+            }
+
+            function stopDragging(e) {
+                e.stopPropagation();
+                e.preventDefault();
+
+                $(document).unbind('mousemove.rsz', opt.dragFunc);
+                $(document).unbind('mouseup.rsz', stopDragging);
+
+                if (window.Touch || navigator.maxTouchPoints) {
+                    $(document).unbind('touchmove.rsz', opt.dragFunc);
+                    $(document).unbind('touchend.rsz', stopDragging);
+                }
+                $(document).unbind('selectstart.rsz', noop);
+
+                // reset changed values
+                $el.css("transition", startTransition);
+
+                if (opt.onDragEnd)
+                    opt.onDragEnd(e, $el, opt);
+
+                return false;
+            }
+
+            function getMousePos(e) {
+                var pos = { x: 0, y: 0, width: 0, height: 0 };
+                if (typeof e.clientX === "number") {
+                    pos.x = e.clientX;
+                    pos.y = e.clientY;
+                } else if (e.originalEvent.touches) {
+                    pos.x = e.originalEvent.touches[0].clientX;
+                    pos.y = e.originalEvent.touches[0].clientY;
+                } else
+                    return null;
+
+                return pos;
+            }
+        });
+    };
+}));

Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
demos/assets/lazyload-2.0.0-beta.2.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 136 - 0
demos/assets/lodash-4.17.4.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 0
demos/assets/popper.js-1.12.5/popper-utils.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 0
demos/assets/popper.js-1.12.5/popper.min.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 8 - 0
demos/assets/routie-0.3.2.min.js


+ 77 - 0
demos/customAnchor.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>自定义-锚点</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 250,
+    y: 250
+  },{
+    id: 'node2',
+    x: 250,
+    y: 100
+  },{
+    id: 'node3',
+    x: 50,
+    y: 400
+  },{
+    id: 'node5',
+    x: 450,
+    y: 400
+  }],
+  edges: [{
+    id: 'edge1',
+    target: 'node2',
+    source: 'node1'
+  },{
+    id: 'edge2',
+    target: 'node3',
+    source: 'node1'
+  },{
+    id: 'edge4',
+    target: 'node5',
+    source: 'node1'
+  }]
+};
+
+// 设置右边中点为连接锚点
+G6.registerNode('node2', {
+  anchor: [
+    // 右边中点
+    [1, 0.5]
+  ]
+});
+
+// 设置相交盒模型为矩形
+G6.registerNode('node3', {
+  anchor: {
+    type: 'rect'
+  }
+});
+
+const graph = new G6.Graph({
+  container: 'mountNode',  // 容器ID
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.node({
+  label(model) {
+    return model.id;
+  },
+  shape(model) {
+    return model.id;
+  }
+})
+graph.read(data);
+</script>
+</body>
+</html>

+ 54 - 0
demos/customDraw.html

@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>自定义-绘制</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  "nodes": [
+    {
+      "shape": "customNode",
+      "id": "node1"
+    }
+  ],
+};
+
+G6.registerNode('customNode', {
+  draw(item){
+    const group = item.getGraphicGroup();
+    group.addShape('text', {
+      attrs: {
+        x: 100,
+        y: 100,
+        fill: '#333',
+        text: '我是一个自定义节点,\n有下面那个方形和我自己组成'
+      }
+    });
+    return group.addShape('rect', {
+      attrs: {
+        x: 100,
+        y: 100,
+        width: 100,
+        height: 100,
+        stroke: 'red'
+      }
+    });
+  }
+});
+
+const graph = new G6.Graph({
+  container: 'mountNode',  // 容器ID
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+
+</script>
+</body>
+</html>

+ 75 - 0
demos/customEnterLeaveAnimate.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>自定义-出场入场动画</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+  const data = {
+    "nodes": [
+      {
+        "shape": "customNode",
+        "id": "node1",
+        x: 100,
+        y: 100
+      }
+    ],
+  };
+
+  G6.registerNode('customNode', {
+    // 自定义入场动画
+    enterAnimate(item) {
+      const group = item.getGraphicGroup();
+      const model = item.getModel();
+      const x = model.x;
+      const y = model.y;
+
+      group.transform([
+        [ 't', -x, -y ],
+        [ 's', 0.01, 0.01 ],
+        [ 't', x, y ],
+      ]);
+      !group.get('destroyed') && group.animate({
+        transform: [
+          [ 't', -x, -y ],
+          [ 's', 100, 100 ],
+          [ 't', x, y ],
+        ],
+      }, 450, 'easeBackOut');
+    },
+    // 自定义出场动画
+    leaveAnimate(item) {
+      const group = item.getGraphicGroup();
+      const model = item.getModel();
+      const x = model.x;
+      const y = model.y;
+      group && !group.get('destroyed') && group.animate({
+        transform: [
+          [ 't', -x, -y ],
+          [ 's', 0.01, 0.01 ],
+          [ 't', x, y ],
+        ],
+      }, 450, 'easeCircleOut', function() {
+        group.remove();
+      });
+    }
+  });
+
+  const graph = new G6.Graph({
+    container: 'mountNode',  // dom 容器 或 容器ID
+    fitView: 'cc',
+    height: window.innerHeight,
+    animate: true
+  });
+  graph.read(data);
+  setTimeout(()=>{
+    graph.remove("node1")
+  }, 800)
+</script>
+</body>
+</html>

+ 49 - 0
demos/customFlowingEdge.html

@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>自定义-流动效果线条</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+G6.registerEdge('flowingEdge', {
+  afterDraw(item) {
+    const keyShape = item.getKeyShape();
+    keyShape.attr('lineDash', [10, 10]);
+    keyShape.attr('lineDashOffset', 0)
+    keyShape.animate({
+      lineDashOffset: -20,
+      repeat: true
+    }, 500);
+  }
+});
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200
+  }],
+  edges: [{
+    target: 'node2',
+    source: 'node1',
+    shape:  'flowingEdge'
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+
+</script>
+</body>
+</html>

+ 72 - 0
demos/customInherit.html

@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>自定义-继承</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 50,
+    y: 50,
+    shape: 'rect'
+  }, {
+    id: 'node2',
+    x: 100,
+    y: 200,
+    shape: 'custom'
+  }]
+};
+
+G6.registerNode('rect', {
+  draw(item){
+    const group = item.getGraphicGroup();
+    this.drawText(item);
+    return group.addShape('rect', {
+      attrs: {
+        x: 100,
+        y: 100,
+        width: 100,
+        height: 100,
+        stroke: 'red'
+      }
+    });
+  },
+  drawText(item) {
+    const group = item.getGraphicGroup();
+    const text = this.getText();
+    group.addShape('text', {
+      attrs: {
+        x: 100,
+        y: 100,
+        fill: '#333',
+        text
+      }
+    });
+  },
+  getText() {
+    return '我是一个节点 rect';
+  }
+});
+
+G6.registerNode('custom', {
+  getText(){
+    return '我是一个自定义节点,\n继承于 rect';
+  }
+}, 'rect');
+
+const graph = new G6.Graph({
+  container: 'mountNode',  // 容器ID
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+</script>
+</body>
+</html>

+ 81 - 0
demos/defaultEdge.html

@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>默认-边</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200,
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200,
+  }],
+  edges: [{
+    source: 'node1',
+    target: 'node2',
+    label: '普通边'
+  },{
+    source: 'node1',
+    target: {
+      x: 100,
+      y: 50
+    },
+    label: '单向悬空边'
+  },{
+    source: {
+      x: 150,
+      y: 50
+    },
+    target: {
+      x: 150,
+      y: 100
+    },
+    label: '双向悬空边'
+  },{
+    source: 'node1',
+    target: {
+      x: 100,
+      y: 280,
+    },
+    style: {
+      endArrow: true
+    },
+    label: '箭头'
+  },{
+    source: 'node1',
+    target: {
+      x: 10,
+      y: 200,
+    },
+    sizesize: 4,
+    label: '粗细'
+  },{
+    source: 'node2',
+    target: {
+      x: 400,
+      y: 200,
+    },
+    color: 'red',
+    label: '颜色'
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+
+</script>
+</body>
+</html>

+ 62 - 0
demos/defaultGroup.html

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>默认-群组</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200,
+    label: '节点1',
+    parent: 'group1'
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200,
+    label: '节点2',
+    parent: 'group1'
+  },{
+    id: 'node3',
+    x: 100,
+    y: 300,
+    label: '节点3',
+    parent: 'group2'
+  },{
+    id: 'node4',
+    x: 300,
+    y: 300,
+    label: '节点4',
+    parent: 'group2'
+  }],
+  edges: [{
+    id: 'edge1',
+    target: 'node2',
+    source: 'node1'
+  }],
+  groups: [{
+    id: 'group1',
+    label: '展开群组'
+  },{
+    id: 'group2',
+    label: '折叠群组',
+    collapsed: true
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+
+</script>
+</body>
+</html>

+ 41 - 0
demos/defaultNode.html

@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>默认-节点</title>
+</head>
+<body>
+<div id="mountNode"></div>
+<script src="../build/g6.js"></script>
+<script>
+const data = {
+  nodes: [{
+    id: 'node1',
+    x: 100,
+    y: 200,
+    size: 40,
+    label: '节点1'
+  },{
+    id: 'node2',
+    x: 300,
+    y: 200,
+    color: '#eb2f96',
+    label: '节点2'
+  }],
+  edges: [{
+    id: 'edge1',
+    target: 'node2',
+    source: 'node1'
+  }]
+};
+const graph = new G6.Graph({
+  container: 'mountNode',
+  fitView: 'cc',
+  height: window.innerHeight
+});
+graph.read(data);
+</script>
+</body>
+</html>

+ 56 - 0
demos/galleryCodeTree.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>画廊-函数生成树</title>
+</head>
+<body>
+<div id="mountNode" ></div>
+<p>创意来源:https://www.rosettacode.org/wiki/Fractal_tree#JavaScript</p>
+<script src="../build/g6.js"></script>
+<script>
+const getTreeData = (x1, y1, angle, depth, nodes = [], edges = []) => {
+  const deg_to_rad = Math.PI / 180;
+  if (depth !== 0){
+    const x2 = x1 + (Math.cos(angle * deg_to_rad) * depth * 10.0);
+    const y2 = y1 + (Math.sin(angle * deg_to_rad) * depth * 10.0);
+    const id1 = G6.Util.guid();
+    const id2 = G6.Util.guid();
+    nodes.push({
+      id: id1,
+      x: x1,
+      y: y1
+    });
+    nodes.push({
+      id: id2,
+      x: x2,
+      y: y2
+    });
+    edges.push({
+      source: id1,
+      target: id2
+    });
+    getTreeData(x2, y2, angle - 30, depth - 1, nodes, edges);
+    getTreeData(x2, y2, angle + 30, depth - 1, nodes, edges);
+  }
+  return {
+    nodes,
+    edges,
+  }
+};
+const data = getTreeData(0, 0, -90, 9);
+const graph = new G6.Graph({
+  container: 'mountNode',
+  fitView: 'autoZoom',
+  height: window.innerHeight
+});
+graph.node({
+  size: 2
+});
+graph.read(data);
+
+</script>
+</body>
+</html>

+ 133 - 0
demos/galleryUniversity.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>画廊-浙江省各城市大学分布</title>
+  <style>
+    .label {
+      fill: white;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="mountNode" style="background: #0F141F;"></div>
+  <p>数据来源:https://github.com/hugg95/university-data</p>
+  <p>描述:展示了浙江省,各个城市,本科和专科的数量及分布情况。</p>
+  <p>解读:杭州作为省会城市拥有最多的高校,各个城市专科和本科学校的数量基本持平。</p>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="./assets/d3-4.13.0.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script src="../build/toolD3Mapper.js"></script>
+  <script>
+    $.getJSON('./assets/data/university.json', data => {
+      const Mapper = window.ToolD3Mapper;
+      const Util = G6.Util;
+      const { forceSimulation, forceLink, forceManyBody, forceX, forceY } = d3;
+      const nodeColorMapper = new Mapper('node', '办学层次', 'color', ['#4BA4C4', '#F0D79F', '#FF8B33']);
+      let simulation;
+
+      // 注册城市文本导引信息
+      G6.registerGuide('city-label', {
+        draw(item) {
+          const tree = item.getGraph();
+          const roots = tree.getRoots();
+          const group = item.getGraphicGroup();
+          roots.forEach(root => {
+            const bbox = Util.getTotalBBox(root.getAllChildren().map(child => {
+              return child.getBBox();
+            }));
+            const model = root.getModel();
+            group.addShape('text', {
+              attrs: {
+                x: bbox.minX,
+                y: bbox.minY - 8,
+                text: model.name,
+                fill: '#ccc',
+              },
+            });
+          });
+        },
+      });
+
+      const tree = new G6.Tree({
+        container: 'mountNode',
+        height: window.innerHeight,
+        plugins: [nodeColorMapper],
+        layout() {
+          const nodes = tree.getNodes().map(node => {
+            return node.getModel();
+          });
+          const edges = tree.getEdges().map(edge => {
+            return Util.mix({}, edge.getModel());
+          });
+          simulation && simulation.stop();
+          simulation = forceSimulation(nodes)
+            .force('charge', forceManyBody())
+            .force('link', forceLink(edges).id(d => {
+              return d.id;
+            }).distance(20)
+              .strength(1))
+            .force('y', forceY())
+            .on('tick', () => {
+              tree.updateNodePosition();
+            });
+        },
+      });
+      tree.node({
+        size: 10,
+        style: {
+          stroke: null
+        }
+      });
+      tree.edge({
+        style() {
+          return {
+            strokeOpacity: 0.6
+          }
+        }
+      });
+      tree.read(data);
+      tree.add('guide', {
+        shape: 'city-label',
+      });
+      tree.translate(tree.getWidth() / 2, tree.getHeight() / 2);
+
+      // 拖拽画布交互
+      let lastPoint;
+      tree.on('canvas:mouseenter', () => {
+        tree.css({
+          cursor: '-webkit-grabbing',
+        });
+      });
+      tree.on('dragstart', () => {
+        tree.css({
+          cursor: '-webkit-grabbing',
+        });
+      });
+      tree.on('drag', ev => {
+        if (lastPoint) {
+          tree.translate(ev.domX - lastPoint.x, ev.domY - lastPoint.y);
+        }
+        lastPoint = {
+          x: ev.domX,
+          y: ev.domY,
+        };
+      });
+      tree.on('dragend', () => {
+        lastPoint = undefined;
+        tree.css({
+          cursor: '-webkit-grab',
+        });
+      });
+      tree.on('canvas:mouseleave', () => {
+        lastPoint = undefined;
+      });
+    });
+  </script>
+</body>
+
+</html>

+ 98 - 0
demos/galleryWebIndex.html

@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>画廊-首页展示</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <p>数据来源:http://sigmajs.org/assets/data/les-miserables.json</p>
+  <p>描述:数据含义暂时未知,用于艺术展示</p>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="./assets/d3-4.13.0.min.js"></script>
+  <script src="./assets/d3-legend-2.25.6.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script src="../build/layoutCircle.js"></script>
+  <script src="../build/layoutDagre.js"></script>
+  <script src="../build/layoutGrid.js"></script>
+  <script src="../build/layoutArchimeddeanSpiral.js"></script>
+  <script src="../build/toolD3Mapper.js"></script>
+  <script src="../build/templateMaxSpanningForest.js"></script>
+  <script>
+    $.getJSON('./assets/data/g6-index.json', data => {
+      const Template = G6.Plugins['template.maxSpanningForest'];
+      const Mapper = G6.Plugins['tool.d3.mapper'];
+      const nodeSizeMapper = new Mapper('node', 'weight', 'size', [8, 20], {
+        legendCfg: null
+      });
+      const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [1, 8], {
+        legendCfg: null
+      });
+      const nodeColorMapper = new Mapper('node', 'weight', 'color', ['#E0F5FF', '#BAE7FF', '#91D5FF', '#69C0FF', '#3DA0F2', '#1581E6', '#0860BF'], {
+        legendCfg: null
+      });
+      const template = new Template();
+      const graph = new G6.Graph({
+        id: 'mountNode',             // dom id
+        height: window.innerHeight,
+        plugins: [template, nodeSizeMapper, nodeColorMapper, edgeSizeMapper],
+        animate: true,
+      });
+      const force = template.layout;
+      const circle = new G6.Layouts['Circle']({
+        sort(a, b) {
+          return a.weight - b.weight;
+        }
+      });
+      const grid = new G6.Layouts['Grid']({
+        sort(a, b) {
+          return b.weight - a.weight;
+        }
+      });
+      const dagre = new G6.Layouts['Dagre']({
+        nodesep() {
+          return graph.getWidth() / 50;
+        },
+        ranksep() {
+          return graph.getHeight() / 25;
+        },
+        marginx() {
+          return graph.getWidth() / 8;
+        },
+        marginy() {
+          return graph.getHeight() / 8;
+        },
+        useEdgeControlPoint: false,
+      });
+      const spiral = new G6.Layouts['ArchimeddeanSpiral']({
+        sort(a, b) {
+          return b.weight - a.weight;
+        }
+      });
+      graph.edge({
+        style(model) {
+          return {
+            stroke: graph.find(model.target).getModel().color,
+            strokeOpacity: 0.8
+          };
+        }
+      });
+      graph.read(data);
+      setInterval(() => {
+        if (document.visibilityState === 'visible') {
+          let layouts = [circle, dagre, force, grid, spiral];
+          layouts = layouts.filter(layout => {
+            return layout !== graph.getLayout();
+          });
+          const layout = layouts[parseInt(layouts.length * Math.random())];
+          graph.changeLayout(layout);
+        }
+      }, 2000);
+    });
+  </script>
+</body>
+</html>

+ 170 - 0
demos/galleryXiaomi.html

@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>画廊-小米关系图谱</title>
+</head>
+
+<body>
+  <div style="position:relative; background: #e5ddd1;">
+    <div id="mountNode"></div>
+    <div id="minimap" style="position:absolute; bottom: 0px; right: 0px;"></div>
+  </div>
+  <p>数据来源:企查查</p>
+  <p>描述:展示了 小米科技有限责任公司 的企业关系图谱</p>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="./assets/d3-4.13.0.min.js"></script>
+  <script src="./assets/d3-legend-2.25.6.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script src="../build/behaviourAnalysis.js"></script>
+  <script src="../build/toolD3Mapper.js"></script>
+  <script>
+    $.getJSON('./assets/data/xiaomi.json', data => {
+      const Mapper = window.ToolD3Mapper;
+      const { forceSimulation, forceLink, forceManyBody, forceX, forceY, forceCollide } = d3;
+      const nodeMap = {};
+      const nodeSizeMapper = new Mapper('node', 'degree', 'size', [8, 48], {
+        legendCfg: null
+      });
+      const nodeColorMapper = new Mapper('node', 'type', 'color', ['#e18826', '#002a67']);
+      const G = G6.G;
+      let simulation;
+      const graph = new G6.Graph({
+        container: 'mountNode',
+        height: window.innerHeight,
+        plugins: [nodeSizeMapper, nodeColorMapper],
+        modes: {
+          default: ['rightPanCanvas']
+        },
+        layout(nodes, edges) {
+          if (simulation) {
+            simulation.alphaTarget(0.3).restart();
+          } else {
+            simulation = forceSimulation(nodes)
+              .force('charge', forceManyBody().strength(-100))
+              .force('link', forceLink(edges).id(model => {
+                return model.id;
+              }))
+              .force('collision', forceCollide().radius(model => {
+                return model.size / 2 * 1.2;
+              }))
+              .force('y', forceY())
+              .force('x', forceX())
+              .on('tick', () => {
+                graph.updateNodePosition();
+              });
+          }
+        }
+      });
+      graph.node({
+        style(model) {
+          if (model.type === 'Company') {
+            return {
+              fill: '#e18826',
+              shadowColor: 'rgba(0,0,0, 0.3)',
+              shadowBlur: 3,
+              shadowOffsetX: 2,
+              shadowOffsetY: 2,
+              stroke: null
+            }
+          }
+          return {
+            fill: '#002a67',
+            shadowColor: 'rgba(0,0,0, 0.3)',
+            shadowBlur: 3,
+            shadowOffsetX: 3,
+            shadowOffsetY: 5,
+            stroke: null
+          }
+        },
+        label(model) {
+          return {
+            text: model.properties['name'],
+            stroke: null,
+            fill: '#fff'
+          };
+        }
+      });
+      graph.edge({
+        style() {
+          return {
+            stroke: '#b3b3b3',
+            lineWidth: 2
+          }
+        }
+      });
+      graph.read(data);
+      graph.translate(graph.getWidth() / 2, graph.getHeight() / 2);
+
+      // 拖拽节点交互
+      let subject; // 逼近点
+      graph.on('mousedown', ev => {
+        if (ev.domEvent.button === 0) {
+          subject = simulation.find(ev.x, ev.y);
+        }
+      });
+
+      graph.on('dragstart', ev => {
+        subject && simulation.alphaTarget(0.3).restart();
+      });
+
+      graph.on('drag', ev => {
+        if (subject) {
+          subject.fx = ev.x;
+          subject.fy = ev.y;
+        }
+      });
+
+      graph.on('mouseup', resetState);
+      graph.on('canvas:mouseleave', resetState);
+
+      function resetState() {
+        if (subject) {
+          simulation.alphaTarget(0);
+          subject.fx = null;
+          subject.fy = null;
+          subject = null
+        }
+      }
+
+      // 鼠标移入节点显示 label
+      function tryHideLabel(node) {
+        const model = node.getModel();
+        const label = node.getLabel();
+        const labelBox = label.getBBox();
+        if (labelBox.maxX - labelBox.minX > model.size) {
+          label.hide();
+          graph.draw();
+        }
+      }
+      const nodes = graph.getNodes();
+      const edges = graph.getEdges();
+
+      edges.forEach(edge => {
+        edge.getGraphicGroup().set('capture', false); // 移除边的捕获,提升图形拾取效率
+      });
+
+      nodes.forEach(node => {
+        tryHideLabel(node);
+      });
+
+      graph.on('node:mouseenter', ev => {
+        const item = ev.item;
+        graph.toFront(item);
+        item.getLabel().show();
+        graph.draw();
+      });
+
+      graph.on('node:mouseleave', ev => {
+        const item = ev.item;
+        tryHideLabel(item);
+      });
+    });
+
+  </script>
+</body>
+
+</html>

+ 47 - 0
demos/graphAnimate.html

@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>图-动画</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }],
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      animate: true
+    });
+    graph.read(data);
+    setTimeout(() => {
+      graph.update('node1', {
+        x: 50,
+        y: 50
+      });
+    }, 1000);
+    setTimeout(() => {
+      graph.update('node1', {
+        x: 200,
+        y: 50
+      });
+    }, 1200);
+    setTimeout(() => {
+      graph.remove('node1');
+    }, 2000);
+
+  </script>
+</body>
+</html>

+ 49 - 0
demos/graphChangeSize.html

@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>图-更改画布尺寸</title>
+</head>
+
+<body>
+  <button id="changeSize">更改画布尺寸</button>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+    });
+    graph.read(data);
+    graph.css({
+      border: '1px solid red',
+    });
+    document.getElementById('changeSize').onclick = () => {
+      graph.changeSize(250 + 250 * Math.random(), 250 + 250 * Math.random());
+      graph.setFitView('cc');
+    };
+
+  </script>
+</body>
+
+</html>

+ 59 - 0
demos/graphZIndex.html

@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>图-调整层级</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const nodes = [];
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const num = 500;
+    for (let index = 0; index < num; index++) {
+      nodes.push({
+        x: width * Math.random(),
+        y: height * Math.random(),
+      });
+    }
+    const data = {
+      nodes,
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      width,
+      height,
+    });
+    graph.node({
+      style: {
+        fillOpacity: 0.8
+      }
+    });
+    graph.read(data);
+    graph.on('node:mouseenter', ev => {
+      const { item } = ev;
+      graph.toFront(item);
+      graph.update(item, {
+        style: {
+          fill: 'red'
+        }
+      });
+    });
+    graph.on('node:mouseleave', ev => {
+      const { item } = ev;
+      graph.toBack(item);
+      graph.update(item, {
+        style: {
+          fill: '#1890FF'
+        }
+      });
+    });
+  </script>
+</body>
+</html>

+ 62 - 0
demos/index.njk

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <link rel="stylesheet" href="./assets/codemirror-5.29.0/codemirror-merged.min.css">
+  <link rel="stylesheet" href="./assets/bootstrap-4.1.0/bootstrap.min.css">
+  <link rel="stylesheet" href="./assets/bootstrap-4.1.0/bootstrap-grid.min.css">
+  <link rel="stylesheet" href="./assets/index.css">
+  <title>Demos</title>
+</head>
+<body>
+<div id="code-list" style="display: none">
+  {% for file in demoFiles %}
+    <textarea id="code-{{ file.basename }}">{{ file.content | safe }}</textarea>
+  {% endfor %}
+</div>
+<div class="main">
+  <div class="container filter">
+    <input id="query" class="form-control" type="text" placeholder="search">
+  </div>
+  <div class="row demo-thumbnails">
+    {% for file in demoFiles %}
+      <div class="demo-thumbnail col-md-4 col-sm-6 col-xs-12 text-center" data-basename="{{ file.basename }}">
+        <div class="card">
+          <a href="#/{{ file.basename }}" class="thumbnail-container">
+            <img src="{{ file.screenshot }}" alt="">
+          </a>
+          <div class="card-body">
+            <h6>{{ file.basename }}</h6>
+          </div>
+        </div>
+      </div>
+    {% endfor %}
+  </div>
+  <div id="doc-container" style="display:none;">
+    <div class="code-panel" id="code-panel">
+      <div class="code-banner">
+        <a class="btn btn-light" href="#">back</a>
+        <button id="execute" class="btn btn-primary">execute</button>
+        <button id="copy-code" class="btn btn-light">copy</button>
+      </div>
+      <div class="code-editor">
+        <textarea name="code" id="code"></textarea>
+      </div>
+    </div>
+    <div id="resize-handler"></div>
+    <div id="chart-panel" class="preview"></div>
+  </div>
+</div>
+<script src="./assets/codemirror-5.29.0/codemirror-merged.min.js"></script>
+<script src="./assets/lodash-4.17.4.min.js"></script>
+<script src="./assets/jquery-3.2.1.min.js"></script>
+<script src="./assets/jquery.resizable-0.20.0.js"></script>
+<script src="./assets/popper.js-1.12.5/popper.min.js"></script>
+<script src="./assets/bootstrap-4.1.0/bootstrap.min.js"></script>
+<script src="./assets/clipboard-1.7.1.min.js"></script>
+<script src="./assets/routie-0.3.2.min.js"></script>
+<script src="./assets/index.js"></script>
+</body>
+</html>

+ 81 - 0
demos/layout.html

@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>布局-综合场景</title>
+</head>
+
+<body>
+  <button id="change_layout">栅格布局</button>
+  <button id="random_layout">打散布局</button>
+  <button id="layout">整理布局</button>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/utilRandomData.js"></script>
+  <script src="../build/behaviourAnalysis.js"></script>
+  <script>
+    const Util = G6.Util;
+    const data = Util.createChainData(50);
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const num = 50;
+    let setCol = 5;
+    let node;
+
+
+    // 布局生成器
+    const layoutCreator = col => {
+      return nodes => {
+        const hgap = 76;
+        const vgap = 50;
+
+        nodes.forEach((node, index) => {
+          if (parseInt(index / col) % 2 === 0) {
+            node.x = (col - index % col) * hgap;
+          } else {
+            node.x = index % col * hgap + hgap;
+          }
+          node.y = parseInt(index / col) * vgap + vgap / 2;
+        });
+      }
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      width,
+      height,
+      animate: true,
+      layout: {
+        processer: layoutCreator(setCol),
+        auto: false
+      },
+      modes: {
+        default: ['panNode']
+      },
+    });
+    graph.read(data);
+    graph.layout();
+    document.getElementById('layout').onclick = () => {
+      graph.layout();
+    };
+    document.getElementById('change_layout').onclick = () => {
+      if (setCol === 5) {
+        setCol = 6;
+      } else {
+        setCol = 5;
+      }
+      graph.changeLayout(layoutCreator(setCol));
+    };
+    document.getElementById('random_layout').onclick = () => {
+      graph.changeLayout(nodes => {
+        nodes.forEach(node => {
+          node.x = width * Math.random();
+          node.y = height * Math.random();
+        })
+      });
+    };
+  </script>
+</body>
+</html>

+ 43 - 0
demos/layoutSimple.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>布局-简单使用</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/utilRandomData.js"></script>
+  <script>
+    const Util = G6.Util;
+    const data = Util.createChainData(50);
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      width,
+      height,
+      layout(nodes) {
+        const col = 5;
+        const hgap = 76;
+        const vgap = 50;
+
+        nodes.forEach((node, index) => {
+          if (parseInt(index / col) % 2 === 0) {
+            node.x = (col - index % col) * hgap;
+          } else {
+            node.x = index % col * hgap + hgap;
+          }
+          node.y = parseInt(index / col) * vgap + vgap / 2;
+        });
+      }
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 32 - 0
demos/pluginArchimeddeanSpiral.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>插件-布局-阿基米德螺线</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/utilRandomData.js"></script>
+  <script src="../build/layoutArchimeddeanSpiral.js"></script>
+  <script>
+    const Plugin = window.LayoutArchimeddeanSpiral;
+    const Util = G6.Util;
+    const data = Util.createChainData(160);
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      plugins: [new Plugin()]
+    });
+    graph.node({
+      size: 16
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 32 - 0
demos/pluginCircle.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>插件-布局-圆形</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/utilRandomData.js"></script>
+  <script src="../build/layoutCircle.js"></script>
+  <script>
+    const Plugin = window.LayoutCircle;
+    const Util = G6.Util;
+    const data = Util.createCyclicData(30);
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      plugins: [new Plugin()]
+    });
+    graph.node({
+      size: 16
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 185 - 0
demos/pluginMaxSpanningForest.html

@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>插件-模版-最大生成森林</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/templateMaxSpanningForest.js"></script>
+  <script>
+    const Plugin = window.TemplateMaxSpanningForest;
+    const plugin = new Plugin();
+    const data = {
+      nodes: [
+        {
+          id: 0,
+          weight: 42.194703980779714,
+          label: 'name0',
+        },
+        {
+          id: 1,
+          weight: 72.86640536738712,
+          label: 'name1',
+        },
+        {
+          id: 2,
+          weight: 82.29983433131834,
+          label: 'name2',
+        },
+        {
+          id: 3,
+          weight: 23.204885400175424,
+          label: 'name3',
+        },
+        {
+          id: 4,
+          weight: 100.84964997048472,
+          label: 'name4',
+        },
+        {
+          id: 5,
+          weight: 72.90971413062293,
+          label: 'name5',
+        },
+        {
+          id: 6,
+          weight: 15.029159176990348,
+          label: 'name6',
+        },
+        {
+          id: 7,
+          weight: 24.38308784826313,
+          label: 'name7',
+        },
+        {
+          id: 8,
+          weight: 78.00164088714241,
+          label: 'name8',
+        },
+        {
+          id: 9,
+          weight: 53.580641600279954,
+          label: 'name9',
+        },
+      ],
+      edges: [
+        {
+          id: '1-6',
+          source: 1,
+          target: 6,
+          weight: 89.69805016254719,
+        },
+        {
+          id: '5-3',
+          source: 5,
+          target: 3,
+          weight: 86.31397440928264,
+        },
+        {
+          id: '0-5',
+          source: 0,
+          target: 5,
+          weight: 76.94877138495532,
+        },
+        {
+          id: '5-9',
+          source: 5,
+          target: 9,
+          weight: 63.754902669930644,
+        },
+        {
+          id: '2-7',
+          source: 2,
+          target: 7,
+          weight: 4.449707271000913,
+        },
+        {
+          id: '3-4',
+          source: 3,
+          target: 4,
+          weight: 36.97483959651564,
+        },
+        {
+          id: '9-3',
+          source: 9,
+          target: 3,
+          weight: 50.354719513581635,
+        },
+        {
+          id: '6-4',
+          source: 6,
+          target: 4,
+          weight: 10.02660118138856,
+        },
+        {
+          id: '8-3',
+          source: 8,
+          target: 3,
+          weight: 13,
+        },
+        {
+          id: '8-4',
+          source: 8,
+          target: 4,
+          weight: 13,
+        },
+        {
+          id: '2-1',
+          source: 2,
+          target: 1,
+          weight: 13,
+        },
+        {
+          id: '4-5',
+          source: 4,
+          target: 5,
+          weight: 60,
+        },
+        {
+          id: '4-8',
+          source: 4,
+          target: 8,
+          weight: 63,
+        },
+        {
+          id: '8-5',
+          source: 8,
+          target: 5,
+          weight: 13,
+        },
+        {
+          id: '9-2',
+          source: 9,
+          target: 2,
+          weight: 13,
+        },
+        {
+          id: '1-8',
+          source: 1,
+          target: 8,
+          weight: 19.02660118138856,
+        },
+        {
+          id: '4-2',
+          source: 4,
+          target: 2,
+          weight: 50.02660118138856,
+        },
+      ],
+    };
+    const graph = new G6.Graph({
+      id: 'mountNode',             // dom id
+      fitView: 'cc',
+      height: window.innerHeight,
+      plugins: [plugin],
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 63 - 0
demos/pluginMinimap.html

@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>插件-缩略图</title>
+</head>
+
+<body>
+  <div style="position: relative;">
+    <div id="mountNode"></div>
+    <div id="minimap" style="border: 1px solid #999; position: absolute; top: 0px;"></div>
+  </div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/toolMinimap.js"></script>
+  <script>
+    const Plugin = window.ToolMinimap;
+    const plugin = new Plugin({
+      container: 'minimap',
+      width: 180,
+      height: 120
+    });
+    const data = {
+      nodes: [{
+        id: 'node0',
+        x: -100,
+        y: 200
+      }, {
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }, {
+        id: 'node3',
+        x: 600,
+        y: 200
+      }],
+      edges: [{
+        target: 'node0',
+        source: 'node1'
+      }, {
+        target: 'node2',
+        source: 'node1'
+      }, {
+        target: 'node2',
+        source: 'node3'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      plugins: [plugin]
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 49 - 0
demos/pluginQuadraticCurve.html

@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>插件-二阶贝塞尔曲线</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script src="../build/edgeQuadraticCurve.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        target: 'node2',
+        source: 'node1',
+        shape: 'quadraticCurve'
+      }, {
+        target: 'node1',
+        source: 'node2',
+        shape: 'quadraticCurve'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+    });
+    graph.edge({
+      style: {
+        endArrow: true
+      }
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 38 - 0
demos/quickNet.html

@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>快速上手-网图</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      width: 500,
+      height: 500
+    });
+    graph.read(data);
+  </script>
+</body>
+</html>

+ 39 - 0
demos/quickTree.html

@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>快速上手-树图</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      roots: [{
+        label: 'root',
+        children: [{
+          label: 'child1',
+          children: [
+            {
+              label: 'child\n1.1'
+            }
+          ]
+        }, {
+          label: 'child2'
+        }]
+      }]
+    };
+    const tree = new G6.Tree({
+      container: 'mountNode',
+      width: 500,
+      height: 500,
+      fitView: 'cc'
+    });
+    tree.read(data);
+  </script>
+</body>
+</html>

+ 100 - 0
demos/tree-compact-box.html

@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <title>紧凑树</title>
+  <style>
+    ::-webkit-scrollbar {
+      display: none;
+    }
+
+    html,
+    body {
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script type="text/javascript">
+    G6.registerNode('treeNode', {
+      anchor: [
+        [ 0, 0.5 ],
+        [ 1, 0.5 ]
+      ]
+    });
+    G6.registerEdge('smooth', {
+      getPath(item) {
+        const points = item.getPoints();
+        const start = points[0];
+        const end = points[points.length - 1];
+        const hgap = Math.abs(end.x - start.x);
+        if (end.x > start.x) {
+          return [
+            [ 'M', start.x, start.y ],
+            [ 'C', start.x + hgap / 4, start.y, end.x - hgap / 2, end.y, end.x, end.y ]
+          ];
+        }
+        return [
+          [ 'M', start.x, start.y ],
+          [ 'C', start.x - hgap / 4, start.y, end.x + hgap / 2, end.y, end.x, end.y ]
+        ];
+      }
+    });
+
+    $.getJSON('./assets/data/modeling-methods.json', function (data) {
+      var layout = new G6.Layouts.CompactBoxTree({
+        // direction: 'LR', // 方向(LR/RL/H/TB/BT/V)
+        getHGap: function getHGap() /* d */ {
+          // 横向间距
+          return 100;
+        },
+        getVGap: function getVGap() /* d */ {
+          // 竖向间距
+          return 10;
+        }
+      })
+      var tree = new G6.Tree({
+        id: 'mountNode', // 容器ID
+        height: window.innerHeight, // 画布高
+        layout,
+        fitView: 'autoZoom' // 自动缩放
+      });
+      tree.node({
+        shape: 'treeNode',
+        size: 8
+      }).label(function (obj) {
+        return obj.name;
+      });
+      tree.edge({
+        shape: 'smooth'
+      });
+      tree.on('afterchange', ()=>{
+        tree.getNodes().forEach(node=>{
+          const model = node.getModel();
+          const label = node.getLabel();
+          const keyShape = node.getKeyShape();
+          const children = node.getChildren();
+          const parent = node.getParent();
+          const box = keyShape.getBBox();
+          const labelBox = label.getBBox();
+          let dx = (box.maxX - box.minX + labelBox.maxX - labelBox.minX) / 2+ 8;
+          let dy = 0;
+          if (children.length != 0) {
+            dx = -dx;
+          }
+          label.translate(dx, dy);
+        });
+        tree.draw();
+      });
+      tree.read({ roots: [data] });
+    });
+  </script>
+</body>
+
+</html>

+ 105 - 0
demos/tree-dendrogram.html

@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <title>紧凑树</title>
+  <style>
+    ::-webkit-scrollbar {
+      display: none;
+    }
+
+    html,
+    body {
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script type="text/javascript">
+    G6.registerNode('treeNode', {
+      anchor: [
+        [ 0, 0.5 ],
+        [ 1, 0.5 ]
+      ]
+    });
+    G6.registerEdge('smooth', {
+      getPath(item) {
+        const points = item.getPoints();
+        const start = points[0];
+        const end = points[points.length - 1];
+        const hgap = Math.abs(end.x - start.x);
+        if (end.x > start.x) {
+          return [
+            [ 'M', start.x, start.y ],
+            [ 'C', start.x + hgap / 4, start.y, end.x - hgap / 2, end.y, end.x, end.y ]
+          ];
+        }
+        return [
+          [ 'M', start.x, start.y ],
+          [ 'C', start.x - hgap / 4, start.y, end.x + hgap / 2, end.y, end.x, end.y ]
+        ];
+      }
+    });
+
+    $.getJSON('./assets/data/modeling-methods.json', function (data) {
+      var Util = G6.Util;
+      // 准备布局配置
+      var layoutCfg = {
+        "direction": "LR",
+        "nodeSize": 20,
+        "rankSep": 400
+      };
+      // 自定义树节点
+      var DEFAULT_NODE_SIZE = layoutCfg.nodeSize;
+      // 生成树图实例
+      var layout = new G6.Layouts.Dendrogram(layoutCfg)
+      var tree = new G6.Tree({
+        id: 'mountNode',
+        layout,
+        height: window.innerHeight, // 画布高
+        fitView: 'autoZoom', // 自动缩放
+        showButton: false
+      });
+
+      tree.node({
+        shape: 'treeNode',
+        size: 8
+      }).label(function (obj) {
+        return obj.name;
+      });
+      tree.edge({
+        shape: 'smooth'
+      });
+      tree.on('afterchange', ()=>{
+        tree.getNodes().forEach(node=>{
+          const model = node.getModel();
+          const label = node.getLabel();
+          const keyShape = node.getKeyShape();
+          const children = node.getChildren();
+          const parent = node.getParent();
+          const box = keyShape.getBBox();
+          const labelBox = label.getBBox();
+          let dx = (box.maxX - box.minX + labelBox.maxX - labelBox.minX) / 2+ 8;
+          let dy = 0;
+          if (children.length != 0) {
+            dx = -dx;
+          }
+          label.translate(dx, dy);
+        });
+        tree.draw();
+      });
+
+      // 加载数据
+      // 渲染树图
+      tree.read({ roots: [data] });
+    });
+  </script>
+</body>
+
+</html>

+ 89 - 0
demos/tree-indented.html

@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <title>紧凑树</title>
+  <style>
+    ::-webkit-scrollbar {
+      display: none;
+    }
+
+    html,
+    body {
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script type="text/javascript">
+    G6.registerNode('treeNode', {
+      anchor: [
+        [ 0, 0.5 ],
+        [ 0.5, 1 ]
+      ]
+    });
+    G6.registerEdge('VH', {
+      getPath(item) {
+        const points = item.getPoints();
+        const start = points[0];
+        const end = points[points.length - 1];
+        return [
+          [ 'M', start.x, start.y ],
+          [ 'L', start.x, end.y ],
+          [ 'L', end.x, end.y ]
+        ];
+      }
+    });
+
+    $.getJSON('./assets/data/modeling-methods.json', function (data) {
+      var layout = new G6.Layouts.IndentedTree({
+        direction: 'LR', // 方向(LR/RL/H)
+        indent: 30, // 缩进量
+        getVGap: function getVGap() /* d */ {
+          // 竖向间距
+          return 4;
+        }
+      })
+      var tree = new G6.Tree({
+        id: 'mountNode', // 容器ID
+        height: window.innerHeight, // 画布高
+        layout, // 缩进树布局
+        fitView: 'autoZoom' // 自动缩放
+      });
+      tree.node({
+        shape: 'treeNode',
+        size: 16
+      }).label(function (obj) {
+        return obj.name;
+      });
+      tree.edge({
+        shape: 'VH'
+      });
+      tree.on('afterchange', ()=>{
+        tree.getNodes().forEach(node=>{
+          const model = node.getModel();
+          const label = node.getLabel();
+          const keyShape = node.getKeyShape();
+          const children = node.getChildren();
+          const parent = node.getParent();
+          const box = keyShape.getBBox();
+          const labelBox = label.getBBox();
+          let dx = (box.maxX - box.minX + labelBox.maxX - labelBox.minX) / 2+ 8;
+          let dy = 0;
+          label.translate(dx, dy);
+        });
+        tree.draw();
+      });
+
+      tree.read({ roots: [data] });
+    });
+  </script>
+</body>
+
+</html>

+ 112 - 0
demos/tree-mindmap.html

@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <title>紧凑树</title>
+  <style>
+    ::-webkit-scrollbar {
+      display: none;
+    }
+
+    html,
+    body {
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
+  <script src="./assets/jquery-3.2.1.min.js"></script>
+  <script src="../build/g6.js"></script>
+  <script type="text/javascript">
+    const Util = G6.Util;
+    // 注册脑图节点
+    G6.registerNode('mindNode', {
+      anchor: [
+        [ 0, 0.5 ],
+        [ 1, 0.5 ]
+      ]
+    });
+    // 注册脑图边
+    G6.registerEdge('mindEdge', {
+      getPath(item) {
+        const points = item.getPoints();
+        const start = points[0];
+        const end = points[points.length - 1];
+        const hgap = Math.abs(end.x - start.x);
+        if (end.x > start.x) {
+          return [
+            [ 'M', start.x, start.y ],
+            [ 'C', start.x + hgap / 4, start.y, end.x - hgap / 2, end.y, end.x, end.y ]
+          ];
+        }
+        return [
+          [ 'M', start.x, start.y ],
+          [ 'C', start.x - hgap / 4, start.y, end.x + hgap / 2, end.y, end.x, end.y ]
+        ];
+      }
+    });
+    $.getJSON('./assets/data/modeling-methods.json', function (data) {
+      var layout = new G6.Layouts.Mindmap({
+        direction: 'H', // 方向(LR/RL/H/TB/BT/V)
+        getHGap: function getHGap() /* d */ {
+          // 横向间距
+          return 100;
+        },
+        getVGap: function getVGap() /* d */ {
+          // 竖向间距
+          return 10;
+        }
+      });
+      var tree = new G6.Tree({
+        id: 'mountNode', // 容器ID
+        height: window.innerHeight, // 画布高
+        fitView: 'autoZoom', // 自动缩放
+        layout,
+      });
+      tree.node({
+        label(model) {
+          return {
+            text: model.name,
+            stroke: '#fff',
+            lineWidth: 3
+          };
+        },
+        size: 8,
+        shape: 'mindNode'
+      });
+      tree.edge({
+        shape: 'mindEdge'
+      });
+      tree.on('afterchange', ()=>{
+        tree.getNodes().forEach(node=>{
+          const model = node.getModel();
+          const label = node.getLabel();
+          const keyShape = node.getKeyShape();
+          const parent = node.getParent();
+          const box = keyShape.getBBox();
+          const labelBox = label.getBBox();
+          let dx = (box.maxX - box.minX + labelBox.maxX - labelBox.minX) / 2+ 8;
+          let dy = (box.maxY - box.minY) / 2 + 8;
+          if(parent){
+            const parentModel = parent.getModel();
+            if(parentModel.x > model.x){
+              dx = -dx;
+            }
+            dy = 0;
+          } else {
+            dx = 0;
+          }
+          label.translate(dx, dy);
+        });
+        tree.draw();
+      });
+      tree.read({ roots: [data] });
+    });
+  </script>
+</body>
+
+</html>

+ 58 - 0
demos/viewportFitView.html

@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>视口-自适应</title>
+</head>
+
+<body>
+  适应视口:
+  <button class="fitView">tl</button>
+  <button class="fitView">tc</button>
+  <button class="fitView">tr</button>
+  <button class="fitView">cc</button>
+  <button class="fitView">lc</button>
+  <button class="fitView">rc</button>
+  <button class="fitView">bl</button>
+  <button class="fitView">bc</button>
+  <button class="fitView">br</button>
+  <button class="fitView">autoZoom</button>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      fitView: 'cc'
+    });
+    graph.read(data);
+    const buttons = document.getElementsByClassName('fitView');
+    for (let index = 0; index < buttons.length; index++) {
+      const button = buttons[index];
+      button.onclick = () => {
+        graph.setFitView(button.innerHTML);
+      };
+    }
+  </script>
+</body>
+</html>

+ 51 - 0
demos/viewportFocus.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>视口-聚焦</title>
+</head>
+
+<body>
+  <button id="focus_node1">聚焦节点1</button>
+  <button id="focus_node2">聚焦节点2</button>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200,
+        label: '节点1',
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200,
+        label: '节点2',
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+      fitView: 'cc'
+    });
+    graph.read(data);
+
+    document.getElementById('focus_node1').onclick = () => {
+      graph.focus('node1');
+    };
+    document.getElementById('focus_node2').onclick = () => {
+      graph.focus('node2');
+    };
+  </script>
+</body>
+</html>

+ 52 - 0
demos/viewportTranslate.html

@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>视口-平移</title>
+</head>
+
+<body>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+    });
+    graph.read(data);
+    let lastPoint;
+    graph.on('drag', ev => {
+      if (lastPoint) {
+        graph.translate(ev.domX - lastPoint.x, ev.domY - lastPoint.y);
+      }
+      lastPoint = {
+        x: ev.domX,
+        y: ev.domY
+      };
+    });
+    graph.on('dragend', ev => {
+      lastPoint = undefined;
+    });
+  </script>
+</body>
+</html>

+ 52 - 0
demos/viewportZoom.html

@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>视口-缩放</title>
+</head>
+
+<body>
+  <button class="zoom" data-ratio="0.2">20%</button>
+  <button class="zoom" data-ratio="0.5">50%</button>
+  <button class="zoom" data-ratio="1">100%</button>
+  <button class="zoom" data-ratio="1.5">150%</button>
+  <button class="zoom" data-ratio="2">200%</button>
+  <div id="mountNode"></div>
+  <script src="../build/g6.js"></script>
+  <script>
+    const data = {
+      nodes: [{
+        id: 'node1',
+        x: 100,
+        y: 200
+      }, {
+        id: 'node2',
+        x: 300,
+        y: 200
+      }],
+      edges: [{
+        id: 'edge1',
+        target: 'node2',
+        source: 'node1'
+      }]
+    };
+    const graph = new G6.Graph({
+      container: 'mountNode',
+      fitView: 'cc',
+      height: window.innerHeight,
+    });
+    graph.read(data);
+
+    const buttons = document.getElementsByClassName('zoom');
+    for (let index = 0; index < buttons.length; index++) {
+      const button = buttons[index];
+      button.onclick = () => {
+        graph.zoom(Number(button.dataset.ratio));
+      };
+    }
+  </script>
+</body>
+</html>

+ 127 - 0
package.json

@@ -0,0 +1,127 @@
+{
+  "name": "@antv/g6",
+  "version": "2.0.0",
+  "description": "graph visualization frame work, graph editor, graph analyser",
+  "main": "lib/index.js",
+  "browser": "build/g6.js",
+  "module": "src/g6.js",
+  "homepage": "https://github.com/antvis/g6",
+  "author": "https://github.com/orgs/antvis/people",
+  "repository": {
+    "type": "git",
+    "url": "git@github.com:antvis/g6.git"
+  },
+  "bugs": {
+    "url": "https://github.com/antvis/g6/issues"
+  },
+  "keywords": [
+    "g6",
+    "relational data",
+    "graph visualization",
+    "graph editor",
+    "graph analysis"
+  ],
+  "contributors": [
+    {
+      "email": "leanne06204@163.com",
+      "name": "夏柘",
+      "url": "https://github.com/AceLeeWinnie"
+    },
+    {
+      "email": "wyueliu@gmail.com",
+      "name": "敏岳",
+      "url": "https://github.com/liuwuyue"
+    },
+    {
+      "email": "deggs.k@gmail.com",
+      "name": "大知",
+      "url": "https://github.com/deggs7"
+    }
+  ],
+  "license": "MIT",
+  "devDependencies": {
+    "babel-cli": "~6.26.0",
+    "babel-core": "~6.26.0",
+    "babel-eslint": "~8.0.3",
+    "babel-loader": "~7.1.4",
+    "babel-plugin-transform-object-rest-spread": "^6.26.0",
+    "babel-plugin-transform-remove-strict-mode": "~0.0.2",
+    "babel-preset-env": "~1.6.1",
+    "body-parser": "~1.18.2",
+    "chai": "~4.1.2",
+    "clipboard": "^1.5.15",
+    "codemirror": "*",
+    "commander": "~2.12.2",
+    "connect": "~3.6.6",
+    "d3-queue": "~3.0.7",
+    "debug": "~3.1.0",
+    "electron": "~2.0.2",
+    "eslint": "~3.19.0",
+    "eslint-config-airbnb": "~15.0.1",
+    "eslint-config-egg": "~4.2.0",
+    "eslint-plugin-html": "~3.1.1",
+    "eslint-plugin-jsx-a11y": "~5.1.1",
+    "eslint-plugin-react": "~7.1.0",
+    "event-simulate": "~1.0.0",
+    "get-port": "~3.2.0",
+    "home": "~1.0.1",
+    "html2canvas": "*",
+    "jquery": "~2.1.4",
+    "jszip": "~3.1.5",
+    "nightmare": "~2.10.0",
+    "nunjucks": "~3.0.1",
+    "open": "~0.0.5",
+    "parseurl": "~1.3.2",
+    "pre-commit": "~1.2.2",
+    "radixsort": "~1.0.0",
+    "serve-static": "~1.13.2",
+    "shelljs": "~0.7.8",
+    "string-replace-loader": "~1.3.0",
+    "torchjs": "~2.0.3",
+    "uglify-js": "~3.1.10",
+    "webpack": "~4.10.2",
+    "webpack-cli": "~3.0.0"
+  },
+  "scripts": {
+    "build": "webpack",
+    "build-lib": "babel src --out-dir lib",
+    "ci": "npm run lint && npm run test",
+    "copy": "node ./bin/cp-dist.js",
+    "coverage": "npm run coverage-generator && npm run coverage-viewer",
+    "coverage-generator": "torch --compile --coverage --renderer --recursive --source-pattern index.js,src/**/*.js test/unit",
+    "coverage-viewer": "torch-coverage",
+    "demos": "electron ./demos/app.js",
+    "demos-web": "node ./demos/app.js --web --port 2046",
+    "dev": "npm run watch & npm run demos-web",
+    "dist": "npm run mkdir-dist && npm run build && npm run copy",
+    "lint": "eslint --ext .js ./src",
+    "lint-fix": "eslint --ext .html,.js --fix ./",
+    "mkdir-dist": "node ./bin/mkdir-dist.js",
+    "prepublishOnly": "npm run build-lib && npm run dist",
+    "screenshot": "node ./bin/screenshot.js",
+    "start": "npm run dev",
+    "test": "torch --compile --renderer --recursive ./test/unit",
+    "test-all": "npm run test && npm run test-cases",
+    "test-cases": "torch --compile --renderer --recursive ./test/cases",
+    "test-cases-live": "torch --compile --interactive --watch --recursive ./test/cases",
+    "test-live": "torch --compile --interactive --watch --recursive ./test/unit",
+    "watch": "webpack --config webpack-dev.config.js",
+    "win-dev": "node ./bin/win-dev.js"
+  },
+  "pre-commit": {
+    "run": []
+  },
+  "dependencies": {
+    "@antv/g": "~2.1.0",
+    "@antv/hierarchy": "~0.3.13",
+    "ant-design-palettes": "~1.1.2",
+    "d3": "^5.4.0",
+    "d3-svg-legend": "^2.25.6",
+    "dagre": "~0.8.2",
+    "lodash": "~4.17.4",
+    "wolfy87-eventemitter": "~5.2.4"
+  },
+  "engines": {
+    "node": ">=8.9.0"
+  }
+}

+ 0 - 0
plugins/behaviour.analysis/index.js


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff