Compare commits

...

1218 Commits

Author SHA1 Message Date
Бородин Роман c3c3031f37 listen 0.0.0.0 2023-10-01 14:20:36 +03:00
Бородин Роман a7b9baaaaf . 2023-07-31 13:27:08 +03:00
Бородин Роман 593d9241be -i устарела 2023-07-31 13:24:46 +03:00
Бородин Роман 35ac5a50d6 oauth 2023-07-31 12:46:18 +03:00
Liang Ding f6a72ff464
🎨 加入只读模式配置 2022-08-09 10:11:53 +08:00
Liang Ding 24d4654030
🎨 加入只读模式配置 2022-08-09 10:09:38 +08:00
Liang Ding 58611c8f1e
🎨 加入只读模式配置 2022-08-09 10:05:05 +08:00
Liang Ding 9076912b9d
🎨 加入只读模式配置 2022-08-09 10:01:55 +08:00
Liang Ding 68fee17793
🎨 加入只读模式配置 2022-08-09 09:56:45 +08:00
Liang Ding 877e96df4d
🎨 加入只读模式配置 2022-08-09 09:43:46 +08:00
D 029982f00e
Merge pull request #14 from 88250/dependabot/npm_and_yarn/yargs-parser-5.0.1
⬆️ Bump yargs-parser from 5.0.0 to 5.0.1
2021-04-11 23:52:23 +08:00
dependabot[bot] 5cfb9ca5fb
⬆️ Bump yargs-parser from 5.0.0 to 5.0.1
Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/yargs/yargs-parser/releases)
- [Changelog](https://github.com/yargs/yargs-parser/blob/v5.0.1/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs-parser/compare/v5.0.0...v5.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-11 14:43:47 +00:00
Liang Ding 93654cfa32
🎨 社区域名迁移 2021-04-11 22:42:50 +08:00
Liang Ding de0110eadf
🎨 Docker 容器限制 CPU 和内存 2021-03-05 20:11:01 +08:00
Liang Ding 2e53a02446
🎨 Docker 容器限制 CPU 和内存 2021-03-05 20:09:37 +08:00
Liang Ding 3a4aabc297
🎨 链滴 2021-03-05 20:02:08 +08:00
Liang Ding 9b8c3eb888
🎨 Docker 容器限制 CPU 和内存 2021-03-05 20:00:09 +08:00
Liang Ding 7f9f726f71
🎨 社区域名迁移 2020-10-13 21:56:20 +08:00
Liang Ding a0ec5a70b7
Merge remote-tracking branch 'origin/master' 2020-06-10 12:19:20 +08:00
Liang Ding 51a3f6b932
🔒 修复登录验证安全漏洞 Fix #9 2020-06-10 12:18:53 +08:00
D 791f438ff9
Merge pull request #4 from 88250/dependabot/npm_and_yarn/acorn-5.7.4
⬆️ Bump acorn from 5.7.3 to 5.7.4
2020-05-17 10:37:12 +08:00
Liang Ding e3c3b165c8
🎨 #7 2020-04-19 13:06:57 +08:00
Liang Ding 3256f60990
🎨 go1.13+ 自动完成失效问题 Fix #6 2020-04-09 13:15:50 +08:00
Liang Ding e51b78ea85
⬆️ 升级依赖 2020-04-09 13:14:08 +08:00
Liang Ding 3bf430e35f
🎨 go1.13+ 自动完成失效问题 #6 2020-04-09 11:58:50 +08:00
dependabot[bot] 12c5da94f6
⬆️ Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-06 15:26:50 +00:00
Liang Ding cd9d0165c0
⬆️ 更新依赖 2020-01-24 18:28:07 +08:00
Liang Ding 231a35c725
🎨 关闭账号迁移公告 2020-01-22 10:30:15 +08:00
Liang Ding dbd41730ae
🎨 Fix #3 2020-01-18 17:17:15 +08:00
Liang Ding c84ead6140
🎨 #3 2020-01-18 17:16:12 +08:00
Liang Ding 786a0f5c4a
🎨 #3 2020-01-18 17:14:08 +08:00
Liang Ding 624ef3a152
🎨 #3 2020-01-18 17:04:37 +08:00
Liang Ding 1287018811
🎨 #3 2020-01-18 17:02:56 +08:00
Liang Ding 3193610b7f
🎨 #3 2020-01-18 16:57:57 +08:00
Liang Ding c065239e69
🎨 #3 2020-01-18 16:54:18 +08:00
Liang Ding 7d222f22cb
🎨 #3 2020-01-18 16:42:56 +08:00
Liang Ding ab4feaba81
🎨 Docker 环境输出延时问题 2020-01-18 15:40:52 +08:00
Liang Ding aa65a8db04
🎨 Docker 环境输出延时问题 2020-01-18 14:38:56 +08:00
Liang Ding e4f0e410b8
⬆️ 更新依赖 2020-01-18 14:18:08 +08:00
Van 0568356fdc
login ui 2019-12-14 17:10:40 +08:00
Liang Ding 215820f02b
🎨 #2 2019-12-14 16:32:28 +08:00
Liang Ding c3b7c26119
🎨 #2 2019-12-13 20:54:55 +08:00
Liang Ding 89e3b7be8f
🎨 #2 2019-12-13 20:50:35 +08:00
Liang Ding 358067ea10
🎨 #2 2019-12-13 20:29:55 +08:00
Liang Ding bd54c4ef10
🎨 Fix #1 2019-12-11 11:30:38 +08:00
Liang Ding 47374dd8c8
🎨 重新导入 2019-12-01 20:21:00 +08:00
Liang Ding e84f994f2b
📝 更新说明 2019-10-07 20:18:59 +08:00
Liang Ding 90303f2aed
📝 更新说明 2019-09-23 22:32:07 +08:00
Liang Ding f633ca07eb
📝 更新说明 2019-09-23 22:30:41 +08:00
Liang Ding a6f9de5a62
⬆️ 更新依赖 2019-08-06 12:04:27 +08:00
Van d26f35bc5f
⬆️ lodash.template 2019-07-18 09:00:38 +08:00
Van 5f0612530a
❇️ add sponsor 2019-07-17 18:36:28 +08:00
Van e281d0f554
❇️ add sponsor 2019-07-17 18:35:00 +08:00
Van dd949ae6a3
add sponsor 2019-07-17 18:15:59 +08:00
Liang Ding 6d8a334131
Merge branch '1.7.0-dev' 2019-07-07 18:23:05 +08:00
Liang Ding 3cf3063e76
:octocat: 关注项目并邀请加入 B3 开源组织 2019-07-07 18:22:54 +08:00
Van fbb824de6e
min css -> clear css 2019-06-23 15:27:15 +08:00
Van ef52c48b26
fix #367 2019-06-23 14:45:20 +08:00
Van 1c295785d0
rm log 2019-06-23 14:14:37 +08:00
D b57889bdac
Merge pull request #366 from b3log/dependabot/npm_and_yarn/braces-2.3.2
⬆️ Bump braces from 1.8.5 to 2.3.2
2019-06-07 19:23:30 +08:00
dependabot[bot] 9d0113f979
⬆️ Bump braces from 1.8.5 to 2.3.2
Bumps [braces](https://github.com/micromatch/braces) from 1.8.5 to 2.3.2.
- [Release notes](https://github.com/micromatch/braces/releases)
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-07 11:18:18 +00:00
Van 7a254f89d8
Upgrade concat-with-sourcemaps to version 1.0.6 or later 2019-05-30 16:41:02 +08:00
Liang Ding 544cb886ec
:octocat: 使用组织库提供的模板 2019-05-30 09:58:27 +08:00
Liang Ding cdc1f5408b
🎨 交叉编译支持 2019-05-29 18:48:08 +08:00
Liang Ding f6bdc5f176
🎨 更新依赖 2019-05-29 18:47:48 +08:00
Liang Ding d3ab996830
🎨 使用组织库默认仓库配置 2019-05-25 23:19:16 +08:00
Liang Ding bae3ad8c20
:octocat: 加入社区协作相关文档 2019-05-25 22:54:20 +08:00
Liang Ding 7dafee49e9
💝 加入赞助链接 2019-05-25 22:53:58 +08:00
Liang Ding efce7adc51
📝 更新徽标 2019-05-25 22:05:46 +08:00
Liang Ding d474e913f6
💚 移除测试覆盖率 2019-05-25 00:07:32 +08:00
Liang Ding c5fdd3f0a6
📝 更新 README 2019-05-24 23:46:30 +08:00
Liang Ding 6dc68d4aa5
:octocat: 主要语言确实是 js 😂 2019-05-24 23:40:49 +08:00
Liang Ding c0c00bf9c8
:octocat: 测试忽略 js 语言 2019-05-24 23:39:00 +08:00
Liang Ding 4f9221c313
:octocat: 更新项目主要语言 2019-05-24 23:35:06 +08:00
Liang Ding f3e9502443
:octocat: 忽略 min 压缩 js 2019-05-24 23:30:53 +08:00
Liang Ding 00439ae418
:octocat: 改变项目主要语言为 Go 2019-05-24 23:28:58 +08:00
Liang Ding c931e611db
🔥 删除单元测试临时目录 2019-05-24 22:36:20 +08:00
Liang Ding b53d2d28b7
♻️ 重构返回值状态码 2019-05-24 21:52:17 +08:00
Liang Ding 83d52b107a
📝 引入 Gulu 工具库 2019-05-24 21:06:00 +08:00
Liang Ding 4930408b17
♻️ 引入 Gulu 工具库 2019-05-24 21:04:25 +08:00
Liang Ding 1405d7482a
💬 删除 README 中的废话 2019-05-24 16:16:32 +08:00
Liang Ding 0253ad8e45
✏️ 修复笔误 2019-05-24 11:54:00 +08:00
Liang Ding 4da07183a9
🔖 更新版本号 2019-05-24 11:53:30 +08:00
Liang Ding 1eb2a78c3b
🔧 默认生产模式 2019-05-24 11:45:17 +08:00
Liang Ding 6bd4efd16f
🔧 默认生成模式 2019-05-24 11:44:58 +08:00
Liang Ding 17e59cf55a
🎨 判断目录 2019-05-24 11:43:26 +08:00
Liang Ding 879b3d78c3
✏️ Fix typos 2019-05-24 11:42:52 +08:00
Liang Ding ce8351f38c
🎨 #363 2019-05-24 11:36:42 +08:00
Liang Ding 58ae60da85
🎨 #363 2019-05-24 08:43:07 +08:00
Liang Ding 5c549a56c5
🎨 #363 2019-05-24 08:41:34 +08:00
Liang Ding 38f88c2c17
🎨 Fix #362 2019-05-23 18:42:59 +08:00
Liang Ding 1857df995d
#362 2019-05-23 18:28:11 +08:00
Liang Ding 67c36e0089
🎨 更新 Playground 示例 2019-05-23 15:51:56 +08:00
Liang Ding d764f13192
📝 更新 Hello World 示例 2019-05-23 15:30:38 +08:00
Liang Ding 9716c3027f
🎨 Fix #360 2019-05-23 11:46:01 +08:00
Liang Ding e2c0b8ec8a
🎨 #360 2019-05-23 11:26:33 +08:00
Liang Ding 2e0f150cdb
🎨 Fix #361 2019-05-23 11:22:42 +08:00
Liang Ding 4a28d10a9d
🎨 #306 goproxy 2019-05-20 17:59:10 +08:00
Liang Ding eb896e3db3
🎨 #306 GO111MODULE=on 2019-05-20 17:56:58 +08:00
Liang Ding d3f522acd9
#306 支持 go mod 2019-05-20 17:36:16 +08:00
Liang Ding 00051a9e39
🎨 #306 go get -> go mod 2019-05-20 17:12:13 +08:00
Liang Ding b1194b4763
🔧 移除 CPU 核数设置 2019-05-19 21:03:32 +08:00
Liang Ding ffd99ff8bd
🌐 默认使用中文 2019-05-19 20:47:13 +08:00
Liang Ding c7372868e4
🌐 更新 title 2019-05-19 20:42:25 +08:00
Van c86f0daa94
🐛 playground output 2019-05-19 18:58:33 +08:00
Liang Ding 03cbb8fce4
🎨 升级公告 2019-05-19 15:37:37 +08:00
Liang Ding 763e140775
🎨 进程运行结束信号 2019-05-19 11:01:36 +08:00
Liang Ding 2b50910784
📦 打包 2019-05-19 01:54:05 +08:00
Liang Ding 72a7281513
🎨 playground 输出 2019-05-19 01:53:31 +08:00
Liang Ding 7576910f43
🐳 限制 CPU 使用 2019-05-19 01:29:27 +08:00
Liang Ding ff3546a844
🎨 kill 命令日志 2019-05-19 01:25:41 +08:00
Liang Ding b84338bb87
♻️ 重构进程管理 2019-05-19 01:20:47 +08:00
Liang Ding f8d26e0c57
🎨 通过 docker rm 回收容器 2019-05-19 01:03:33 +08:00
Liang Ding 02a809803e
🐳 通过 docker rm 回收容器 2019-05-19 00:59:10 +08:00
Liang Ding 9a16c9214f
🎨 不通过 timeout 命令控制执行超时 2019-05-19 00:45:49 +08:00
Liang Ding e952c8fa09
🐳 执行超时控制 2019-05-18 23:38:49 +08:00
Van a236fc195c
Merge remote-tracking branch 'origin/master' 2019-05-17 21:37:30 +08:00
Van 1787db9091
:pack: 2019-05-17 21:37:18 +08:00
Liang Ding 802178cbf3
🐳 仅限制 CPU 2019-05-17 18:27:39 +08:00
Liang Ding 43a3ffe2a0
🐳 限制 CPU、内存使用 2019-05-17 18:20:36 +08:00
Liang Ding 04a2c8ed2f
🎨 登录验证 2019-05-17 16:35:47 +08:00
Liang Ding 1d802f8717
🚚 重命名文件 2019-05-17 16:30:39 +08:00
Liang Ding e230dfdb2d
🎨 登录跳转 2019-05-17 14:09:19 +08:00
Van fb124a3a5b
🐛 2019-05-17 13:50:15 +08:00
Liang Ding 76dd5cacba
🎨 登录页 2019-05-17 13:26:02 +08:00
Liang Ding 4a5f8a1314
🎨 社区地址 2019-05-17 13:22:34 +08:00
Liang Ding 8341881400
🔥 删掉短网址功能 2019-05-17 13:18:33 +08:00
Liang Ding f764d61323
🎨 沙盒短网址 2019-05-17 13:14:46 +08:00
Liang Ding f32e0a21d3
🎨 ws 协议 2019-05-17 13:04:56 +08:00
Liang Ding 0d5f5f80f8
🎨 文件目录 2019-05-17 13:00:59 +08:00
Liang Ding 4760796915
🎨 监听端口 2019-05-17 12:42:16 +08:00
Liang Ding 58de20eb86
🔧 默认定时打印运行报告 2019-05-17 12:37:10 +08:00
Liang Ding 7d7e51e8a6
🎨 登录页 2019-05-17 12:20:38 +08:00
Liang Ding ad6df5b1b4
🎨 #347 2019-05-17 12:17:58 +08:00
Van 4b6f6af949
Merge remote-tracking branch 'origin/master' 2019-05-17 12:02:47 +08:00
Van 6bdd404469
🎨 login 2019-05-17 12:02:35 +08:00
Liang Ding 72e46e3e52
🎨 登录重定向 2019-05-17 11:59:57 +08:00
Liang Ding 29d4d93019
🔇 减少日志 2019-05-17 11:57:15 +08:00
Liang Ding 359ce01227
📝 更新 README 2019-05-17 11:45:19 +08:00
Liang Ding beb58995af
Merge branch 'master' of https://github.com/b3log/wide 2019-05-17 11:41:30 +08:00
Liang Ding 7e62a1db3f
📝 更新 README 2019-05-17 11:41:26 +08:00
Liang Ding 8c520527ce
📝 更新 README 2019-05-17 11:40:12 +08:00
Van bc1f8a5853
Merge remote-tracking branch 'origin/master' 2019-05-17 11:29:27 +08:00
Van ade8fa8892
🎨 login 2019-05-17 11:29:15 +08:00
Liang Ding ec22992396
📄 更新文件头 2019-05-17 11:28:50 +08:00
Liang Ding d0396da439
🎨 #347 2019-05-17 11:25:44 +08:00
Liang Ding 8b0b1503c3
Merge branch 'master' of https://github.com/b3log/wide 2019-05-17 10:39:21 +08:00
Liang Ding b8507efed3
🎨 #347 2019-05-17 10:34:09 +08:00
Van c8a82c1694
🎨 2019-05-17 10:26:00 +08:00
Van 283270c4e1
🎨 2019-05-17 10:25:11 +08:00
Van 7167b83c9e
Merge remote-tracking branch 'origin/master'
# Conflicts:
#	package-lock.json
2019-05-17 09:48:47 +08:00
Van 4d16aac364
🎨 add 2019-05-17 09:48:30 +08:00
Liang Ding e7a3fa9334
🎨 #347 2019-05-17 09:32:03 +08:00
Liang Ding eabce6b343
🎨 #347 2019-05-17 09:27:22 +08:00
Liang Ding aa17c25400
🎨 #347 2019-05-17 01:41:04 +08:00
Liang Ding 4f48e827f6
🎨 #347 2019-05-17 01:06:01 +08:00
Liang Ding be63fc0d16
🔧 简化配置 2019-05-17 00:49:13 +08:00
Liang Ding 5989d0fb7b
🎨 #347 2019-05-17 00:41:52 +08:00
Liang Ding 003354ffe4
🎨 #347 2019-05-16 23:37:04 +08:00
Liang Ding b5aa4f9a7d
🏗️ #347 2019-05-16 23:17:25 +08:00
Liang Ding 912c5821f1
📄 更新授权细节 2019-05-16 21:44:13 +08:00
Liang Ding 2cbf74ddf7
📝 更新文档地址 2019-05-16 21:41:20 +08:00
Liang Ding cc928edb4a
🔥 Fix #357 2019-05-16 21:28:55 +08:00
Liang Ding 44aee6b548
🔥 删除 doc 2019-05-16 21:28:31 +08:00
Liang Ding ec89d3ee4e
🔥 #356 2019-05-16 21:21:26 +08:00
Liang Ding b115e34f15
🎨 Fix #318 2019-05-16 18:59:39 +08:00
Liang Ding f32bc7ab5f
🎨 #296 2019-05-16 18:53:05 +08:00
Liang Ding ad5ffb7510
🎨 #296 2019-05-16 18:42:47 +08:00
Liang Ding 7f28661cb4
🎨 非 Windows 环境强制使用 docker 2019-05-16 18:29:28 +08:00
Liang Ding efa278c773
🔥 删除 Shell 功能 2019-05-16 18:26:32 +08:00
Liang Ding c24e4f4711
🎨 #318 2019-05-16 18:12:38 +08:00
Liang Ding 7deb7c60e5
🎨 #318 2019-05-16 18:10:44 +08:00
Liang Ding e97ce75b92
🐛 NPE 2019-05-16 17:50:58 +08:00
Liang Ding b97ba481ef
🔥 #356 2019-05-16 17:36:27 +08:00
Liang Ding c13b585d3a
🎨 #318 2019-05-16 17:27:43 +08:00
Liang Ding 1c3c96e371
🔒 #355 删除上传文件 2019-05-16 17:25:47 +08:00
Liang Ding 73f873c62f
💚 使用 go 1.12 进行 CI 2019-05-16 13:55:06 +08:00
Liang Ding c62d40e3a9
🎨 #296 2019-05-16 13:52:18 +08:00
Liang Ding 93a64bb9d7
🎨 #296 2019-05-16 13:51:18 +08:00
Liang Ding 8058946f14
🎨 执行超时反馈 2019-05-16 13:46:53 +08:00
Liang Ding 0d36d56631
. 2019-05-16 13:18:09 +08:00
Liang Ding 9a3316b124
🎨 进程状态 2019-05-16 13:12:04 +08:00
Liang Ding 51fa9146d1
🎨 进程退出状态 2019-05-16 13:07:28 +08:00
Liang Ding c80d6746c0
🎨 Cleanup code 2019-05-16 12:56:55 +08:00
Liang Ding c7e4034407
🔒 #296 限制执行时间 2019-05-16 12:56:25 +08:00
Liang Ding 6bc8f32e85
🐳 沙箱环境 2019-05-16 12:48:44 +08:00
Liang Ding 41e2316cc5
🎨 docker 沙箱 2019-05-16 12:47:03 +08:00
Liang Ding be2d5b74b3
🎨 user cache dir 2019-05-16 12:41:39 +08:00
Liang Ding 07e9f4654f
🎨 Cleanup code 2019-05-16 12:19:26 +08:00
Liang Ding 318f453125
🎨 用户目录 2019-05-16 12:13:11 +08:00
Liang Ding f1cc112a52
🎨 读取用户目录 2019-05-16 12:07:21 +08:00
Liang Ding bb3d4979b7
🎨 Cleanup code 2019-05-16 12:02:32 +08:00
Liang Ding d30e3b76b9
🎨 Cleanup code 2019-05-16 11:44:07 +08:00
Liang Ding 1d84db7740
🔧 用户元数据目录配置化 2019-05-16 11:17:06 +08:00
Liang Ding 1a4feddf34
🐳 #296 2019-05-16 10:53:22 +08:00
Liang Ding 88f16e1057
🎨 修复编译问题 2019-05-16 10:53:03 +08:00
Liang Ding efcceccac4
🎨 Cleanup code 2019-05-16 10:52:31 +08:00
Liang Ding 11dbe42c9a
使用模块依赖 2019-05-16 09:52:21 +08:00
Liang Ding a8121d05f8
🐳 #296 去掉命名空间 2019-05-16 09:51:58 +08:00
Liang Ding 1d42fb1215
去掉 vendor 2019-05-16 09:47:09 +08:00
Liang Ding 6cdd14c783
🎉 📄 2019
2019 新年快乐!
2019-01-02 21:43:11 +08:00
Liang Ding f86ca5b632
💡 增加注释 2018-12-30 14:57:43 +08:00
D ce2ae8f97f
Fix #352
添加编译工具依赖的动态库环境变量
2018-12-30 14:55:39 +08:00
yougg 6b4879fcbd
添加编译工具依赖的动态库环境变量 2018-12-30 14:26:39 +08:00
Liang Ding 60b3de2159
💚 修复单元测试 2018-10-28 10:07:01 +08:00
Liang Ding 2edbefd883
🐛 Fix #349 2018-10-28 10:03:24 +08:00
Liang Ding d4980a0e4d
🐛 Fix #349 2018-10-28 09:59:23 +08:00
D 10d8d65d72
Merge pull request #348 from HaraldNordgren/go_versions
Bump Go versions and use 1.n.x to get latest minor versions
2018-10-28 09:30:28 +08:00
Harald Nordgren 138583d96f Bump Go versions and use 1.n.x to get latest minor versions 2018-10-28 00:05:43 +02:00
Liang Ding 22670dcd7f
:octocat: 加入 GitHub Issue 模板 2018-10-07 09:57:12 +08:00
Liang Ding 36f0921c55
📝 README 使用中文 2018-10-07 09:55:34 +08:00
Liang Ding e23e26dbdf
📝 更新用户指南 2018-10-07 08:53:18 +08:00
Van 3666c12738
⬆️ upgrade gulp 2018-10-05 23:11:52 +08:00
Van 50b03b1ec8
:sparklers: fixed #346 2018-10-05 21:04:40 +08:00
Liang Ding fb9a273af1
🎨 跳过常规库目录扫描 2018-10-05 20:52:11 +08:00
Van 87c528eac3
:sparklers: 2018-10-05 17:29:18 +08:00
Liang Ding 8c7d2636b2
🔧 Change repo language 2018-05-06 22:33:44 +08:00
Liang Ding e39b302bdc Update vendor 2018-03-13 13:24:04 +08:00
Liang Ding 982ef9c07f Update vendor 2018-03-13 13:19:03 +08:00
Liang Ding 0b8da63175 Update vendor 2018-03-13 13:11:10 +08:00
Liang Ding 0a51a03f35 Update gocode in vendor 2018-03-13 13:05:45 +08:00
Liang Ding bfd836c6fc 🐳 docker build 2018-03-13 12:57:02 +08:00
Liang Ding 1d1b548a68 🐳 docker build 2018-03-13 12:32:44 +08:00
Liang Ding 1fcc40af30 💚 CI 2018-03-13 12:18:17 +08:00
Liang Ding b80a132866 💚 CI 2018-03-13 12:15:51 +08:00
Liang Ding aea0392beb 🔧 Vendoring 2018-03-13 12:10:52 +08:00
Liang Ding f96c8befdf 🔧 vendor 2018-03-13 12:09:30 +08:00
Liang Ding 01ccdc329a 🐳 docker build 2018-03-13 12:06:55 +08:00
Liang Ding 688cb7cce4 🐳 docker build 2018-03-13 12:04:39 +08:00
Liang Ding 3f48fd3250 🐳 docker build 2018-03-13 11:58:24 +08:00
Liang Ding 7dc9e0e867 🐳 Docker build 2018-03-13 11:54:01 +08:00
Liang Ding 7b040f9f4f Add vendor 2018-03-13 11:47:32 +08:00
Liang Ding c023c9ae8c Add vendor 2018-03-13 11:29:05 +08:00
Liang Ding a399713787 🐳 Update Dockerfile 2018-03-13 11:25:58 +08:00
Liang Ding 9f067a8361 🔥 Remove redundant dependencies 2018-03-13 11:25:13 +08:00
Liang Ding 3548f97239 🔧 Update author email 2018-03-13 10:58:49 +08:00
Liang Ding eed97d5fd8 🎨 Fix #334 2018-03-13 10:53:05 +08:00
Liang Ding 8229848549 🔒 Update external URL using SSL 2018-03-12 12:28:33 +08:00
Liang Ding b7f209abcc 🔧 Update author email 2018-03-12 12:20:52 +08:00
Liang Ding eeeacf917c 🍱 Go Report Card 2018-02-20 12:47:19 +08:00
Liang Ding d905075fc1 📄 2018 Happy new year! 2018-01-01 12:35:13 +08:00
Liang Ding fc36272dc8 🔧 File header exclude vendor 2017-11-14 19:40:26 +08:00
Liang Ding ae678d84d0 📄 Add file header 2017-11-14 19:40:07 +08:00
Liang Ding f859a19e7b 💚 Fix CI build 2017-10-18 13:29:22 +08:00
Liang Ding 5121acdc58 ⬆️ #326 2017-08-15 11:02:32 +08:00
Liang Ding 0b0a67cc33 🔖 Release 1.5.3 2017-08-09 09:39:16 +08:00
Liang Ding 197a33f0e8 🐛 Fix #281 2017-08-09 09:19:36 +08:00
Liang Ding 02301d15fb 🎨 Fix #325 2017-08-09 00:35:20 +08:00
Liang Ding 1fb29e4602 🐳 #325 2017-08-08 22:00:00 +08:00
Liang Ding 2108a9b407 🎨 Fix #322 2017-08-08 21:59:40 +08:00
Liang Ding eba96fd0b6 🐳 #325 2017-08-08 21:21:47 +08:00
Liang Ding 8e972abee0 Merge branch 'master' into 1.5.3-dev 2017-08-08 21:01:50 +08:00
Liang Ding 26548d8717 🎨 Run client program using 'runner' user in docker container 2017-08-08 21:00:49 +08:00
Liang Ding d919da01a7 Merge remote-tracking branch 'refs/remotes/origin/1.5.3-dev' 2017-05-05 13:36:23 +08:00
Liang Ding 2895959529 Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-05-05 13:35:52 +08:00
Liang Ding 65e65e2d8a 🎨 Fix #321 Rename user's getters 2017-05-05 13:35:09 +08:00
Liang Ding 41d641224e Merge remote-tracking branch 'refs/remotes/origin/1.5.3-dev' 2017-05-04 18:06:10 +08:00
Liang Ding 4893c974bc Fix #320 2017-05-04 18:05:01 +08:00
Liang Ding 81f3bce1c5 🔧 Change admin user default theme to 'default' 2017-05-04 17:21:50 +08:00
Van e456a17443 :sparklers: upgrade gulp cc 2017-04-24 00:11:51 +08:00
Van 0e6495cb66 :sparklers: add study link 2017-04-24 00:10:16 +08:00
Liang Ding 2b790ce300 Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-03-29 21:06:23 +08:00
Liang Ding a000f6b44c Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-03-29 21:05:58 +08:00
D 8ffe989c2e Merge pull request #317 from khjde1207/master
修改不能显示双引号的问题。
2017-03-29 21:05:21 +08:00
khjde1207 f69d7c3f06 Update users.go 2017-03-29 13:56:57 +09:00
khjde1207 687c953931 " -> &quot;
-ldflags "-H windowsgui" 중 " 이후 부분 잘리는 현상 해결.
2017-03-29 13:37:09 +09:00
Liang Ding 3cb76438ff Merge remote-tracking branch 'refs/remotes/origin/1.5.3-dev' 2017-03-27 21:32:18 +08:00
Liang Ding 4ab1cb3293 🎨 Some little tweaks for #308
@khjde1207
2017-03-27 21:31:35 +08:00
Liang Ding ec3c3b52f4 Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-03-27 21:08:40 +08:00
Liang Ding b61764ef37 Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-03-27 21:07:24 +08:00
D f4fec2978e Merge pull request #316 from khjde1207/master
Enhance #308 by specifying OS.
2017-03-27 21:04:07 +08:00
khjde1207 dfcbeaa9c8 Update build.go 2017-03-27 11:52:32 +09:00
khjde1207 bc9466f435 Update cross.go 2017-03-27 11:51:55 +09:00
khjde1207 b5365794eb Update cross.go 2017-03-27 11:42:10 +09:00
khjde1207 d7550b110a Update user.go 2017-03-27 11:22:22 +09:00
khjde1207 2293e37e72 Update cross.go 2017-03-27 11:05:31 +09:00
khjde1207 ed7170a02f Update build.go 2017-03-27 11:04:34 +09:00
khjde1207 4b1cdbb249 Update wide.go 2017-03-27 11:00:15 +09:00
khjde1207 b33638dc06 Update user.go 2017-03-27 10:59:35 +09:00
khjde1207 e1dca8c16d Update menu.js 2017-03-27 10:58:05 +09:00
khjde1207 9314f2b721 Update wide.min.js.map 2017-03-27 10:46:50 +09:00
khjde1207 290625c8e9 Update wide.min.js 2017-03-27 10:43:41 +09:00
khjde1207 2f4ead4731 Update users.go 2017-03-27 10:38:59 +09:00
khjde1207 699ba5e0c5 Update preference.html 2017-03-27 10:37:28 +09:00
Liang Ding d1a54532c9 Merge remote-tracking branch 'refs/remotes/origin/1.5.3-dev' 2017-03-15 23:38:56 +08:00
Liang Ding ee069e75ac 🎨 logging 2017-03-15 23:38:34 +08:00
Liang Ding 0309fd01a2 Merge remote-tracking branch 'refs/remotes/origin/master' into 1.5.3-dev 2017-03-15 23:37:54 +08:00
Liang Ding e4e2c029e1 🎨 Compress static resource 2017-03-15 23:37:52 +08:00
Liang Ding 4a4788be85 Merge remote-tracking branch 'refs/remotes/origin/1.5.3-dev' 2017-03-15 23:27:06 +08:00
Liang Ding ea40ff990b Fix #308
解决构建报错时 lint 渲染以及运行输出渲染换行的问题
2017-03-15 23:26:10 +08:00
Liang Ding 029bc7c917 🔨 #308
重新调整了构建时输出流和错误流的处理
TBD:构建出错时的 lints 结构组装
2017-03-15 22:26:12 +08:00
Liang Ding a89af9a374 🔧 Tweak default user confs 2017-03-15 22:24:32 +08:00
Liang Ding a93a52197d 🏁 Fix output's path jump failed on Windows 2017-03-15 22:23:23 +08:00
Liang Ding bfbc47c3c4 #308
用户可以在 Preference->Go Tool 中设置构建参数。

需要重写 build 输出处理,目前是按照是否有输出来判断构建成败,应该改为按输出流/错误流判断
2017-03-15 00:21:13 +08:00
Liang Ding 130006608f 📄 Update copyright year 2017-03-14 22:40:45 +08:00
Liang Ding d5493ea380 🐳 Dockerfile 2017-01-23 20:19:12 +08:00
Liang Ding b7eb57ac99 ⬆️ Upgrade gocode for Docker 2017-01-23 20:04:09 +08:00
Liang Ding 7f4df9b4de Fix #302 2017-01-23 17:49:43 +08:00
Liang Ding d82fa32715 🐳 #302 2017-01-23 16:42:32 +08:00
Liang Ding 9500ea33a2 🐳 #302 2017-01-23 15:28:34 +08:00
Liang Ding 771dffc7d4 🐳 #302
First try
2017-01-23 14:52:29 +08:00
Liang Ding e074ddd235 🎨 format code 2016-12-28 11:17:32 +08:00
D b5453f02b7 Merge pull request #301 from khjde1207/master
反复存档会发生runtime.throw
2016-12-28 09:50:29 +08:00
khjde1207 8155c94ffc 反复存档会发生runtime.throw
反复存档会发生runtime.throw
读了一次就不再读。。。。
2016-12-27 22:50:32 +09:00
D bed29d8418 Update README.md 2016-12-15 10:29:58 +08:00
Liang Ding 0ecb57a871 🔖 Release 1.5.2 2016-12-15 09:20:34 +08:00
Liang Ding 78a44a9744 🎨 clean code 2016-12-14 20:43:34 +08:00
Liang Ding be80e292d0 🎨 format code 2016-12-14 18:19:18 +08:00
Liang Ding 23f299f0bf #293 2016-12-14 18:15:53 +08:00
Van 28d8f99f85 #285 2016-10-30 00:02:21 +08:00
Van 79984ab9ac fixed #285 2016-10-29 23:30:09 +08:00
D 817c556df4 Merge pull request #289 from khjde1207/master
Well done, thank u!
2016-10-25 15:45:32 +08:00
khjde1207 0c42e682a6 Update index.html 2016-10-25 16:16:46 +09:00
D 3f888d0884 Fix #287
非常感谢你!
2016-10-25 14:03:08 +08:00
khjde1207 0686ef3020 ko_KR.json
韩文版
2016-10-25 14:37:07 +09:00
Liang Ding def80b904f Release 1.5.1 2016-08-31 09:26:50 +08:00
D 910cf9c536 Update README.md 2016-06-14 20:25:51 +08:00
Liang Ding 0062c28edb #277 2016-06-12 18:17:36 +08:00
Liang Ding 487d97d844 Fix #277 2016-06-12 17:58:53 +08:00
我叫 D,你好 ;-p 6b4ec1b924 Update README.md 2016-05-07 11:12:30 +08:00
我叫 D,你好 ;-p bcbc6048c4 Merge pull request #274 from shiyou0130011/language-dictionary-fix
修正繁體中文、日文的用詞
2016-05-04 20:12:12 -05:00
Tzuyang Tsai 38f1b190f1 修正日語用詞 2016-05-05 00:00:27 +08:00
Tzuyang Tsai 6d9e4aaf6a 再次修正用詞
修正 commit:714dd8768f5f720c4553a3c5c2e5bf5a03c5b59d 沒修到的

此外,將所有「文件」改為「檔案」、
把不必翻譯成中文的除蟲改為 debug
2016-05-04 23:53:50 +08:00
Tzuyang Tsai 714dd8768f 修改繁體中文的字典
將用詞改為台灣的用詞
2016-05-04 23:41:18 +08:00
Van 5963f23d33 cc 2016-02-27 11:16:04 +08:00
Liang Ding be04f26b0d Fix #268 2016-02-26 13:45:54 +08:00
Liang Ding ada7da92c9 Fix #267
@plinyGo
2016-02-26 11:12:33 +08:00
Van 35576c42b5 compress 2016-02-20 14:44:46 +08:00
Liang Ding f1e1c1e085 黑客派 link update
https://hacpai.com
2016-02-20 14:38:16 +08:00
Liang Ding 952daeb76c Fix undefined layout bug 2016-02-16 21:24:37 +08:00
Liang Ding 3a3ffb1bc4 Release Wide 1.5.0 2016-02-04 15:16:05 +08:00
Liang Ding 3b6fd9b248 Update README.md 2016-01-15 16:28:44 +08:00
Liang Ding b8d6544059 . 2016-01-15 14:26:50 +08:00
Vanesssa a6bc92f4f1 modified: static/css/playground-embed.css
modified:   static/css/playground.css
2016-01-15 14:22:40 +08:00
Liang Ding 7128572b0a Improve get local IP impl 2016-01-15 13:34:27 +08:00
Liang Ding 6318f50b59 #264 2016-01-14 15:45:52 +08:00
Liang Ding db1f2d29a9 Exclude VCS files from file tree
.git, .svn, .hg
2016-01-14 15:09:35 +08:00
Vanesssa f4f909ee6c modified: static/css/lib.min.css
modified:   static/css/wide.min.css
	modified:   static/js/lib.min.js
	modified:   static/js/wide.min.js
	modified:   static/js/wide.min.js.map
2016-01-12 16:52:38 +08:00
Liang Ding d9fb62c2b1 Fix #263
多谢 @DreamUFO 反馈 🍇
2016-01-12 16:42:38 +08:00
Liang Ding 991526927a Update file header 2015-12-30 16:13:43 +08:00
Liang Ding e48568fbe7 Skip loading corrupted user 2015-12-30 10:06:32 +08:00
Liang Ding 2e03c1449c logging 2015-12-28 16:00:17 +08:00
Liang Ding 638dd3e67c . 2015-12-25 09:28:44 +08:00
Liang Ding 743dbe2fcc Fix #261
@Vanessa219 根据运行模式来决定是否启用压缩的静态资源
2015-12-23 10:08:03 +08:00
Liang Ding aea671c393 . 2015-12-23 10:07:07 +08:00
Vanesssa ed3e213c72 modified: views/index.html 2015-12-15 18:09:14 +08:00
Vanesssa 676d4736f4 Merge remote-tracking branch 'origin/master' 2015-12-15 18:03:43 +08:00
Vanesssa 2bd137cc63 new file: static/css/lib.min.css
new file:   static/css/wide.min.css
	new file:   static/js/lib.min.js
	new file:   static/js/lib/ztree/zTreeStyle.min.css
	new file:   static/js/wide.min.js
	new file:   static/js/wide.min.js.map
2015-12-15 18:00:23 +08:00
Vanesssa 121c022ff9 modified: .gitignore
modified:   gulpfile.js
	modified:   package.json
	modified:   static/js/tree.js
	modified:   views/index.html
	static/css/lib.min.css
	static/css/wide.min.css
	static/js/lib.min.js
	static/js/lib/ztree/zTreeStyle.min.css
	static/js/wide.min.js
	static/js/wide.min.js.map
2015-12-15 17:59:45 +08:00
Liang Ding 55de8c75ad . 2015-12-15 14:34:03 +08:00
Vanesssa d9f7bb2869 modified: static/css/wide.css
modified:   static/js/hotkeys.js
	modified:   static/js/windows.js
	modified:   views/index.html
2015-12-15 14:26:10 +08:00
Liang Ding 30f11f3867 . 2015-12-15 12:55:56 +08:00
Vanesssa b4b96b3985 modified: static/css/side.css
modified:   static/js/hotkeys.js
	modified:   static/js/windows.js
	modified:   views/index.html
2015-12-14 18:10:53 +08:00
Liang Ding 77cd76d0c0 Fix #205 & #132 2015-12-08 17:30:17 +08:00
Vanesssa f8a693f20c new file: gulpfile.js
new file:   package.json
2015-12-08 16:32:06 +08:00
Vanesssa 43ad792460 modified: static/js/session.js
modified:   static/js/windows.js
	gulpfile.js
	package.json
2015-12-08 16:30:41 +08:00
Liang Ding 6ed1c99b53 #205 2015-12-08 09:58:06 +08:00
Vanesssa b36d04071f resize color 2015-12-07 16:43:23 +08:00
Van 6da4023ad2 details style 2015-12-06 22:54:30 +08:00
Van f1acbd3682 . 2015-12-06 11:58:55 +08:00
Liang Ding 9bc0d8e036 . 2015-12-06 11:46:39 +08:00
Liang Ding 134a6f8178 Workaround 2015-12-06 11:39:26 +08:00
Liang Ding 43c3ae0c69 . 2015-12-06 11:39:19 +08:00
Van a1c5ac31df 改了一天,中途看了个跑男&羋🈷️传。应该差不多了,还要洗个白白,明天 shopping & 🎬 2015-12-06 00:27:15 +08:00
Van 2a41cbae8b resize language & lib 2015-12-05 19:40:19 +08:00
Van b44fd6d959 要开始写 window resize,有点小激动啊 2015-12-05 10:20:38 +08:00
Liang Ding 8f4fc25324 Set tree node selected after close an editor 2015-12-04 15:28:29 +08:00
Liang Ding 10de6cd3d9 Disqus load 2015-11-30 11:44:49 +08:00
Liang Ding ac0cc1ea04 . 2015-11-27 16:55:28 +08:00
Liang Ding f3dadc4795 Fix #257 2015-11-24 17:39:35 +08:00
Liang Ding 0ee3704a5b #257 2015-11-24 16:44:34 +08:00
Liang Ding a732640bc1 #257 2015-11-24 16:30:37 +08:00
Liang Ding 039e826053 #257 2015-11-24 15:42:28 +08:00
Liang Ding 0482e148e3 Fix #147
One year ago...
2015-11-24 14:55:09 +08:00
Liang Ding 8d2400c9c4 . 2015-11-24 14:54:20 +08:00
Liang Ding aceb391c9f . 2015-11-20 12:12:29 +08:00
Liang Ding 53530862bb . 2015-11-20 10:28:41 +08:00
Liang Ding 84cdbe88b3 Fix unzip empty dir bug 2015-11-20 09:38:29 +08:00
Liang Ding bf7ebfd618 Fix zip empty dir bug 2015-11-20 09:13:50 +08:00
Liang Ding 25b628c844 Update README.md 2015-11-17 13:16:59 +08:00
Liang Ding 1dd79cf684 Fix copy directory resource leak 2015-10-26 14:19:46 +08:00
Liang Ding 42093ebfc1 . 2015-10-26 13:18:36 +08:00
Liang Ding 0780cdb04f . 2015-10-21 12:50:56 +08:00
Liang Ding 7772fcdb16 . 2015-10-21 12:16:39 +08:00
Liang Ding bff474033b . 2015-10-21 12:07:30 +08:00
Liang Ding 02d4bbab34 . 2015-10-21 12:04:08 +08:00
Liang Ding 4deb98baaf . 2015-10-20 18:40:26 +08:00
Liang Ding abe5672490 #257 2015-10-20 18:31:20 +08:00
Liang Ding fdba27c224 #257 2015-10-20 18:08:48 +08:00
Liang Ding 2210eac230 logging 2015-10-14 10:08:06 +08:00
Liang Ding 9119a1da45 file util: copy file/dir 2015-10-13 11:10:33 +08:00
Liang Ding 3c30d09cb6 . 2015-10-12 13:43:59 +08:00
Liang Ding 7347c0b151 . 2015-10-12 13:33:18 +08:00
Liang Ding 60570b6680 Update README.md 2015-10-09 09:56:57 +08:00
Liang Ding d42310fb84 Update README.md 2015-10-03 17:03:58 +08:00
Liang Ding 15f3c12579 close editor after rename or remove the file 2015-10-03 15:00:19 +08:00
Liang Ding 6fe83917dd #248
多谢 @gitwillsky
2015-10-02 23:07:43 +08:00
Liang Ding c34f9cdda6 Merge pull request #250 from gitwillsky/willsky
add websocket ping/pong feature
2015-10-02 22:37:06 +08:00
willsky c49a47cd21 add websocket ping/pong feature 2015-10-02 22:26:45 +08:00
willsky fa75a59f10 add websocket ping/pong feature 2015-10-02 22:20:47 +08:00
Liang Ding 59bb310fa0 File rename/remove bug fix 2015-10-02 21:39:36 +08:00
Liang Ding 54cfad611d #247 2015-10-02 17:01:24 +08:00
Liang Ding 4657c48865 start page 2015-09-30 11:55:02 +08:00
Liang Ding 426783b963 Update README.md 2015-09-28 15:44:57 +08:00
Liang Ding f0b5cbca16 Update README.md 2015-09-28 15:41:16 +08:00
Liang Ding 9908cb5c43 Release Wide 1.4.0 2015-09-28 15:20:03 +08:00
Liang Ding ac579ab769 Fix #218 2015-09-27 14:29:26 +08:00
Van f4693a6eee async file 2015-09-27 11:02:48 +08:00
Liang Ding a119370a23 #218 and WebSocket bug fix 2015-09-27 07:36:34 +08:00
Liang Ding b758ec854e . 2015-09-26 18:31:57 +08:00
Liang Ding 151757cc8a . 2015-09-26 18:17:28 +08:00
Liang Ding 82a9f5a331 . 2015-09-26 18:14:45 +08:00
Liang Ding 915f7e3cee #218 2015-09-26 18:09:30 +08:00
Van 38c742b405 登陆 2015-09-26 17:33:55 +08:00
Liang Ding 8de99bcd08 about 2015-09-26 16:45:56 +08:00
Liang Ding c743c6f007 Search bug on Windows 2015-09-26 16:01:09 +08:00
Liang Ding bb8b5534d2 More pretty logging and doc 2015-09-26 15:40:05 +08:00
Liang Ding fc2c0e4b81 Open file bug on Windows 2015-09-26 15:39:09 +08:00
Liang Ding 5794df245c Update credits 2015-09-26 09:48:08 +08:00
Liang Ding 66654b7b15 package tool 2015-09-19 21:40:32 +08:00
Liang Ding b5f59eadf0 Update README.md 2015-09-18 10:17:14 +08:00
Liang Ding fd6636df3f Fix #243 2015-09-17 21:43:49 +08:00
Liang Ding 08a7524ee0 . 2015-09-12 14:31:30 +08:00
Liang Ding 3d9c50cb8e Merge pull request #241 from thewhitetulip/patch-1
Fixed a typo
2015-09-08 14:29:38 +08:00
Suraj Patil 9edc58b6d5 Fixed a typo
Spelling mistake of Directory
2015-09-08 11:08:05 +05:30
Liang Ding 5259b54304 Fix #240 2015-09-08 09:37:19 +08:00
Liang Ding 78ac45ae24 Fix #239 2015-09-08 09:27:01 +08:00
Liang Ding 9b9a17a842 hacpai.com 2015-08-27 00:48:54 +08:00
Liang Ding 6d31a0a20d . 2015-08-20 22:00:44 +08:00
Liang Ding 3f8544442d . 2015-08-20 21:52:35 +08:00
Liang Ding eaa93281d1 . 2015-08-20 21:47:00 +08:00
Liang Ding 0a96f993fb . 2015-08-15 11:41:37 +08:00
Liang Ding 1d99c130da Fix #236 2015-08-15 11:34:18 +08:00
Liang Ding 012eab83c7 Update README.md 2015-08-05 14:16:52 +08:00
Liang Ding 2a8788e6e9 . 2015-08-05 14:11:40 +08:00
Liang Ding af3224484a . 2015-08-05 13:50:32 +08:00
Liang Ding e947f6333e . 2015-08-05 13:49:12 +08:00
Liang Ding 87aa489fd0 . 2015-08-05 13:42:13 +08:00
Liang Ding 534d7f331c . 2015-08-05 13:29:05 +08:00
Liang Ding 5c87368bb8 . 2015-08-05 13:23:48 +08:00
Liang Ding 94d7f0c220 . 2015-08-05 13:21:25 +08:00
Liang Ding d80c1b7a54 . 2015-08-05 13:20:03 +08:00
Liang Ding cc70eb66ff . 2015-08-05 13:16:53 +08:00
Liang Ding 67b511ad86 . 2015-08-05 13:07:53 +08:00
Liang Ding 2b0e356979 . 2015-08-05 13:03:13 +08:00
Liang Ding 4b41e905f6 . 2015-08-05 12:58:39 +08:00
Liang Ding aa454d7513 . 2015-08-05 12:38:48 +08:00
Liang Ding 1ec18a5268 . 2015-08-05 12:02:16 +08:00
Liang Ding bcf41780ab . 2015-08-05 11:46:44 +08:00
Liang Ding 1c661915b7 . 2015-08-05 11:32:51 +08:00
Liang Ding d1fe1d757a . 2015-08-05 11:27:38 +08:00
Liang Ding e76d4f3fd9 Dockerfile 2015-08-05 11:24:53 +08:00
Liang Ding 2ebaa98efb . 2015-08-05 11:09:52 +08:00
Liang Ding 23c924e286 Update README.md 2015-08-04 22:00:21 +08:00
Liang Ding 95c7f65395 . 2015-08-04 21:55:00 +08:00
Liang Ding ab24e1b6d9 unit test 2015-08-04 21:48:38 +08:00
Liang Ding 931e573f52 #231 🍼 2015-08-04 21:37:14 +08:00
Liang Ding eea2ae469d Update README.md 2015-08-02 15:50:14 +08:00
Liang Ding 519a3f0909 Update README.md 2015-08-02 13:31:18 +08:00
Liang Ding a286aa5e95 Update README.md 2015-08-01 13:52:04 +08:00
Liang Ding 8bec021a47 Update README.md 2015-08-01 13:50:46 +08:00
Liang Ding ef40f975b2 Update README.md 2015-07-31 11:42:18 +08:00
Liang Ding 38e16a4658 Playground output escape 2015-07-31 11:39:08 +08:00
Liang Ding 95cdebcc09 fix file permission bug 2015-07-23 16:31:37 +08:00
Liang Ding 5dff1fd490 [Playground] Do not auto focus 2015-07-21 23:16:48 +08:00
Liang Ding e16696ac63 Release 1.3.0 2015-07-18 10:19:08 +08:00
Liang Ding a001bd6e35 . 2015-07-08 10:39:45 +08:00
Liang Ding c4018f7903 Index 302 redirect 2015-06-30 09:50:35 +08:00
Liang Ding 8ecd8eb2fc Merge pull request #232 from PaladinTyrion/master
fix map usage in events.go
2015-06-22 09:11:22 +08:00
paladintyrion 38b64b491a fix map usage in events.go 2015-06-22 08:36:02 +08:00
Liang Ding bb12838d1c Enhance autocompletion
IDE & Playground
2015-06-15 21:50:51 +08:00
Liang Ding da9c6079f0 Fix #230 2015-06-14 14:43:09 +08:00
Liang Ding 492e74e1e8 . 2015-06-14 13:23:46 +08:00
Liang Ding 84214a4a1a . 2015-06-10 10:44:47 +08:00
Liang Ding fed20889f0 Fix file permission of Go API 2015-06-04 14:32:16 +08:00
Liang Ding e8c27e762f Fix #83 2015-05-11 15:46:17 +08:00
Liang Ding e3b5fbbfa8 . 2015-04-29 18:04:30 +08:00
Liang Ding 3ee68aa6ce . 2015-04-27 14:37:27 +08:00
Liang Ding 46ecea41c0 Sole node id 2015-04-27 14:36:35 +08:00
Liang Ding f1b258ff7f Merge pull request #228 from PaladinTyrion/optimizeSth
Too many of your codes could be optimized
2015-04-27 14:10:23 +08:00
PaladinTyrion 4e1189f338 Too many of your codes could be optimized 2015-04-27 13:54:49 +08:00
Liang Ding d05bce6e27 Update emmet plugin 2015-04-27 10:09:52 +08:00
Liang Ding 22dcf590e7 Reduce ztree tId usages
tId is transient, so we can't use it to identify a node, using path
instead of it.
2015-04-26 21:58:23 +08:00
Liang Ding 4a697eeff2 playground output style 2015-04-16 09:37:21 +08:00
Liang Ding 23438f7af8 package script 2015-04-15 17:45:24 +08:00
Liang Ding e63e8f70ca . 2015-04-15 17:04:51 +08:00
Liang Ding 869ca61809 enhance runtime dependencies scout 2015-04-15 17:01:01 +08:00
88250 f288d76eb9 Fix #227 2015-04-12 08:15:28 +08:00
Van 9bc8addfbf Fix #226 2015-04-10 20:03:07 +08:00
Liang Ding f573841b59 playground emebed code fine tuning 2015-04-08 10:34:36 +08:00
Liang Ding e54e89f1b8 community news 2015-04-02 12:22:19 +08:00
Liang Ding 8f42e3cc47 code font 2015-04-01 11:40:28 +08:00
Liang Ding 06d56e7151 . 2015-04-01 10:47:12 +08:00
Liang Ding fec7480be9 . 2015-04-01 10:43:38 +08:00
Van 6f5d1fe086 . 2015-03-28 13:02:58 +08:00
Van 78fcc0d19a unzip gbk package 2015-03-28 12:15:44 +08:00
Liang Ding fb22dbd273 zip testing 2015-03-26 17:41:56 +08:00
Liang Ding 39c85c03a1 Fix Playground's gutter overflow 2015-03-25 16:00:59 +08:00
Liang Ding 003a94ba3f Update README.md 2015-03-24 21:18:55 +08:00
Liang Ding f87cabc558 upgrade CodeMirror 2015-03-24 17:27:39 +08:00
Van e3c07ed720 footer 2015-03-23 21:15:21 +08:00
Liang Ding ca2f593df3 footer 2015-03-23 17:55:15 +08:00
Liang Ding f30f32b0ac update document links 2015-03-23 16:55:09 +08:00
Liang Ding 9758d8508b reduce gorutines 2015-03-23 15:30:52 +08:00
Van 0cd5a093d5 autorefresh dir after compressing 2015-03-22 12:59:25 +08:00
Liang Ding 59e260ea6f Update README.md 2015-03-22 11:49:04 +08:00
Van 4ed531a6be Fix #223 2015-03-21 20:22:53 +08:00
Van bd1a2778c3 Fix #224 2015-03-21 18:10:53 +08:00
Van fce39ce4e2 Fix #222 2015-03-21 14:39:26 +08:00
Liang Ding af808c2024 logging 2015-03-20 17:41:49 +08:00
Liang Ding ec22bcd3ad channel err logging 2015-03-20 16:49:58 +08:00
Liang Ding 8c1a705a6a logging 2015-03-20 16:37:03 +08:00
Van c13108a7c0 update readme 2015-03-19 21:41:43 +08:00
Van 50ee59a2b7 remove gobuild releated
thank you @codeskyblue for the wonderful project in the past
2015-03-19 21:36:06 +08:00
Van 9aaea36153 update download count 2015-03-19 21:31:02 +08:00
Liang Ding 4b9c72f440 release gorutines 2015-03-19 11:42:52 +08:00
Van 73cfcdce55 update feature description 2015-03-18 22:19:13 +08:00
Van 3abdab7d32 update feature description 2015-03-18 22:14:09 +08:00
Van 785d1ee021 Fix #220 2015-03-18 22:04:51 +08:00
Van 175901bb93 new to &{} 2015-03-18 21:43:29 +08:00
Liang Ding eaaee8443a panic recover in gorutine 2015-03-16 11:24:55 +08:00
Liang Ding e10eadb1e0 #219 2015-03-15 12:11:23 +08:00
Liang Ding 7fcc39be60 #218 2015-03-13 21:04:31 +08:00
Liang Ding 1be7dc89e0 comments 2015-03-11 13:52:14 +08:00
Liang Ding 39f4a358c7 doc 2015-03-11 10:22:35 +08:00
Liang Ding 71ccdd4a72 . 2015-03-11 10:16:38 +08:00
Liang Ding a872615b12 Update README.md 2015-03-10 17:57:31 +08:00
Liang Ding 55efb753e6 Terms and Download link 2015-03-10 15:14:29 +08:00
Liang Ding a0ac768410 Terms ©️ 2015-03-10 15:05:41 +08:00
Liang Ding 9307dcaf77 . 2015-03-10 15:05:30 +08:00
Liang Ding 68f6c1d314 add git clone under team menu 2015-03-10 14:37:58 +08:00
Liang Ding 02a660109a fix #217 2015-03-10 11:49:38 +08:00
Liang Ding 46e187a1a8 uniform names of handler functions 2015-03-09 14:16:46 +08:00
Liang Ding 04888ede4c package tool 2015-03-09 11:58:13 +08:00
Liang Ding e433d354e2 add placeholder in git clone input 2015-03-09 11:36:15 +08:00
Liang Ding 4ba9c895de Update README.md 2015-03-08 17:24:46 +08:00
Liang Ding d2638f8f7a Update README.md 2015-03-08 17:18:50 +08:00
Liang Ding fe2162ed3c Update README.md 2015-03-08 17:17:35 +08:00
Liang Ding 8288e7da01 Update README.md 2015-03-08 17:14:05 +08:00
Van 188307ee0a auto expand directory after git clone 2015-03-08 12:43:04 +08:00
Van 0af3f70e53 fix #191 2015-03-08 11:39:55 +08:00
Van a31f71c838 #191 2015-03-08 11:00:56 +08:00
Van 168ee7c5ee #191 2015-03-08 08:59:21 +08:00
Van d35d476162 . 2015-03-08 08:46:57 +08:00
Van 63b969df55 git clone 2015-03-08 00:01:06 +08:00
Van fcb529cfe3 #191
add context menu on directory
2015-03-07 17:51:28 +08:00
Liang Ding d8f3cf7cad Merge remote-tracking branch 'origin/master' 2015-03-04 13:50:52 +08:00
Liang Ding fa4c1ef06f tweak width of autocomplete window of playground 2015-03-04 13:47:42 +08:00
Van fda56c4fb7 playground add disqus 2015-03-01 22:03:56 +08:00
Van 92bee6437b disqus 2015-03-01 21:28:30 +08:00
Van 9e920eff93 add discus 2015-03-01 21:13:21 +08:00
Van 6b7aaa6a77 about 2015-03-01 19:03:46 +08:00
Van abf626e7b2 prepare to release 1.2.0 2015-03-01 16:01:52 +08:00
Liang Ding 232cb5b2f2 logging 2015-02-27 10:53:37 +08:00
Liang Ding 1f444bb0ae Fix #212 2015-02-26 17:01:39 +08:00
Liang Ding 184426c35e #212
parse request path
2015-02-22 15:54:51 +08:00
Liang Ding 3b8cb3385b #212
disqus integration
2015-02-21 14:37:04 +08:00
Van 94d80bae1a share dialog responsive 2015-02-21 12:38:03 +08:00
Liang Ding 6444889cba i18n 2015-02-21 12:27:04 +08:00
Van 337f669c9c #212 2015-02-20 09:42:05 +08:00
Liang Ding 05c1d2ffe8 session stat 2015-02-17 12:06:56 +08:00
Liang Ding 8fd9ae1315 #212
i18n and style of share dialog
2015-02-17 10:29:30 +08:00
Liang Ding a0fefc5db8 logging 2015-02-17 10:07:20 +08:00
Liang Ding ec0ca1e118 #212
short url
2015-02-16 17:10:21 +08:00
Liang Ding 5ecaff8c8f #212 2015-02-16 14:24:33 +08:00
Liang Ding 82b924c171 . 2015-02-16 14:06:57 +08:00
Liang Ding 184ec43472 skip hiden files in user conf dir 2015-02-16 11:16:56 +08:00
Liang Ding 9cc6e9fab6 get user home 2015-02-16 10:15:09 +08:00
Liang Ding c5f3942ed7 get user home 2015-02-16 10:06:17 +08:00
Liang Ding ce489a73e7 #212
add playground link
2015-02-15 16:04:18 +08:00
Liang Ding 4b254f4e91 #212 2015-02-15 15:46:31 +08:00
Liang Ding 9591345bfc #212 2015-02-15 15:15:14 +08:00
Liang Ding 2565035b54 #212 2015-02-15 14:24:49 +08:00
Liang Ding 45084f1628 #212 2015-02-15 13:53:02 +08:00
Liang Ding b6df14764c #212 2015-02-15 13:42:29 +08:00
Liang Ding d4fb7f011c #212 2015-02-15 11:09:17 +08:00
Liang Ding c0fcae05f1 #212 2015-02-15 10:08:51 +08:00
Van 1e72294b35 #212 2015-02-14 22:38:16 +08:00
Van cef345ab92 #212 2015-02-14 20:38:33 +08:00
Van ae2f6476be #212 2015-02-14 17:49:21 +08:00
Van 1cf399bdd5 #212 2015-02-14 00:00:38 +08:00
Liang Ding 7831ac960f compress codemirror.js 2015-02-13 14:47:49 +08:00
Liang Ding 2e1f3e8f38 #212 2015-02-13 14:08:35 +08:00
Liang Ding ad4c0cd896 . 2015-02-13 13:15:58 +08:00
Liang Ding ed1fd23759 #212 2015-02-13 12:57:12 +08:00
Liang Ding 6e1ae805e9 #212 2015-02-13 10:50:14 +08:00
Liang Ding 36a8bb4d50 #212 2015-02-13 09:59:51 +08:00
Liang Ding 7521c8b6d4 terms 2015-01-28 17:41:14 +08:00
Liang Ding e4edf9d098 . 2015-01-28 17:25:30 +08:00
Van cddab905c2 bug fixed 2015-01-28 16:23:53 +08:00
Van e3c2826183 dialog position fixed 2015-01-28 16:21:35 +08:00
Liang Ding 534661f643 Update README.md 2015-01-28 11:00:59 +08:00
Liang Ding 1d885281b9 Merge pull request #207 from youymi/master
fixed issue#200
2015-01-27 16:09:19 +08:00
youymi c08d2ebbf4 fixed issue#200 2015-01-27 15:58:58 +08:00
Liang Ding aa6dbe115b Update README.md 2015-01-25 13:31:07 +08:00
Liang Ding dc74ec281a skip non-golang autocomplete 2015-01-21 17:28:57 +08:00
Liang Ding 7b62a9b3ed #205 2015-01-19 10:19:38 +08:00
Liang Ding d39f207c4c update license header 2015-01-18 13:59:10 +08:00
Liang Ding ffe09fa731 update license header 2015-01-18 13:54:28 +08:00
Liang Ding ecf0bee616 Merge branch 'master' of https://github.com/b3log/wide 2015-01-17 11:15:50 +08:00
Liang Ding 3b34e53a8e . 2015-01-17 11:15:34 +08:00
Van 6f88a4ba10 样式修改 2015-01-16 17:20:31 +08:00
Liang Ding b46a173a85 . 2015-01-15 16:58:23 +08:00
Liang Ding 73ddbed41f . 2015-01-15 13:33:36 +08:00
Liang Ding d4bc170d8f Update README.md 2015-01-14 15:10:21 +08:00
Liang Ding 5864f647fb Update README.md 2015-01-14 15:10:03 +08:00
Liang Ding 15e0a79ebc output clear 2015-01-14 14:52:39 +08:00
Liang Ding 4b9c9a871a Merge branch 'master' of https://github.com/b3log/wide 2015-01-14 14:19:52 +08:00
Liang Ding a2f50ae94e optimize output stderr 📜 2015-01-14 14:19:31 +08:00
Liang Ding b5d1d6b5bb Update README.md 2015-01-14 13:37:14 +08:00
Liang Ding e42b02a306 Update README.md 2015-01-14 13:36:51 +08:00
Liang Ding 0a5a88335d . 2015-01-14 12:18:27 +08:00
Liang Ding eb7a509471 . 2015-01-14 11:49:11 +08:00
Liang Ding 9e0c1b1dd3 . 2015-01-14 11:40:02 +08:00
Liang Ding c5a0c4884a . 2015-01-14 11:13:27 +08:00
Liang Ding 18d5d1c09b Fix #202 2015-01-14 11:09:10 +08:00
Liang Ding fef4fffce0 refactor
separate functions into several files
2015-01-13 17:28:30 +08:00
Liang Ding 0edec10397 some details 💅 2015-01-13 13:54:19 +08:00
Liang Ding 93090f93c4 windows tcp conn load issue workaround 2015-01-12 14:49:52 +08:00
Liang Ding 9fb70afb16 clear output 2015-01-12 13:56:00 +08:00
Liang Ding a3a3ed0577 Update README.md 2015-01-12 11:47:04 +08:00
Liang Ding 66a7354be0 logging 🍭 2015-01-11 12:12:06 +08:00
Liang Ding abf9803ac7 disallow change workspace 🐛 😟 2015-01-09 10:18:24 +08:00
Liang Ding cc9554a4ca docs 2015-01-08 17:16:36 +08:00
Van 95c1e4335e gravtar http 修改 2015-01-07 10:25:18 +08:00
Liang Ding acd0030d26 themes black -> dark 2015-01-06 23:34:57 +08:00
Van 43a0c9ea6e Merge branch 'master' of https://github.com/b3log/wide 2015-01-04 16:37:43 +08:00
Van 84dc880775 搜索 bug 2015-01-04 16:37:13 +08:00
Liang Ding 969f10a94e directory context menu 2015-01-04 16:21:39 +08:00
Liang Ding f0334afb67 . 2015-01-04 16:20:41 +08:00
Van cfd5027396 关闭所有 bug 2015-01-04 15:31:03 +08:00
Van 5f22b9a6a7 黑色编辑器右下角白块修改 2015-01-04 11:28:56 +08:00
Van 97716c8dc6 Merge branch 'master' of https://github.com/b3log/wide
Conflicts:
	static/js/wide.js
2015-01-04 11:25:02 +08:00
Van 51f41a10d1 弹出层bug 修改 2015-01-04 11:21:44 +08:00
Liang Ding 1ed57dc5da #199 2015-01-04 10:11:08 +08:00
Van 3bdba1a2fa outline & menu bug 2015-01-03 16:01:24 +08:00
Liang Ding ef32199984 #199 2015-01-03 15:18:13 +08:00
Liang Ding 287fc618a0 #199 2015-01-03 12:39:00 +08:00
Van 0d51c6c55d outline 2015-01-02 23:14:41 +08:00
Van d3d5edb16d outline 2015-01-02 18:14:58 +08:00
Liang Ding 6d0b928b0e signup 2015-01-02 17:56:13 +08:00
Liang Ding 00145e662b . 2015-01-02 17:55:59 +08:00
Van a588c3ed5c outline 2015-01-01 22:15:54 +08:00
Liang Ding 82b6da0a41 Dockerfile go get golang.org/x/tools/cmd/vet 2015-01-01 10:24:10 +08:00
Liang Ding 77af34ab0e Dockerfile go get golang.org/x/tools/cmd/vet 2015-01-01 10:19:17 +08:00
Liang Ding 92d922f66b license header ©️ 2015-01-01 10:06:33 +08:00
Liang Ding d641800309 ©️ year 2015-01-01 09:24:16 +08:00
Liang Ding 72e54f6509 Fix #182 2014-12-31 18:02:04 +08:00
Liang Ding d900b13174 . 2014-12-31 16:53:34 +08:00
Liang Ding 0202b7d477 Update README.md 2014-12-31 15:11:05 +08:00
Liang Ding 2a74baecad Update README.md 2014-12-31 15:10:21 +08:00
Liang Ding 8ac62de7c7 Update README.md 2014-12-31 14:24:14 +08:00
Liang Ding c672903794 Update CodeMirror from 4.8 to 4.10 2014-12-30 17:45:45 +08:00
Liang Ding 8188d7d89c . 2014-12-30 17:08:15 +08:00
Liang Ding 3de0437227 #199 2014-12-30 16:51:19 +08:00
Liang Ding 8d0ae1d7d1 #199 2014-12-30 12:05:06 +08:00
Liang Ding edbecccec3 logging 2014-12-30 11:02:09 +08:00
Liang Ding a5666c5b5e . 2014-12-29 14:16:14 +08:00
Liang Ding cef12a03c7 logging 2014-12-26 12:02:57 +08:00
Liang Ding ea84e1e0a7 add http demo 2014-12-25 23:59:32 +08:00
Liang Ding b19ab097e1 🐛 2014-12-25 22:13:47 +08:00
Van 6f7067f052 SEO 2014-12-25 17:08:58 +08:00
Van 44d63749b8 go api 右键不弹窗 2014-12-25 16:34:35 +08:00
Liang Ding d75e52b648 . 2014-12-25 16:08:57 +08:00
Liang Ding bd1d3ffca4 Fix #33 2014-12-25 10:42:04 +08:00
Liang Ding 8a6e4a943c . 2014-12-25 10:41:19 +08:00
Liang Ding 0822aa19ed Update README.md 2014-12-25 09:41:18 +08:00
Liang Ding 59b376c93e Fix update password error bug 🐛 2014-12-24 23:17:32 +08:00
Liang Ding 27f073cbbf logging 🎸 2014-12-24 22:08:48 +08:00
Liang Ding 39a3ac13d2 Fix CPU Hang 2014-12-24 13:41:49 +08:00
Liang Ding ea0bf4bae0 . 2014-12-24 12:35:48 +08:00
Liang Ding 2825d347b4 add profiling interface 2014-12-24 10:18:32 +08:00
Liang Ding 4b8c7e7183 logging 2014-12-24 01:22:09 +08:00
Liang Ding 66b82e31d4 logging 2014-12-24 00:35:55 +08:00
Liang Ding c0c6609e83 Fix #189 2014-12-24 00:14:03 +08:00
Van 1372fbd88c Merge branch 'master' of https://github.com/b3log/wide 2014-12-23 17:25:53 +08:00
Van a9a09b44da tab bug 2014-12-23 17:25:07 +08:00
Liang Ding 4e37625c93 logging 2014-12-23 17:25:00 +08:00
Liang Ding 243ddd63c4 reporting 2014-12-23 16:15:45 +08:00
Liang Ding 991cd7f606 reporting 2014-12-23 16:03:09 +08:00
Liang Ding d94e6759bf Merge branch 'master' of https://github.com/b3log/wide 2014-12-23 15:43:43 +08:00
Liang Ding 4f9fc21ec1 . 2014-12-23 15:43:30 +08:00
Van 56238e26e3 搜索bug 2014-12-23 15:40:28 +08:00
Liang Ding 4ba5c4925d Merge branch 'master' of https://github.com/b3log/wide 2014-12-23 11:50:17 +08:00
Liang Ding 2970338289 search 2014-12-23 11:50:01 +08:00
Van bafbcad93b clear bug 2014-12-23 11:32:05 +08:00
Liang Ding 6d2a043970 default logging level to debug 2014-12-23 11:15:25 +08:00
Liang Ding 5ecffc15ea Merge branch 'master' of https://github.com/b3log/wide 2014-12-23 10:44:44 +08:00
Liang Ding 9a49f753d9 Try to resolve #171 2014-12-23 10:44:27 +08:00
Van 21ea03de49 Merge branch 'master' of https://github.com/b3log/wide 2014-12-23 10:03:21 +08:00
Van 1a1c929bab 代码被覆盖问题修改 2014-12-23 10:03:03 +08:00
Liang Ding 594a02115b logging 2014-12-23 09:45:03 +08:00
Liang Ding 2dface044e . 2014-12-23 09:35:29 +08:00
Liang Ding 1f552a7130 . 2014-12-22 22:52:05 +08:00
Liang Ding 15d2d6523c . 2014-12-22 22:39:54 +08:00
Liang Ding e08eaecf81 . 2014-12-22 22:16:35 +08:00
Liang Ding 8255ae6934 . 2014-12-22 21:53:30 +08:00
Liang Ding c1dd898021 . 2014-12-22 21:19:13 +08:00
Van b394ac44b4 tab 颜色 2014-12-22 18:13:50 +08:00
Liang Ding 65dc95b10e test coverage 2014-12-22 14:20:55 +08:00
Liang Ding 06477f6a37 Update README.md 2014-12-22 14:09:06 +08:00
Liang Ding 8265fb302e test coverage 2014-12-22 11:25:17 +08:00
Liang Ding 7b40eab657 test coverage 2014-12-21 12:07:33 +08:00
Liang Ding 261f20abef test coverage 2014-12-21 11:59:12 +08:00
Liang Ding 20219fbac8 test coerage 2014-12-21 11:32:22 +08:00
Liang Ding 98f6e2fbe7 test coverage 2014-12-21 11:06:10 +08:00
Liang Ding a9326e876b . 2014-12-21 10:33:27 +08:00
Liang Ding 75353f5e70 add license header ©️ 2014-12-20 20:39:45 +08:00
Liang Ding 7478085182 . 2014-12-20 20:25:14 +08:00
Liang Ding 69e42bc987 . 2014-12-20 20:24:45 +08:00
Liang Ding a0a9a6e936 Update .travis.yml 2014-12-20 19:25:55 +08:00
Liang Ding de00beb8c4 Update clean.sh 2014-12-20 19:25:17 +08:00
Liang Ding 8381ba2bc5 Update .travis.yml 2014-12-20 19:16:40 +08:00
Liang Ding 48a78db2c5 . 2014-12-20 19:12:25 +08:00
Liang Ding 1ee7a1c291 Update clean.sh 2014-12-20 18:55:26 +08:00
Liang Ding f867d28386 Update clean.sh 2014-12-20 18:50:06 +08:00
Liang Ding 4c86d5b6e1 Update .travis.yml 2014-12-20 18:47:58 +08:00
Liang Ding 83e2784770 Update .travis.yml 2014-12-20 18:45:59 +08:00
Liang Ding 4aa516f6f9 Update .travis.yml 2014-12-20 18:40:44 +08:00
Liang Ding 21f1c086af Update .travis.yml 2014-12-20 18:34:10 +08:00
Liang Ding ae34fa162d Create clean.sh 2014-12-20 18:32:43 +08:00
Liang Ding 9a8d0ea83d Update .travis.yml 2014-12-20 18:04:25 +08:00
Liang Ding ba08da858b Update .travis.yml 2014-12-20 18:00:49 +08:00
Liang Ding c30ba7504c unit tests 🏮 2014-12-20 17:53:37 +08:00
Liang Ding a42152e43f Update .travis.yml 2014-12-20 12:14:11 +08:00
Liang Ding 357c8629e3 Update README.md 2014-12-20 12:02:36 +08:00
Liang Ding 0f3ff47324 Update README.md 2014-12-20 11:33:32 +08:00
Liang Ding 6404c5a1f7 Update README.md 2014-12-20 11:33:05 +08:00
Liang Ding 4f8070e74e Update .travis.yml 2014-12-19 21:38:02 +08:00
Liang Ding 524d022787 . 2014-12-19 17:44:50 +08:00
Liang Ding 2bb0a92ddb . 2014-12-19 17:20:04 +08:00
Van 490d48734d heko 2014-12-19 15:46:26 +08:00
Liang Ding 3b359d92d8 separate users' configurations from wide.json 2014-12-19 15:43:59 +08:00
Liang Ding 24c29e176e . 2014-12-18 23:04:41 +08:00
Liang Ding 20ac7c89d4 don't pop window when export file 🐨 2014-12-18 22:50:24 +08:00
Liang Ding c896602207 . 2014-12-18 22:03:49 +08:00
Liang Ding 2b6ed90817 . 2014-12-18 21:39:42 +08:00
Van 6fb88282a3 Merge branch 'master' of https://github.com/b3log/wide 2014-12-18 18:04:25 +08:00
Van 53a2350dfd . 2014-12-18 18:04:12 +08:00
Liang Ding d924bad508 don't pop window when export file 2014-12-18 18:00:36 +08:00
Van de20c46e4e Merge branch 'master' of https://github.com/b3log/wide 2014-12-18 16:56:32 +08:00
Van 7da9a49d2c 树菜单弹出位置优化 2014-12-18 16:56:21 +08:00
Liang Ding 3f9937e767 Merge branch 'master' of https://github.com/b3log/wide 2014-12-18 16:48:01 +08:00
Van ffa61c83b2 Merge branch 'master' of https://github.com/b3log/wide 2014-12-18 16:47:01 +08:00
Van cee226e672 add export and import for menu 2014-12-18 16:46:38 +08:00
Liang Ding 78a827ebcd share 🎁 2014-12-18 16:46:34 +08:00
Liang Ding a7369bcea4 share 2014-12-18 16:20:51 +08:00
Liang Ding dc31495232 logging 2014-12-18 15:54:58 +08:00
Liang Ding 3a03be54eb set namespace when fork child process 2014-12-18 15:38:57 +08:00
Liang Ding 221b419ca6 . 2014-12-18 15:20:36 +08:00
Liang Ding 8a61139817 Update README.md 2014-12-18 13:13:21 +08:00
Liang Ding c86b7568f0 . 2014-12-18 12:06:07 +08:00
Liang Ding dd7cdc30a6 . 2014-12-18 11:55:30 +08:00
Liang Ding 06f218d7e9 . 2014-12-18 11:41:46 +08:00
Liang Ding 8c689a5d39 2014-12-18 11:40:53 +08:00
Liang Ding 7044c970a3 . 2014-12-18 11:21:24 +08:00
Liang Ding b431ce5dff . 2014-12-18 11:11:56 +08:00
Liang Ding 9f538e0477 . 2014-12-18 11:01:54 +08:00
Liang Ding e3df23a09e . 2014-12-18 10:47:59 +08:00
Liang Ding 10a39edcf2 . 2014-12-18 10:12:31 +08:00
Liang Ding 780a313437 . 2014-12-18 10:03:23 +08:00
Liang Ding 8e6d5c860a . 2014-12-17 18:31:44 +08:00
Liang Ding 2eecdc2a84 . 2014-12-17 18:19:46 +08:00
Liang Ding b8c786bd50 . 2014-12-17 18:08:05 +08:00
Liang Ding 7bf1c80e1e . 2014-12-17 17:58:31 +08:00
Liang Ding d5a70c5b1a . 2014-12-17 17:46:48 +08:00
Liang Ding a9897de8e7 . 2014-12-17 17:30:37 +08:00
Liang Ding 9d76269667 . 2014-12-17 17:20:04 +08:00
Liang Ding a7890c021d . 2014-12-17 16:08:13 +08:00
Liang Ding 9dcda91ced try to use linux namespace to separate wide and client program 2014-12-17 15:38:02 +08:00
Liang Ding c906f6e5c6 Update README.md 2014-12-16 16:52:07 +08:00
Liang Ding 3b4092e457 Update README.md 2014-12-16 16:51:31 +08:00
Van 782b937665 goto file can't focus 2014-12-16 16:34:00 +08:00
Liang Ding 2d3791bdb9 logging 2014-12-16 15:54:53 +08:00
Liang Ding 27ae6f2013 logging 2014-12-16 11:45:19 +08:00
Liang Ding 471312735d . 2014-12-15 15:51:51 +08:00
Liang Ding af3b48fe89 . 2014-12-15 15:50:52 +08:00
Liang Ding a0b806acca add open file to file context menu 🍭 2014-12-15 13:50:39 +08:00
Liang Ding 9c91c49f25 add login btn 2014-12-15 11:32:33 +08:00
Liang Ding 00f5c01aeb community ssl 2014-12-15 10:30:42 +08:00
Liang Ding a2c68deee4 💎 2014-12-15 00:24:36 +08:00
Liang Ding 7997393521 . 2014-12-14 23:16:11 +08:00
Liang Ding abfd4ad782 . 2014-12-14 23:05:54 +08:00
Liang Ding a8c5bd20a2 ico-stop, ico-run 样式修改 2014-12-14 21:58:49 +08:00
Liang Ding 24dfb16d23 Merge branch 'master' of https://github.com/b3log/wide 2014-12-14 17:44:00 +08:00
Liang Ding 9178591046 . 2014-12-14 17:43:46 +08:00
Van 9967b88291 移除 http 2014-12-14 17:33:18 +08:00
Van b791847ca0 avatar 2014-12-14 16:40:45 +08:00
Liang Ding efcbeb5b1a Wide 1.1.0 2014-12-14 15:24:46 +08:00
Liang Ding 8180413359 share 2014-12-14 13:06:53 +08:00
Liang Ding 9cb63171c1 gravatar 2014-12-14 12:27:35 +08:00
Van 9cc20fe032 Merge branch 'master' of https://github.com/b3log/wide 2014-12-14 12:21:17 +08:00
Van 9d08a061aa share 2014-12-14 12:20:39 +08:00
Liang Ding 595207279f gravatar 2014-12-14 10:36:25 +08:00
Liang Ding 5bc4dfd088 gavatar 2014-12-14 10:19:23 +08:00
Liang Ding 4856b4477f logging 2014-12-14 01:01:54 +08:00
Liang Ding 2358e03adb logging 2014-12-14 00:43:55 +08:00
Liang Ding 391b41eb4a Merge branch 'master' of https://github.com/b3log/wide 2014-12-13 23:58:00 +08:00
Liang Ding 6fb252187e Dockerfile :octocat: 2014-12-13 23:57:08 +08:00
Van 85a78cee9e Merge branch 'master' of https://github.com/b3log/wide 2014-12-13 23:02:07 +08:00
Van 38b94b5b33 . 2014-12-13 23:02:24 +08:00
Van 4ecb60d4f2 run 2014-12-13 22:58:43 +08:00
Liang Ding ffef5c005b Dockerfile :octocat: 2014-12-13 22:49:11 +08:00
Liang Ding e8808a0329 Dockerfile :octocat: 2014-12-13 22:25:11 +08:00
Liang Ding 67d931fcf8 Dockerfile :octocat: 2014-12-13 22:09:23 +08:00
Liang Ding 1659e587d2 Dockerfile :octocat: 2014-12-13 22:03:55 +08:00
Liang Ding 970a06550d logging 2014-12-13 21:32:44 +08:00
Van 8d97b3af07 add ico 2014-12-13 20:10:28 +08:00
Liang Ding d18fc38d76 Dockerfile :octocat: 2014-12-13 19:33:02 +08:00
Liang Ding a750f10401 logging level 🐃 2014-12-13 18:53:48 +08:00
Liang Ding cb14485388 new logging 2014-12-13 18:47:41 +08:00
Liang Ding 27f0897faa package log 2014-12-12 16:50:59 +08:00
Liang Ding f789742434 Fix #176 🌜 2014-12-12 00:11:35 +08:00
Liang Ding 0a1e3eb9b8 Fix #77 2014-12-11 23:11:10 +08:00
Liang Ding 29ae107de4 add context path support 2014-12-11 15:59:58 +08:00
Liang Ding 56f3fa1cf6 add context path support 2014-12-11 15:32:24 +08:00
Liang Ding c3a3862699 change HTML title 2014-12-11 11:40:30 +08:00
Van 321462a508 Merge branch 'master' of https://github.com/b3log/wide 2014-12-09 16:47:20 +08:00
Van f0058c3602 菜单 2014-12-09 16:46:52 +08:00
Liang Ding bec326622e Update README.md 2014-12-09 16:35:12 +08:00
Van 41ba5a2d1a preference tip 多语言修改 2014-12-09 15:07:26 +08:00
Liang Ding ed40c5b5f6 gzip IDE index 2014-12-09 11:51:14 +08:00
Liang Ding fac3adc11b optimize file tree loading 2014-12-09 10:36:33 +08:00
Liang Ding a1fde02879 preference options sort 🍉 2014-12-08 17:43:25 +08:00
Liang Ding 2333e1d422 . 2014-12-08 16:11:43 +08:00
Van e001df500e preference 校验机制及重构 2014-12-08 15:18:28 +08:00
Liang Ding 332213e3cf add user email 📧 2014-12-08 14:02:39 +08:00
Liang Ding 86cf85d24e :octocat: 2014-12-07 17:20:10 +08:00
Liang Ding 3b916aeaad report 2014-12-07 12:08:10 +08:00
Liang Ding aa746186eb refactor 💃 2014-12-07 11:42:34 +08:00
Liang Ding 5499441b8e refactor 2014-12-07 11:33:56 +08:00
Liang Ding 1285a70612 refactor 2014-12-07 11:32:46 +08:00
Liang Ding d4b682a573 refactor 2014-12-07 11:30:53 +08:00
Liang Ding 65f9b77fb5 refactor 2014-12-07 11:29:45 +08:00
Liang Ding 6e7bfa5040 refactor 2014-12-07 11:22:05 +08:00
Liang Ding e74dcc806d refactor 2014-12-07 11:14:10 +08:00
Liang Ding aa479f5054 refactor 2014-12-07 11:12:27 +08:00
Liang Ding dfde3ef339 refactor 2014-12-07 11:07:32 +08:00
Liang Ding f54d1a4259 . 2014-12-07 10:06:46 +08:00
Liang Ding 3cf4e391d4 File tree refresh bind F5 🍅 2014-12-07 09:33:27 +08:00
Liang Ding a323e3a4b8 #176 🍒
Source
2014-12-06 21:56:05 +08:00
Liang Ding c208c63a1e . 2014-12-06 20:52:23 +08:00
Van 23823cb114 . 2014-12-05 18:04:29 +08:00
Van 3eadc12b44 Merge branch 'master' of https://github.com/b3log/wide 2014-12-05 18:00:02 +08:00
Van 43b4cd8d98 菜单功能添加 2014-12-05 17:59:42 +08:00
Liang Ding ec6267f0d8 stat 2014-12-05 17:31:21 +08:00
Liang Ding 6600fec368 #176 2014-12-05 15:16:08 +08:00
Liang Ding d970a2e342 #176 2014-12-05 15:08:44 +08:00
Liang Ding 28ea0769f6 . 2014-12-05 14:43:54 +08:00
Liang Ding 5846709c25 . 2014-12-05 14:14:28 +08:00
Liang Ding 6b2e91540c . 2014-12-05 14:09:53 +08:00
Liang Ding 34845de308 refactor new user ♻️ 2014-12-05 13:44:59 +08:00
Liang Ding 8b2ccb3214 #176 🏃
@Vanessa219
2014-12-05 11:50:46 +08:00
Liang Ding 938b82a42d autocomplete enhance 2014-12-04 17:59:52 +08:00
Van c0bbfffc16 自动补全为 fun 时,添加 () 2014-12-04 15:15:15 +08:00
Van 78accaa82e 快捷键关闭当前编辑器 bug 修复 & 关闭编辑器后焦点设置 2014-12-04 15:00:04 +08:00
Liang Ding c9443a8851 Update README.md 2014-12-04 13:51:11 +08:00
Liang Ding f9918d5483 Update README.md 2014-12-03 17:44:07 +08:00
Liang Ding 7c10fba39d Update README.md 2014-12-03 17:41:53 +08:00
Liang Ding 901b3a9817 Fix #177 2014-12-03 17:25:06 +08:00
Van fd65b031f6 fixed #177 2014-12-03 17:03:09 +08:00
Liang Ding 42812d56fb #177 2014-12-03 17:00:24 +08:00
Liang Ding fedbad35d6 #177 2014-12-03 16:40:45 +08:00
Liang Ding e9a1c45d01 #177 2014-12-03 15:34:56 +08:00
Liang Ding 23b0fb25e5 #177 2014-12-03 15:26:52 +08:00
Van 4ddb20c056 Merge branch 'master' of https://github.com/b3log/wide 2014-12-03 14:38:09 +08:00
Van 3c36469684 横向滚动样式 2014-12-03 14:37:59 +08:00
Liang Ding e074275ac7 i18n 2014-12-03 13:53:50 +08:00
Van 02c1b0b5d6 在树上添加 Ctrl+R 重命名快捷键 2014-12-03 11:40:41 +08:00
Van 1915b56e33 fixed# 179 2014-12-03 11:26:05 +08:00
Liang Ding ea6b058cdf Update README.md 2014-12-02 20:58:57 +08:00
Liang Ding 71dfcdae94 Update README.md 2014-12-02 20:56:31 +08:00
Liang Ding ca37a93012 #181 i18n 2014-12-02 15:29:19 +08:00
Van 3c68538ebf fixed #181 2014-12-02 15:17:25 +08:00
Liang Ding b65f20b38c . 2014-12-02 11:45:52 +08:00
Liang Ding 91c4a5f4b4 . 2014-12-02 11:31:16 +08:00
Van 5dccf1f381 fixed #178 2014-12-02 11:09:53 +08:00
Van 6efeb4b9d0 bottom-window-group 高度修改 2014-12-01 17:15:54 +08:00
Van b96423c772 黑色主题样式修改 2014-12-01 16:54:22 +08:00
Van 65adc14324 fixed #174 2014-12-01 15:27:03 +08:00
Van 5962faf21c 冲突啊,不要改前端代码先 2014-12-01 15:27:00 +08:00
Van f74f1ae71c 。。 2014-12-01 15:26:56 +08:00
Liang Ding 330179f4f1 . 2014-12-01 15:20:30 +08:00
Van 539c4f279d Merge branch 'master' of https://github.com/b3log/wide 2014-12-01 15:06:00 +08:00
Van d6fc3c7d52 . 2014-12-01 15:05:48 +08:00
Liang Ding 15c0b20a9f Merge remote-tracking branch 'origin/master'
Conflicts:
	views/index.html
2014-12-01 14:51:46 +08:00
Liang Ding d6b8cc4360 Fix #172 2014-12-01 14:49:16 +08:00
Van 39601d2d3a fixed #173 2014-12-01 14:40:44 +08:00
Liang Ding 80185b71e7 Update README.md 2014-12-01 11:01:35 +08:00
Liang Ding 5275140b3c Update README.md 2014-12-01 10:59:42 +08:00
Liang Ding 5121d1bda7 Update README.md 2014-12-01 10:53:43 +08:00
Liang Ding ca53aa2880 Update README.md 2014-11-30 15:25:52 +08:00
Liang Ding bea92c98f8 Upgrade to CodeMirror 4.8 2014-11-30 10:24:24 +08:00
Liang Ding b5b859d569 #17 2014-11-30 10:18:17 +08:00
Liang Ding 78ce9cf061 新样式 2014-11-29 21:49:57 +08:00
Liang Ding ece87e3ef1 Fix #155 2014-11-28 23:12:38 +08:00
Liang Ding 0a34ca5638 . 2014-11-28 18:03:23 +08:00
Liang Ding a1be594d87 . 2014-11-28 18:02:34 +08:00
Liang Ding 50b2daa9f7 Fix #169 2014-11-28 15:02:32 +08:00
Liang Ding 58e54a1fa0 . 2014-11-28 14:02:02 +08:00
Liang Ding e64eb89c2f . 2014-11-28 10:55:46 +08:00
Liang Ding 0772adb9f1 Update README.md 2014-11-28 10:03:26 +08:00
Liang Ding cf3ad14efd Create .travis.yml 2014-11-28 09:59:11 +08:00
Liang Ding 34d9dedece Update README.md 2014-11-28 09:57:16 +08:00
Liang Ding c3811c68e6 Update README.md 2014-11-28 09:56:48 +08:00
Liang Ding 942d1f31b2 Update README.md 2014-11-28 09:55:58 +08:00
Liang Ding 6842f8ad45 Update README.md 2014-11-28 09:24:45 +08:00
Liang Ding f1cc4aa252 Update README.md 2014-11-27 21:12:58 +08:00
Liang Ding 3a7aa8d1d7 Merge pull request #170 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2014-11-27 21:12:15 +08:00
The Gitter Badger b553314104 Added Gitter badge 2014-11-27 13:09:49 +00:00
Liang Ding 7aa7f84d1a . 2014-11-27 09:46:09 +08:00
Liang Ding c90bdfe0e8 Merge pull request #166 from xbreezes/master
Fix #165
2014-11-27 09:27:03 +08:00
Qiao 9a480e7507 #163 ! 2014-11-26 23:20:45 +08:00
Qiao f87393933c #163 2014-11-26 23:09:17 +08:00
Qiao 3602d8a987 Merge pull request #1 from b3log/master
merged with newer
2014-11-26 23:00:58 +08:00
Liang Ding 533a1a3550 logging level 2014-11-26 13:53:24 +08:00
Liang Ding 3586ee1c73 HTTP Session expire validate 2014-11-26 13:49:27 +08:00
Liang Ding 61ef48147b prefs select option enhance 2014-11-26 11:54:01 +08:00
Liang Ding 5ea21eb25f prefs select option enhance 2014-11-26 11:24:24 +08:00
Liang Ding a579872b9c Fix #164 2014-11-26 11:07:06 +08:00
Liang Ding 8564040c04 Update README.md 2014-11-25 15:37:31 +08:00
Liang Ding 84c2488137 Update README.md 2014-11-25 15:35:21 +08:00
Liang Ding 0914185b62 Fix #163 2014-11-25 15:00:37 +08:00
Liang Ding f892a7138d 🍵 Fix #158 2014-11-25 14:20:45 +08:00
Liang Ding 2bf2272ac1 ©️ add license header 2014-11-24 17:24:35 +08:00
Liang Ding 62947f4501 Fix #156 2014-11-24 17:20:43 +08:00
Liang Ding 26823c4cb9 #156 retrieve zip file 2014-11-24 11:17:42 +08:00
Liang Ding a2d4817e15 bf 2014-11-24 10:48:37 +08:00
Liang Ding bdfba6c96d #156 2014-11-24 10:39:33 +08:00
Liang Ding 5a9fa25702 check run 2014-11-23 14:50:42 +08:00
Liang Ding c35371de1b sign up UI details 2014-11-23 14:46:16 +08:00
Liang Ding 197385b414 check run
Wide can't run in the OS' temp directory and it can't run with `go run`
2014-11-23 14:45:55 +08:00
Liang Ding 8de1b0a0bc Update README.md 2014-11-22 16:49:02 +08:00
Liang Ding f3fe6be22a Update README.md 2014-11-22 16:48:37 +08:00
Liang Ding b684a5f686 Update README.md 2014-11-22 16:47:38 +08:00
Liang Ding c5d451a398 🍉 check file size when open file 2014-11-21 18:03:48 +08:00
Liang Ding 04807e032d refactor 2014-11-21 17:41:51 +08:00
Liang Ding 9450d49e8c 💡 adjust logging level 2014-11-21 15:23:28 +08:00
Liang Ding 091efbd23e . 2014-11-21 14:43:58 +08:00
Liang Ding e64213ed86 👻 add signup button 2014-11-21 14:39:56 +08:00
Liang Ding 7978606c0e adjust logging level 2014-11-21 14:22:12 +08:00
Liang Ding 7d4e5e82d5 adjust logging level 2014-11-21 14:20:15 +08:00
Liang Ding 3c428dd5a4 online users' report with more pretty format 2014-11-21 14:08:29 +08:00
Liang Ding 02a6004188 🌠 online stat. 2014-11-21 11:19:57 +08:00
Liang Ding 10d8ee13ab logging 2014-11-20 22:53:54 +08:00
Liang Ding 091141cbb0 logging 2014-11-20 17:13:01 +08:00
Liang Ding deab5e6507 . 2014-11-20 17:04:18 +08:00
Liang Ding 5668598ea6 . 2014-11-20 16:52:53 +08:00
Liang Ding dec6205e4f 💙 2014-11-20 16:40:39 +08:00
Liang Ding 7cefee2c80 :octocat: 2014-11-20 16:08:17 +08:00
Liang Ding 80ef5a22bc update Dockerfile 2014-11-20 16:05:39 +08:00
Liang Ding b39fa3ec8e update dockerfile 2014-11-20 16:00:46 +08:00
Liang Ding 714836408a update dockerfile 2014-11-20 15:52:44 +08:00
Liang Ding a94c17e984 . 2014-11-20 14:57:38 +08:00
Liang Ding e6607b3bef 🔥 channel enhance 2014-11-20 14:11:54 +08:00
Liang Ding f006e6bfb4 . 2014-11-20 14:00:11 +08:00
Liang Ding d1b6a3e32d websocket channel enhance 2014-11-20 13:59:08 +08:00
Liang Ding 6279324854 update README.md 2014-11-20 13:20:21 +08:00
Liang Ding 871e9224ca websocket channel init enchance 2014-11-20 11:30:18 +08:00
Liang Ding 24908aa309 preference apply; user signup bug fix 2014-11-20 11:17:43 +08:00
Liang Ding 7218eef212 logging 2014-11-20 10:05:55 +08:00
Liang Ding eb8e5133ab fix npd 2014-11-20 00:18:26 +08:00
Liang Ding ae6c14b16f Update README.md 2014-11-19 23:50:00 +08:00
Liang Ding f3c0edde4f generate `Hello, 世界` demo code when creating user 2014-11-19 21:58:26 +08:00
Liang Ding 289257503a . 2014-11-19 16:40:51 +08:00
Liang Ding a7eb2a0f15 . 2014-11-19 16:13:46 +08:00
Liang Ding fa7ea49739 . 2014-11-19 16:05:58 +08:00
Liang Ding 14a01a3b78 . 2014-11-19 15:44:41 +08:00
Liang Ding 18eaadbe67 . 2014-11-19 15:30:15 +08:00
Liang Ding 8d5fea50ae file tree performance optimize 2014-11-19 15:20:06 +08:00
Liang Ding 13d7250744 . 2014-11-19 15:20:01 +08:00
Liang Ding a07f3f5c8c . 2014-11-19 15:08:20 +08:00
Liang Ding 18d7e8af2d . 2014-11-19 13:57:58 +08:00
Liang Ding 0696b21027 . 2014-11-19 13:37:11 +08:00
Liang Ding 21324808d2 . 2014-11-19 13:31:17 +08:00
Liang Ding 331e7d2fc4 i18n 2014-11-19 12:30:54 +08:00
Liang Ding fc8aa0e2a3 F5 - Build 2014-11-19 09:39:06 +08:00
Liang Ding d27dfaa621 . 2014-11-19 09:32:25 +08:00
Liang Ding b8cba6f400 auto update StaticResourceVersion when starting 2014-11-18 16:56:33 +08:00
Liang Ding be8fc6fb33 add setup with docker 2014-11-18 16:38:49 +08:00
Liang Ding d1b0733493 . 2014-11-18 15:42:11 +08:00
Liang Ding 260bd543ad . 2014-11-18 15:32:55 +08:00
Liang Ding 9c701edb86 . 2014-11-18 15:26:31 +08:00
Liang Ding 5f25d955a9 . 2014-11-18 15:09:26 +08:00
Liang Ding 10f8f9f86c containerize 2014-11-18 15:05:01 +08:00
Liang Ding 8b1ffe0544 containerize 2014-11-18 14:48:18 +08:00
Liang Ding aeef2afb2e . 2014-11-18 14:04:24 +08:00
Liang Ding 8d0504894a . 2014-11-18 13:41:24 +08:00
Liang Ding 1a1a09edfe cmd args ip and port 2014-11-18 13:34:13 +08:00
Liang Ding 56fc7de68a . 2014-11-18 13:16:13 +08:00
Liang Ding 95faadf852 . 2014-11-18 12:46:04 +08:00
Liang Ding e89726844b update static server conf to "" 2014-11-18 12:27:30 +08:00
Liang Ding 436ca38539 docker 2014-11-18 12:01:17 +08:00
Van 1980f5bb65 , 2014-11-18 10:53:26 +08:00
Van dbe1bf2a75 Merge branch 'master' of https://github.com/b3log/wide 2014-11-18 10:51:43 +08:00
Van 6381e16287 . 2014-11-18 10:51:26 +08:00
Liang Ding 79cd7557ff docker image autobuild 2014-11-18 10:33:26 +08:00
Van 1b60c8712e refactor 2014-11-18 10:29:08 +08:00
Van 97a6b1b69f refactor 2014-11-18 10:03:08 +08:00
Liang Ding f74ee1cac4 . 2014-11-18 09:32:06 +08:00
Van 8375ae1130 切换,关闭,打开 tab,底部行列修改 & rename 时全选 2014-11-17 18:09:58 +08:00
Liang Ding 31ef718244 :octocat: daily build 2014-11-17 17:48:32 +08:00
Van 489c4fb4ef go file 完善 2014-11-17 17:38:26 +08:00
Liang Ding 3d4eaa6cea . 2014-11-17 14:49:03 +08:00
Liang Ding 0315c04af8 . 2014-11-17 14:44:58 +08:00
Liang Ding 4563f6691b . 2014-11-17 14:44:04 +08:00
Liang Ding 97f37b7bc2 i18n 2014-11-17 14:37:00 +08:00
Van a470c9869a preference 2014-11-17 14:15:28 +08:00
Liang Ding 05fdf6fb36 . 2014-11-17 11:56:26 +08:00
Van 9c9fb1049b Merge branch 'master' of https://github.com/b3log/wide 2014-11-17 11:37:38 +08:00
Van 2252a50979 树节点初始化状态 bug 修改 2014-11-17 11:37:26 +08:00
Liang Ding 399a68e413 #143 2014-11-17 11:20:35 +08:00
Liang Ding e06197db4c . 2014-11-17 10:49:25 +08:00
Liang Ding 2f0643c940 . 2014-11-16 22:25:50 +08:00
Liang Ding eac57d05ba Fix #153 2014-11-16 22:25:27 +08:00
Liang Ding 18b6be23c1 . 2014-11-16 21:31:07 +08:00
Liang Ding 9ff8924b94 . 2014-11-16 21:24:19 +08:00
Liang Ding 8f93ff0784 . 2014-11-16 21:14:01 +08:00
Liang Ding 315e3b7273 . 2014-11-16 21:13:16 +08:00
Liang Ding d74a53b6c8 #143 2014-11-16 20:45:12 +08:00
Liang Ding f83ec4faaa Fix #150 2014-11-16 17:09:57 +08:00
Liang Ding 625cca8f61 Update README.md 2014-11-16 16:57:48 +08:00
Liang Ding 807823432f . 2014-11-16 16:29:11 +08:00
Liang Ding c0ebee45dc file utils refactor 2014-11-16 16:25:27 +08:00
Liang Ding ac6edadf90 #139 doc 2014-11-14 17:51:06 +08:00
Liang Ding 84de160a09 add preference handler 2014-11-14 17:36:52 +08:00
Liang Ding 13414415a2 add preference handler 2014-11-14 17:34:21 +08:00
Liang Ding 4168a7c0ce Merge branch 'master' of https://github.com/b3log/wide 2014-11-14 17:34:01 +08:00
Liang Ding 851a19bb60 ignore some VCS files when find 2014-11-14 17:31:16 +08:00
Van d1a6062829 preference 2014-11-14 17:22:57 +08:00
Van 5ab014d8c0 go file 2014-11-14 17:06:13 +08:00
Van da10a8e395 go file 2014-11-14 15:04:06 +08:00
Liang Ding 6a5d86fea9 adjust line height 2014-11-14 13:55:42 +08:00
Liang Ding dda620a6d0 adjust line height, make it could customizable 2014-11-14 13:40:35 +08:00
Liang Ding c6f8ab342d 🈴 #139 2014-11-14 11:42:20 +08:00
Liang Ding 2fcff8d4f2 :octocat: daily build 2014-11-13 22:36:12 +08:00
Liang Ding 93f6958b9c go formmat 2014-11-13 22:28:28 +08:00
Liang Ding 5c341e18c3 . 2014-11-13 21:52:25 +08:00
Liang Ding 3c3a80c090 . 2014-11-13 21:40:11 +08:00
Liang Ding ff2563c51a set cursor size larger a little 2014-11-13 21:37:31 +08:00
Liang Ding a412fda809 fix cursor miss 2014-11-13 17:47:51 +08:00
Liang Ding bcdca8950f add file header 2014-11-13 17:12:02 +08:00
Liang Ding 7e93fcf20d #139 2014-11-13 17:00:29 +08:00
Liang Ding c40c055465 . 2014-11-13 14:12:21 +08:00
Liang Ding b0fce047be #139 2014-11-13 11:54:52 +08:00
Liang Ding c87cfc457d :octocat: daily build 2014-11-12 23:20:07 +08:00
Liang Ding 83b7ef55bd add file header 2014-11-12 23:17:59 +08:00
Liang Ding eeb01b16b6 add file header 2014-11-12 23:13:14 +08:00
Liang Ding ebf2032efb . 2014-11-12 23:12:31 +08:00
Liang Ding b5552c4172 file header tool configuration 2014-11-12 23:10:59 +08:00
Liang Ding 456f459dc4 file header tool configuration 2014-11-12 23:10:11 +08:00
Liang Ding a8d761f916 update package script 2014-11-12 14:56:59 +08:00
Liang Ding c400b82983 remove 'master workspace' 2014-11-12 14:49:14 +08:00
Liang Ding 439c94edd2 . 2014-11-12 14:33:06 +08:00
Liang Ding c362c3a4ef set admin user's workspace using $GOPATH by default 2014-11-12 14:17:35 +08:00
Liang Ding b903f6b907 add GoBuild to project credits list
@codeskyblue  🍇
2014-11-12 11:34:38 +08:00
Liang Ding e4ad515e7f . 2014-11-12 11:32:04 +08:00
Liang Ding a12b72654d Merge pull request #148 from codeskyblue/master
fix create user workspace failed in linux
2014-11-12 11:27:51 +08:00
codeskyblue 44bdcb9fb5 fix create user workspace failed in linux 2014-11-12 10:41:27 +08:00
Liang Ding bc08796f68 :octocat: daily build 2014-11-11 23:56:24 +08:00
Liang Ding 125cb10d24 . 2014-11-11 23:55:23 +08:00
Liang Ding aa192b13b3 :octocat: daily build 2014-11-10 22:55:01 +08:00
Van da8e7ad33a Shift-Alt-Down & Shift-Alt-Up 2014-11-10 18:03:26 +08:00
Van fd69181cce Shift-Ctrl-Up & Shift-Ctrl-Down 2014-11-10 17:20:18 +08:00
Liang Ding 287ea702c0 🆎 #135 2014-11-10 11:34:55 +08:00
Liang Ding 66cc9ceca9 . 2014-11-10 10:07:00 +08:00
Liang Ding ef1a106c8b :octocat: daily build 2014-11-09 23:03:03 +08:00
Liang Ding 024f6bb83b #135 2014-11-09 23:01:42 +08:00
Liang Ding 231f1490b8 🍉 Fix #131
Keep selection.
2014-11-09 19:26:47 +08:00
Liang Ding 0dfa779ebb 💛 Fix #131 2014-11-08 14:28:41 +08:00
Liang Ding 984d93773e :octocat: daily build 2014-11-07 23:20:18 +08:00
Liang Ding 1da973387d . 2014-11-07 23:17:56 +08:00
Liang Ding 8036401a3c Fix #138 2014-11-07 23:11:37 +08:00
Liang Ding 861d79fc93 Merge pull request #144 from xbreezes/master
#137
2014-11-07 21:57:58 +08:00
Qiao 79a7fa55ee Update utf8reader.go 2014-11-07 21:33:44 +08:00
Qiao 9bf2046a64 try fix 137 2014-11-07 13:30:06 +00:00
Liang Ding fa5babfe1c #138 2014-11-07 17:22:20 +08:00
Liang Ding 1f1d77a974 🔵 line locate refactor 2014-11-07 14:13:55 +08:00
Van e7c47bf4ae resize 2014-11-07 10:51:55 +08:00
Liang Ding dec2e5233b 2014-11-06 22:54:37 +08:00
Liang Ding 61f8d214d2 :octocat: #137
@jellycool escape the output
2014-11-06 22:50:16 +08:00
Liang Ding 7a255e6772 Fix #142 2014-11-06 21:46:20 +08:00
Liang Ding 25944922d6 Fix #140 2014-11-06 21:28:25 +08:00
Van 684087c0f6 Merge branch 'master' of https://github.com/b3log/wide 2014-11-06 17:55:18 +08:00
Van 1b790e196d new dir can remove and create 2014-11-06 17:55:07 +08:00
Liang Ding a09b688a15 Merge branch 'master' of https://github.com/b3log/wide 2014-11-06 17:48:39 +08:00
Liang Ding 880fd5a86d Build&Run 2014-11-06 17:48:03 +08:00
Van a3b9762d02 Merge branch 'master' of https://github.com/b3log/wide 2014-11-06 17:09:43 +08:00
Van 7a12131065 减小初始化界面抖动 2014-11-06 17:09:32 +08:00
Liang Ding 720cb0746b . 2014-11-06 17:06:53 +08:00
Van 10a60be62d 文件操作权限 2014-11-06 16:51:59 +08:00
Liang Ding 338f16b262 . 2014-11-06 15:52:52 +08:00
Van 6ae2ce8960 bug fixed 2014-11-06 15:47:05 +08:00
Van 6b4dcbbb5a Merge branch 'master' of https://github.com/b3log/wide 2014-11-06 15:04:43 +08:00
Van d2ada7dae0 Ctrl + C 清空output 2014-11-06 15:04:33 +08:00
Liang Ding d0e801b97c . 2014-11-06 15:00:06 +08:00
Liang Ding cc95dbe688 . 2014-11-06 14:58:35 +08:00
Van 34ef9a2494 Merge branch 'master' of https://github.com/b3log/wide 2014-11-06 14:41:56 +08:00
Van 63184be644 fix #125 2014-11-06 14:41:46 +08:00
Liang Ding a2c2c5115b Fix #134 2014-11-06 14:24:54 +08:00
Van a44de44ded bottomGroup refactor 2014-11-06 11:58:55 +08:00
Van d0061b38b8 change workspace & api ico 2014-11-06 10:44:50 +08:00
Liang Ding 475e5df0dc #134 2014-11-05 21:31:19 +08:00
Van 7652d9d459 Tab 上面加上右键菜单(关闭、关闭其他、关闭所有) 2014-11-05 16:41:53 +08:00
Liang Ding d6caae9252 Merge branch 'master' of https://github.com/b3log/wide 2014-11-05 15:25:35 +08:00
Liang Ding d2e011fe9e . 2014-11-05 15:24:58 +08:00
Van 591e04ae3c 菜单按下去时颜色需要更换,下拉菜单需要更具有立体感 2014-11-05 15:09:39 +08:00
Liang Ding c804921da1 . 2014-11-05 13:49:11 +08:00
Liang Ding adcb066b52 Fix #131 2014-11-05 11:36:13 +08:00
Liang Ding d5c6557426 . 2014-11-05 09:42:09 +08:00
Liang Ding b31f749add Fix #130 2014-11-05 00:45:07 +08:00
Liang Ding 9577f26cba viewport enhancement 2014-11-05 00:27:43 +08:00
Liang Ding 79103e4d92 . 2014-11-04 17:46:47 +08:00
Liang Ding a60ef9630a separate save and format 2014-11-04 16:20:51 +08:00
Liang Ding ef8be349a4 pakage tool 2014-11-03 22:25:28 +08:00
Liang Ding 18c1e7f17d package tool 2014-11-03 22:18:26 +08:00
Liang Ding 5d8c8e5d3a package tool 2014-11-03 22:07:04 +08:00
Liang Ding 957a909512 package tool 2014-11-03 14:24:49 +08:00
Liang Ding 164e8cc345 . 2014-11-02 23:04:43 +08:00
Liang Ding 0fad1e3e34 Fix #74 2014-11-02 15:44:24 +08:00
Liang Ding 91a8fdf871 . 2014-11-02 10:58:13 +08:00
Liang Ding b7634fa4a5 . 2014-11-02 10:48:34 +08:00
Liang Ding bb58dad40c Fix #128 2014-11-02 10:39:33 +08:00
Van c4ce4b6f7f bug fix 2014-11-01 21:49:41 +08:00
Van b28791eb31 Merge branch 'master' of https://github.com/b3log/wide 2014-11-01 21:28:26 +08:00
Van b496871322 关闭所有 2014-11-01 21:28:18 +08:00
Liang Ding 6806bd7ac2 Update README.md 2014-11-01 20:48:26 +08:00
Liang Ding b2e98180a8 . 2014-11-01 17:54:36 +08:00
Liang Ding d8c30c549e . 2014-11-01 17:45:43 +08:00
Liang Ding a6cecc0f5d . 2014-11-01 17:38:12 +08:00
Liang Ding b4c2a7cd81 #128 2014-11-01 17:33:22 +08:00
Liang Ding a92e858994 #128 2014-11-01 13:35:32 +08:00
Van ffea905ff4 Fix #67 2014-11-01 12:36:42 +08:00
Van 4e078d31d1 手误 2014-11-01 12:07:52 +08:00
Van 9c1d5b09a0 fix 67 2014-11-01 12:05:52 +08:00
Van 7cb70347bf 保存文件时,未修改的不需要再次保存 2014-10-31 17:01:23 +08:00
Van 2c20093661 保存状态 2014-10-31 15:54:54 +08:00
Liang Ding ac743ca17f Fix #127
@xbreezes
2014-10-31 14:54:44 +08:00
Liang Ding fc7cd03b96 format html 2014-10-31 14:26:08 +08:00
Liang Ding 8b7fdaaace fix build & run stderr output 2014-10-31 13:46:51 +08:00
Liang Ding c7f702cd15 Fix #126 2014-10-31 13:15:40 +08:00
Liang Ding 5b5581392d Fix #106
/signup
2014-10-30 18:21:44 +08:00
Van 748ee58316 2014-10-30 16:28:06 +08:00
Van a9318e0c0c sign up 2014-10-30 16:25:57 +08:00
Liang Ding 17557acf18 . 2014-10-30 13:40:46 +08:00
Liang Ding eee5b14b54 . 2014-10-30 12:08:30 +08:00
Liang Ding f357972067 . 2014-10-30 11:37:37 +08:00
Liang Ding 530ddbbdfb Update README.md 2014-10-30 09:17:50 +08:00
Liang Ding b3c9b67782 Merge pull request #124 from b3log/1.1.0
Merge pull request #120 from b3log/master
2014-10-30 09:00:45 +08:00
Liang Ding f91863b37b #106
Basically completed the back-end implementation.
2014-10-29 23:03:03 +08:00
Liang Ding 0c6a648658 Update README.md 2014-10-29 21:42:22 +08:00
Liang Ding 0196be2aaa i18n 2014-10-29 21:36:57 +08:00
Liang Ding efc7b86419 Merge pull request #123 from tjyang/master
locale for Traditional Chinese
2014-10-29 21:12:40 +08:00
T.J. Yang 7734ad86a4 locale for Traditional Chinese 2014-10-29 04:13:04 -07:00
Liang Ding cfd5367919 Fix #119 2014-10-29 18:15:18 +08:00
Liang Ding 7ec8a77005 #119 2014-10-29 14:05:10 +08:00
Liang Ding d21dc7bc4d Merge pull request #120 from b3log/master
rebase
2014-10-29 09:30:28 +08:00
Liang Ding 5d2497063f . 2014-10-29 09:29:12 +08:00
Liang Ding ee52e46b8a #119 2014-10-29 00:37:47 +08:00
Liang Ding 542bf019a3 . 2014-10-29 00:09:26 +08:00
Liang Ding 190f30c9f4 . 2014-10-29 00:04:46 +08:00
Liang Ding 6e5d13cf2f . 2014-10-28 21:32:19 +08:00
Liang Ding 784cf854f3 Merge pull request #118 from b3log/1.1.0
1.1.0
2014-10-28 20:58:37 +08:00
Liang Ding fd26ba5103 #117 2014-10-28 20:49:14 +08:00
Liang Ding 2691c6bb61 . 2014-10-28 17:56:38 +08:00
Liang Ding ca8e6f9373 Merge pull request #116 from b3log/1.1.0
1.1.0
2014-10-28 17:26:46 +08:00
Liang Ding c69487b983 . 2014-10-28 15:28:46 +08:00
Liang Ding 923346103e . 2014-10-28 15:27:55 +08:00
Liang Ding 3a511b38a6 Merge pull request #115 from b3log/master
rebase
2014-10-28 15:11:55 +08:00
Liang Ding a3624a0e10 Fix #113 2014-10-28 14:38:27 +08:00
Liang Ding 23efab8c60 format 2014-10-28 13:47:43 +08:00
Liang Ding 9cc3dc126f Merge pull request #108 from mattn/japanese-locale
Japanese locale and font-separat by base-{locale}.css
2014-10-28 13:35:16 +08:00
Liang Ding 780c0903b5 Merge pull request #109 from mattn/handle-error
Handle two columns error format. ex: /path/to/file.go: Permission denied
2014-10-28 13:24:44 +08:00
Liang Ding 1c65b74037 refactor path things 2014-10-28 13:22:39 +08:00
Liang Ding 3637283168 #111
@mattn
2014-10-28 12:06:50 +08:00
Liang Ding c346df3168 Merge pull request #111 from mattn/filepath
Fix filepath things
2014-10-28 11:53:21 +08:00
Liang Ding f4b40c2d7a Merge pull request #112 from b3log/1.1.0
1.1.0
2014-10-28 11:53:03 +08:00
Liang Ding 1aaa3ba719 Fix #104 2014-10-28 10:57:16 +08:00
mattn 45d4095401 Fix filepath things. This make wide possible to work "Build/Build&Run" on windows 2014-10-28 11:15:16 +09:00
mattn 1362ad1ea3 Should be zero to avoid highlight editor 2014-10-28 10:16:42 +09:00
mattn 9bfaef796e Handle two columns error format. ex: /path/to/file.go: Permission denied 2014-10-28 10:12:32 +09:00
mattn eaddb049c7 Separate CSSs to handle font-family for buttons 2014-10-28 09:55:00 +09:00
mattn 850be98c9a Separate font-family 2014-10-28 09:48:30 +09:00
mattn b99e954f4f Separate font-family 2014-10-28 09:48:11 +09:00
mattn 21a826c11c Add japanese locale 2014-10-28 09:48:07 +09:00
Liang Ding 70e4b4efca Merge pull request #103 from b3log/master
rebase
2014-10-28 00:01:08 +08:00
Liang Ding f439a45ade failed 2 error 2014-10-27 23:58:10 +08:00
Liang Ding e2ffefd173 . 2014-10-27 23:06:46 +08:00
Liang Ding 67ba1ec568 #97
默认语言配置改为 `en_US`
2014-10-27 23:03:09 +08:00
Liang Ding 292781fe76 Fix #14 2014-10-27 23:02:15 +08:00
Liang Ding e6408edf54 Merge pull request #102 from b3log/1.1.0
conf static ver update
2014-10-27 16:54:13 +08:00
Liang Ding 4af55ce2af . 2014-10-27 16:53:10 +08:00
Liang Ding 94d918250a Merge pull request #101 from b3log/1.1.0
1.1.0
2014-10-27 16:30:30 +08:00
Liang Ding ab6950a621 Fix #100 2014-10-27 16:00:02 +08:00
Liang Ding c1339b946a #100 2014-10-27 15:49:58 +08:00
Liang Ding 0f4a20e261 Fix #70 2014-10-27 14:42:33 +08:00
Liang Ding 4a13d6f074 Merge pull request #99 from b3log/1.1.0
release 1.0.1
2014-10-26 21:48:37 +08:00
Liang Ding 66d651eba6 release 1.0.1 2014-10-26 21:47:12 +08:00
Liang Ding 7fc41ca5fb Merge pull request #98 from b3log/1.1.0
1.1.0
2014-10-26 18:29:50 +08:00
Liang Ding 2bb49cdce6 . 2014-10-26 18:25:35 +08:00
Liang Ding a377ff5ae0 Fix #96 2014-10-26 18:08:01 +08:00
Liang Ding ad75e1088b Fix #95 2014-10-26 16:03:19 +08:00
Liang Ding 280b665b86 . 2014-10-26 15:59:12 +08:00
Liang Ding 107b499f1a Fix #94 2014-10-26 15:41:34 +08:00
Liang Ding 3b8b8ced46 Fix #92 2014-10-26 14:46:15 +08:00
Liang Ding 0a10cf2689 Merge pull request #91 from b3log/master
rebase
2014-10-26 12:52:24 +08:00
Liang Ding 3b9814b500 Update README.md 2014-10-26 12:37:19 +08:00
Liang Ding 952bf233f3 Fix #90 2014-10-26 12:14:56 +08:00
Liang Ding f21c20c167 Fix #89 2014-10-26 11:44:13 +08:00
Liang Ding 9dfc44d796 Merge pull request #88 from Rubyss/master
Fixed wide.bat to not enter an infinite loop
2014-10-26 11:27:42 +08:00
Aviv Rozenboim 16339dbc49 Fixed wide.bat to not enter an infinite loop 2014-10-25 20:27:34 +03:00
Liang Ding f5c59d4530 Merge pull request #87 from b3log/master
rebase
2014-10-24 23:53:27 +08:00
Liang Ding d11db2f3af mime 类型初始化
@admpub 在你的 pr 基础上做了一些小重构
2014-10-24 23:51:24 +08:00
Liang Ding 5fedcd6c57 Merge pull request #86 from admpub/master
解决windows环境下问题
2014-10-24 23:23:47 +08:00
商讯在线 5e46dd3eb5 update 2014-10-24 22:35:49 +08:00
商讯在线 682f8f451d 修复windows环境下css问题 2014-10-24 22:28:07 +08:00
Liang Ding 85736ec2b9 Merge pull request #85 from b3log/master
rebase
2014-10-24 21:35:26 +08:00
Liang Ding 37d8f48bfa Merge pull request #80 from b3log/master
1.0.0
2014-10-24 14:05:42 +08:00
502 changed files with 84471 additions and 16107 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
static/js/lib/* linguist-vendored
static/js/overwrite/* linguist-vendored
*.min.js linguist-vendored

15
.gitignore vendored
View File

@ -1,11 +1,12 @@
/wide.exe /wide.exe
/wide /wide
/main
/data/workspace/bin/ /header
/data/workspace/pkg/ /header.exe
/data/workspace/src/
/data/user_workspaces/*/bin/ /**/.DS_Store
/data/user_workspaces/*/pkg/
/data/user_workspaces/*/src/**/*.exe /node_modules
/nbproject
/workspaces
.idea

View File

@ -1,22 +0,0 @@
author: DL88250@gmail.com
description: A Web IDE for Teams using Golang.
filesets:
depth: 10
includes:
- conf
- data
- doc
- i18n
- static
- views
- README.md
- LICENSE
excludes:
- \.git
settings:
targetdir: ""
build: |
test -d Godeps && go(){ godep go "$@";} ; go install -v
outfiles:
- wide

21
.header.json Normal file
View File

@ -0,0 +1,21 @@
{
"Dir": ".",
"Template": ".header.txt",
"Includes": [
"*.go",
"*/*.go",
"static/css/*.css",
"static/js/*.js",
"static/js/overwrite/*.js"
],
"Excludes": [
"static/js/lib/*",
"static/user/*",
"vendor/**"
],
"UseDefaultExcludes": true,
"Properties": {
"Year": "2014-present",
"Owner": "b3log.org"
}
}

13
.header.txt Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) {{.Year}}, {{.Owner}}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

4
.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: go
go:
- 1.12

View File

@ -192,7 +192,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

126
README.md
View File

@ -1,124 +1,18 @@
# Wide [![Build Status](https://drone.io/github.com/b3log/wide/status.png)](https://drone.io/github.com/b3log/wide/latest) * [Wide 用户指南](https://ld246.com/article/1538873544275)
* [Wide 开发指南](https://ld246.com/article/1538876422995)
## Intro ![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
A <b>W</b>eb <b>IDE</b> for Teams using Golang. ![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
<img src="https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png" width="100%" /> ![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
## Motivation ![Theme](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
* **Team** IDE: ![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
* Safe and reliable: the project source code stored on the server in real time, the developer's machine crashes without losing any source code
* Unified environment: server unified development environment configuration, the developer machine without any additional configuration
* Out of the box: 5 minutes to setup a server then open browser to develop, debug
* Version Control: each developer has its own source code repository, easy sync with the trunk
* **Web based** IDE:
* Developer needs a browser only
* Cross-platform, even on mobile devices
* Easy for extensions
* Easy integration with other systems
* For the geeks
* A try for commercial-open source: versions customized for enterprises, close to their development work flows respectively
* Currently more popular Go IDE has some defects or regrets:
* Text editor (vim/emacs/sublime/Atom, etc.): For the Go newbie is too complex
* Plug-in (goclipse, etc.): the need for the original IDE support, not professional
* LiteIDE: no modern user interface :p
* No team development experience
* There are a few of GO IDEs, and no one developed by Go itself, this is a nice try
## Features ![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
* Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc. ![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
* Autocomplete: Go/HTML etc.
* Format: Go/HTML/JSON etc.
* Run & Debug: run/debug multiple processes at the same time
* Multiplayer: a real team development experience
* Navigation, Jump to declaration, Find usages, File search etc.
* Shell: run command on the server
* Git integration: git command on the web
* Web development: Frontend devlopment (HTML/JS/CSS) all in one
* Go tool: go get/install/fmt etc.
## Architecture ![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
### Build & Run
![Build & Run](https://cloud.githubusercontent.com/assets/873584/4389219/3642bc62-43f3-11e4-8d1f-06d7aaf22784.png)
* A browser tab corresponds to a Wide session
* Execution output push via WebSocket
Flow:
1. Browser sends ````Build```` request
2. Server executes ````go build```` command via ````os/exec````<br/>
2.1. Generates a executable file
3. Browser sends ````Run```` request
4. Server executes the file via ````os/exec````<br/>
4.1. A running process<br/>
4.2. Execution output push via WebSocket channel
5. Browser renders with callback function ````ws.onmessage````
### Code Assist
![Code Assist](https://cloud.githubusercontent.com/assets/873584/4399135/3b80c21c-4463-11e4-8e94-7f7e8d12a4df.png)
* Autocompletion
* Find Usages
Flow:
1. Browser sends code assist request
2. Handler gets user workspace of the request with HTTP session
3. Server executes ````gocode````/````ide_stub````<br/>
3.1 Sets environment variables (e.g. ${GOPATH})<br/>
3.2 ````gocode```` with ````lib-path```` parameter
## Documents
* [用户指南](http://88250.gitbooks.io/wide-user-guide)
* [开发指南](http://88250.gitbooks.io/wide-dev-guide)
## Demos
* 20141024-1.0.0, png ![](http://b3log.org/wide/demo/20141024.png)
### Olds
* [20140927, png](http://b3log.org/wide/demo/20140927.png)
* [20140913, png](http://b3log.org/wide/demo/20140913.png)
* [20140910, png](http://b3log.org/wide/demo/20140910.png)
* [20140823, swf](http://b3log.org/wide/demo/20140823.html)
## Setup from sources
1. Downloads source
2. Gets dependencies with
* `go get -u`
* `go get -u github.com/88250/ide_stub`
* `go get -u github.com/nsf/gocode`
3. Compiles wide with `go build`
4. Configures `conf/wide.json`
5. Runs the executable `wide` or `wide.exe`
## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
## License
Copyright (c) 2014, B3log Team (http://b3log.org)
Licensed under the [Apache License 2.0](https://github.com/b3log/wide/blob/master/LICENSE).
## Credits
* [golang](http://golang.org)
* [CodeMirror](https://github.com/marijnh/CodeMirror)
* [zTree](https://github.com/zTree/zTree_v3)
* [LiteIDE](https://github.com/visualfc/liteide)
* [gocode](https://github.com/nsf/gocode)
* [Gorilla](https://github.com/gorilla)
----
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />
</center>

4
TERMS.md Normal file
View File

@ -0,0 +1,4 @@
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](https://b3log.org)" from any pages, even the pages are developed by you
* If you want to use this software for commercial purpose, please mail to os@b3log.org for request a commercial license
* Copyright (c) b3log.org, all rights reserved

162
conf/user.go Normal file
View File

@ -0,0 +1,162 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
// Panel represents a UI panel.
type Panel struct {
State string `json:"state"` // panel state, "min"/"max"/"normal"
Size uint16 `json:"size"` // panel size
}
// Layout represents the UI layout.
type Layout struct {
Side *Panel `json:"side"` // Side panel
SideRight *Panel `json:"sideRight"` // Right-Side panel
Bottom *Panel `json:"bottom"` // Bottom panel
}
// LatestSessionContent represents the latest session content.
type LatestSessionContent struct {
FileTree []string `json:"fileTree"` // paths of expanding nodes of file tree
Files []string `json:"files"` // paths of files of opening editor tabs
CurrentFile string `json:"currentFile"` // path of file of the current focused editor tab
Layout *Layout `json:"layout"` // UI Layout
}
// User configuration.
type User struct {
Id string
Name string
Avatar string
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Locale string
GoFormat string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
FontFamily string
FontSize string
Theme string
Keymap string // wide/vim
Created int64 // user create time in unix nano
Updated int64 // preference update time in unix nano
Lived int64 // the latest session activity in unix nano
Editor *editor
LatestSessionContent *LatestSessionContent
}
// Editor configuration of a user.
type editor struct {
FontFamily string
FontSize string
LineHeight string
Theme string
TabSize string
}
// Save saves the user's configurations in conf/users/{userId}.json.
func (u *User) Save() bool {
bytes, err := json.MarshalIndent(u, "", " ")
if nil != err {
logger.Error(err)
return false
}
if "" == string(bytes) {
logger.Error("Truncated user [" + u.Id + "]")
return false
}
if err = ioutil.WriteFile(filepath.Join(Wide.Data, "users", u.Id+".json"), bytes, 0644); nil != err {
logger.Error(err)
return false
}
return true
}
// NewUser creates a user with the specified username and workspace.
func NewUser(id, name, avatar, workspace string) *User {
now := time.Now().UnixNano()
return &User{Id: id, Name: name, Avatar: avatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt",
GoBuildArgsForLinux: "", GoBuildArgsForWindows: "", GoBuildArgsForDarwin: "",
FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Keymap: "wide",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// WorkspacePath gets workspace path of the user.
//
// Compared to the use of Wide.Workspace, this function will be processed as follows:
// 1. Replace {WD} variable with the actual directory path
// 2. Replace ${GOPATH} with enviorment variable GOPATH
// 3. Replace "/" with "\\" (Windows)
func (u *User) WorkspacePath() string {
w := u.Workspace
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
return filepath.FromSlash(w)
}
// BuildArgs get build args with the specified os.
func (u *User) BuildArgs(os string) []string {
var tmp string
if os == "windows" {
tmp = u.GoBuildArgsForWindows
}
if os == "linux" {
tmp = u.GoBuildArgsForLinux
}
if os == "darwin" {
tmp = u.GoBuildArgsForDarwin
}
exp := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)'`)
ret := exp.FindAllString(tmp, -1)
for idx := range ret {
ret[idx] = strings.Replace(ret[idx], "\"", "", -1)
}
return ret
}
// GetOwner gets the user the specified path belongs to. Returns "" if not found.
func GetOwner(path string) string {
for _, user := range Users {
if strings.HasPrefix(path, user.WorkspacePath()) {
return user.Id
}
}
return ""
}

View File

@ -1,147 +1,331 @@
// Wide 配置相关,所有配置(包括用户配置)都是保存在 wide.json 中. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package conf includes configurations related manipulations.
package conf package conf
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "html/template"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "sort"
"strconv"
"strings" "strings"
"time" "time"
"github.com/b3log/wide/event" "github.com/88250/gulu"
_ "github.com/b3log/wide/i18n" "github.com/88250/wide/event"
"github.com/b3log/wide/util"
"github.com/golang/glog"
) )
const ( const (
PathSeparator = string(os.PathSeparator) // 系统文件路径分隔符 // PathSeparator holds the OS-specific path separator.
PathListSeparator = string(os.PathListSeparator) // 系统路径列表分隔符 PathSeparator = string(os.PathSeparator)
// PathListSeparator holds the OS-specific path list separator.
PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current Wide's version.
WideVersion = "1.6.0"
// CodeMirrorVer holds the current editor version.
CodeMirrorVer = "5.1"
// UserAgent represents HTTP client user agent.
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
HelloWorld = `package main
import "fmt"
func main() {
fmt.Println("欢迎通过《边看边练 Go 系列》来学习 Go 语言 https://ld246.com/article/1437497122181")
}
`
) )
// 最后一次会话内容结构. // Configuration.
type LatestSessionContent struct {
FileTree []string // 文件树展开的路径集
Files []string // 编辑器打开的文件路径集
CurrentFile string // 当前编辑器文件路径
}
// 用户结构.
type User struct {
Name string
Password string
Workspace string // 该用户的工作空间 GOPATH 路径
Locale string
LatestSessionContent *LatestSessionContent
}
// 配置结构.
type conf struct { type conf struct {
Server string // 服务地址({IP}:7070 Server string // server
StaticServer string // 静态资源服务地址http://{IP}:7070 LogLevel string // logging level: trace/debug/info/warn/error
EditorChannel string // 编辑器通道地址ws://{IP}:7070 Data string // data directory
OutputChannel string // 输出窗口通道地址ws://{IP}:7070 RuntimeMode string // runtime mode (dev/prod)
ShellChannel string // Shell 通道地址ws://{IP}:7070 HTTPSessionMaxAge int // HTTP session max age (in seciond)
SessionChannel string // Wide 会话通道地址ws://{IP}:7070 StaticResourceVersion string // version of static resources
HTTPSessionMaxAge int // HTTP 会话失效时间(秒) Locale string // default locale
StaticResourceVersion string // 静态资源版本 Autocomplete bool // default autocomplete
MaxProcs int // 并发执行数 SiteStatCode template.HTML // site statistic code
RuntimeMode string // 运行模式 ReadOnly bool // read-only mode
Pwd string // 工作目录 OAuthLoginURL string
Workspace string // 主工作空间 GOPATH 路径 OAuthAccessTokenURL string
Locale string // 默认的区域 OAuthUserInfoURL string
Users []*User // 用户集 OAuthClientID string
OAuthClientSecret string
} }
// 配置. // Logger.
var Wide conf var logger = gulu.Log.NewLogger(os.Stdout)
// 维护非变化部分的配置. // Wide configurations.
// var Wide *conf
// 只有 Users 是会运行时变化的,保存回写文件时要使用这个变量.
var rawWide conf
// 定时检查 Wide 运行环境. // configurations of users.
// var Users []*User
// 如果是特别严重的问题(比如 $GOPATH 不存在)则退出进程,另一些不太严重的问题(比如 gocode 不存在)则放入全局通知队列.
func FixedTimeCheckEnv() {
go func() {
// 7 分钟进行一次检查环境
for _ = range time.Tick(time.Minute * 7) {
if "" == os.Getenv("GOPATH") {
glog.Fatal("Not found $GOPATH")
os.Exit(-1)
}
if "" == os.Getenv("GOROOT") { // Indicates whether Docker is available.
glog.Fatal("Not found $GOROOT") var Docker bool
os.Exit(-1) // Docker image to run user's program
} const DockerImageGo = "golang"
gocode := Wide.GetGocode() // Load loads the Wide configurations from wide.json and users' configurations from users/{userId}.json.
cmd := exec.Command(gocode, "close") func Load(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
_, err := cmd.Output() initWide(confPath, confData, confServer, confLogLevel, confReadOnly, confSiteStatCode)
initUsers()
cmd := exec.Command("docker", "version")
_, err := cmd.CombinedOutput()
if nil != err { if nil != err {
event.EventQueue <- event.EvtCodeGocodeNotFound logger.Warnf("Not found 'docker' installed, running user's code will cause security problem")
glog.Warningf("Not found gocode [%s]", gocode) } else {
Docker = true
}
}
func initUsers() {
f, err := os.Open(Wide.Data + PathSeparator + "users")
if nil != err {
logger.Error(err)
os.Exit(-1)
} }
ide_stub := Wide.GetIDEStub() names, err := f.Readdirnames(-1)
cmd = exec.Command(ide_stub, "version") if nil != err {
logger.Error(err)
os.Exit(-1)
}
f.Close()
for _, name := range names {
if strings.HasPrefix(name, ".") { // hiden files that not be created by Wide
continue
}
if ".json" != filepath.Ext(name) { // such as backup (*.json~) not be created by Wide
continue
}
user := &User{}
bytes, _ := os.ReadFile(filepath.Join(Wide.Data, "users", name))
err := json.Unmarshal(bytes, user)
if err != nil {
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
continue
}
// Compatibility upgrade (1.3.0): https://github.com/b3log/wide/issues/83
if "" == user.Keymap {
user.Keymap = "wide"
}
// Compatibility upgrade (1.5.3): https://github.com/b3log/wide/issues/308
//if "" == user.GoBuildArgsForLinux {
// user.GoBuildArgsForLinux = "-i"
//}
//if "" == user.GoBuildArgsForWindows {
// user.GoBuildArgsForWindows = "-i"
//}
//if "" == user.GoBuildArgsForDarwin {
// user.GoBuildArgsForDarwin = "-i"
//}
Users = append(Users, user)
}
initWorkspaceDirs()
initCustomizedConfs()
}
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
bytes, err := os.ReadFile(confPath)
if nil != err {
logger.Error(err)
os.Exit(-1)
}
Wide = &conf{}
err = json.Unmarshal(bytes, Wide)
if err != nil {
logger.Error("Parses [wide.json] error: ", err)
os.Exit(-1)
}
Wide.Autocomplete = true // default to true
// Logging Level
gulu.Log.SetLevel(Wide.LogLevel)
if "" != confLogLevel {
Wide.LogLevel = confLogLevel
gulu.Log.SetLevel(confLogLevel)
}
logger.Debug("Conf: \n" + string(bytes))
// User Home
home, err := gulu.OS.Home()
if nil != err {
logger.Error("Can't get user's home, please report this issue to developer", err)
os.Exit(-1)
}
logger.Debugf("${user.home} [%s]", home)
// Data directory
if "" != confData {
Wide.Data = confData
}
Wide.Data = strings.Replace(Wide.Data, "${home}", home, -1)
Wide.Data = filepath.Clean(Wide.Data)
if err := os.MkdirAll(Wide.Data+"/playground/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
}
if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
}
if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
}
// Server
if "" != confServer {
Wide.Server = confServer
}
if "" != confReadOnly {
Wide.ReadOnly, _ = strconv.ParseBool(confReadOnly)
}
// SiteStatCode
if "" != confSiteStatCode {
Wide.SiteStatCode = confSiteStatCode
}
time := strconv.FormatInt(time.Now().UnixNano(), 10)
logger.Debugf("${time} [%s]", time)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
}
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
//
// Exits process if found fatal issues (such as not found $GOPATH),
// Notifies user by notification queue if found warning issues (such as not found gocode).
func FixedTimeCheckEnv() {
checkEnv() // check immediately
go func() {
for _ = range time.Tick(time.Minute * 7) {
checkEnv()
}
}()
}
func checkEnv() {
defer gulu.Panic.Recover(nil)
cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput()
if nil != err {
logger.Error("Not found 'go' command, please make sure Go has been installed correctly")
os.Exit(-1)
}
logger.Trace(string(buf))
if "" == os.Getenv("GOPATH") {
logger.Error("Not found $GOPATH, please configure it before running Wide")
os.Exit(-1)
}
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
cmd = exec.Command(gocode)
_, err = cmd.Output() _, err = cmd.Output()
if nil != err { if nil != err {
event.EventQueue <- event.EvtCodeIDEStubNotFound event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
glog.Warningf("Not found ide_stub [%s]", ide_stub)
logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/stamblerre/gocode", gocode)
} }
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
cmd = exec.Command(ideStub, "version")
_, err = cmd.Output()
if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound}
logger.Warnf("Not found gotools [%s], please install it with this command: go get github.com/visualfc/gotools", ideStub)
} }
}()
} }
// 定时1 分钟)保存配置. // GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
// func GetUserWorkspace(userId string) string {
// 主要目的是保存用户会话内容,以备下一次用户打开 Wide 时进行会话还原. for _, user := range Users {
func FixedTimeSave() { if user.Id == userId {
go func() { return user.WorkspacePath()
// 1 分钟进行一次配置保存
for _ = range time.Tick(time.Minute) {
Save()
}
}()
}
// 获取 username 指定的用户的工作空间路径,查找不到时返回空字符串.
func (c *conf) GetUserWorkspace(username string) string {
for _, user := range c.Users {
if user.Name == username {
ret := strings.Replace(user.Workspace, "{pwd}", c.Pwd, 1)
return filepath.FromSlash(ret)
} }
} }
return "" return ""
} }
// 获取主工作空间路径. // GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
func (c *conf) GetWorkspace() string { func GetGoFmt(userId string) string {
return filepath.FromSlash(strings.Replace(c.Workspace, "{pwd}", c.Pwd, 1)) for _, user := range Users {
if user.Id == userId {
switch user.GoFormat {
case "gofmt":
return "gofmt"
case "goimports":
return gulu.Go.GetExecutableInGOBIN("goimports")
default:
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
return "gofmt"
}
}
}
return "gofmt"
} }
// 获取 user 的工作空间路径. // GetUser gets configuration of the user specified by the given user id, returns nil if not found.
func (user *User) getWorkspace() string { func GetUser(id string) *User {
ret := strings.Replace(user.Workspace, "{pwd}", Wide.Pwd, 1) if "playground" == id { // reserved user for Playground
return NewUser("playground", "playground", "", "")
}
return filepath.FromSlash(ret) for _, user := range Users {
} if user.Id == id {
// 获取 username 指定的用户配置.
func (*conf) GetUser(username string) *User {
for _, user := range Wide.Users {
if user.Name == username {
return user return user
} }
} }
@ -149,110 +333,131 @@ func (*conf) GetUser(username string) *User {
return nil return nil
} }
// 获取 gocode 路径. // initCustomizedConfs initializes the user customized configurations.
func (*conf) GetGocode() string { func initCustomizedConfs() {
return getGOBIN() + "gocode" for _, user := range Users {
UpdateCustomizedConf(user.Id)
}
} }
// 获取 ide_stub 路径. // UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
func (*conf) GetIDEStub() string {
return getGOBIN() + "ide_stub"
}
// 获取 GOBIN 路径,末尾带路径分隔符.
func getGOBIN() string {
// $GOBIN/
ret := os.Getenv("GOBIN")
if "" != ret {
return ret + PathSeparator
}
// $GOPATH/bin/$GOOS_$GOARCH/
ret = os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator +
os.Getenv("GOOS") + "_" + os.Getenv("GOARCH")
if isExist(ret) {
return ret + PathSeparator
}
// $GOPATH/bin/{runtime.GOOS}_{runtime.GOARCH}/
ret = os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator +
runtime.GOOS + "_" + runtime.GOARCH
if isExist(ret) {
return ret + PathSeparator
}
// $GOPATH/bin/
return os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator
}
// 保存 Wide 配置.
func Save() bool {
// 只有 Users 是会运行时变化的,其他属性只能手工维护 wide.json 配置文件
rawWide.Users = Wide.Users
// 原始配置文件内容
bytes, err := json.MarshalIndent(rawWide, "", " ")
if nil != err {
glog.Error(err)
return false
}
if err = ioutil.WriteFile("conf/wide.json", bytes, 0644); nil != err {
glog.Error(err)
return false
}
return true
}
// 加载 Wide 配置.
func Load() {
bytes, _ := ioutil.ReadFile("conf/wide.json")
err := json.Unmarshal(bytes, &Wide)
if err != nil {
glog.Error(err)
os.Exit(-1)
}
// 保存未经变量替换处理的原始配置文件,用于写回时
json.Unmarshal(bytes, &rawWide)
ip, err := util.Net.LocalIP()
if err != nil {
glog.Error(err)
os.Exit(-1)
}
glog.V(3).Infof("IP [%s]", ip)
Wide.Server = strings.Replace(Wide.Server, "{IP}", ip, 1)
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", ip, 1)
Wide.EditorChannel = strings.Replace(Wide.EditorChannel, "{IP}", ip, 1)
Wide.OutputChannel = strings.Replace(Wide.OutputChannel, "{IP}", ip, 1)
Wide.ShellChannel = strings.Replace(Wide.ShellChannel, "{IP}", ip, 1)
Wide.SessionChannel = strings.Replace(Wide.SessionChannel, "{IP}", ip, 1)
// 获取当前执行路径
file, _ := exec.LookPath(os.Args[0])
pwd, _ := filepath.Abs(file)
pwd = pwd[:strings.LastIndex(pwd, PathSeparator)]
Wide.Pwd = pwd
glog.V(3).Infof("pwd [%s]", pwd)
glog.V(3).Info("Conf: \n" + string(bytes))
}
// 检查文件或目录是否存在.
// //
// 如果由 filename 指定的文件或目录存在则返回 true否则返回 false. // 1. /static/users/{userId}/style.css
func isExist(filename string) bool { func UpdateCustomizedConf(userId string) {
_, err := os.Stat(filename) var u *User
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
if user.Id == userId {
u = user
}
}
return err == nil || os.IsExist(err) if nil == u {
return
}
model := map[string]interface{}{"user": u}
t, err := template.ParseFiles("static/user/style.css.tmpl")
if nil != err {
logger.Error(err)
os.Exit(-1)
}
dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
os.Exit(-1)
}
fout, err := os.Create(dir + PathSeparator + "style.css")
if nil != err {
logger.Error(err)
os.Exit(-1)
}
defer fout.Close()
if err := t.Execute(fout, model); nil != err {
logger.Error(err)
os.Exit(-1)
}
}
// initWorkspaceDirs initializes the directories of users' workspaces.
//
// Creates directories if not found on path of workspace.
func initWorkspaceDirs() {
paths := []string{}
for _, user := range Users {
paths = append(paths, filepath.SplitList(user.WorkspacePath())...)
}
for _, path := range paths {
CreateWorkspaceDir(path)
}
}
// CreateWorkspaceDir creates (if not exists) directories on the path.
//
// 1. root directory:{path}
// 2. src directory: {path}/src
// 3. package directory: {path}/pkg
// 4. binary directory: {path}/bin
func CreateWorkspaceDir(path string) {
createDir(path)
createDir(path + PathSeparator + "src")
createDir(path + PathSeparator + "pkg")
createDir(path + PathSeparator + "bin")
}
// createDir creates a directory on the path if it not exists.
func createDir(path string) {
if !gulu.File.IsExist(path) {
if err := os.MkdirAll(path, 0775); nil != err {
logger.Error(err)
os.Exit(-1)
}
logger.Tracef("Created a dir [%s]", path)
}
}
// GetEditorThemes gets the names of editor themes.
func GetEditorThemes() []string {
ret := []string{}
f, _ := os.Open("static/js/overwrite/codemirror" + "/theme")
names, _ := f.Readdirnames(-1)
f.Close()
for _, name := range names {
ret = append(ret, name[:strings.LastIndex(name, ".")])
}
sort.Strings(ret)
return ret
}
// GetThemes gets the names of themes.
func GetThemes() []string {
ret := []string{}
f, _ := os.Open("static/css/themes")
names, _ := f.Readdirnames(-1)
f.Close()
for _, name := range names {
ret = append(ret, name[:strings.LastIndex(name, ".")])
}
sort.Strings(ret)
return ret
} }

View File

@ -1,28 +1,16 @@
{ {
"Server": "{IP}:7070", "Server": "http://127.0.0.1:7070",
"StaticServer": "http://{IP}:7070", "LogLevel": "debug",
"EditorChannel": "ws://{IP}:7070", "Data": "${home}/wide",
"OutputChannel": "ws://{IP}:7070", "RuntimeMode": "prod",
"ShellChannel": "ws://{IP}:7070",
"SessionChannel": "ws://{IP}:7070",
"HTTPSessionMaxAge": 86400, "HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "201410241024", "StaticResourceVersion": "${time}",
"MaxProcs": 4,
"RuntimeMode": "dev",
"Pwd": "{pwd}",
"Workspace": "{pwd}/data/workspace",
"Locale": "zh_CN", "Locale": "zh_CN",
"Users": [ "SiteStatCode": "",
{ "ReadOnly": false,
"Name": "admin", "OAuthLoginURL": "",
"Password": "admin", "OAuthAccessTokenURL": "",
"Workspace": "{pwd}/data/user_workspaces/admin", "OAuthUserInfoURL": "",
"Locale": "zh_CN", "OAuthClientID": "",
"LatestSessionContent": { "OAuthClientSecret": ""
"FileTree": [],
"Files": [],
"CurrentFile": ""
}
}
]
} }

View File

@ -1 +0,0 @@
User workspaces.

View File

@ -1,9 +0,0 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, 世界")
}

View File

@ -1,411 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
{{.i18n.wide}}
</title>
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/codemirror.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/hint/show-hint.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/lint.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldgutter.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/theme/wide.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/ztree/zTreeStyle.css">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/dialog.css?{{.conf.StaticResourceVersion}}">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/base.css?{{.conf.StaticResourceVersion}}">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/wide.css?{{.conf.StaticResourceVersion}}">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/side.css?{{.conf.StaticResourceVersion}}">
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/start.css?{{.conf.StaticResourceVersion}}">
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<!-- 主菜单 -->
<div class="menu fn-clear">
<ul class="fn-cleaer">
<li>
<span>
{{.i18n.file}}
</span>
<div class="frame">
<ul>
<li class="save-all disabled" onclick="wide.saveAllFiles()">
<span>
{{.i18n.save_all_files}}
</span>
</li>
<li class="close-all disabled" onclick="wide.closeAllFiles()">
<span>
{{.i18n.close_all_files}}
</span>
</li>
<li class="hr">
</li>
<li onclick="wide.exit()">
<span>
{{.i18n.exit}}
</span>
</li>
</ul>
</div>
</li>
<li>
<span>
{{.i18n.run}}
</span>
<div class="frame">
<ul>
<li class="run disabled" onclick="wide.run()">
<span>
{{.i18n.build_n_run}}
</span>
</li>
<li class="hr">
</li>
<li class="go-get disabled" onclick="wide.goget()">
<span>
{{.i18n.goget}}
</span>
</li>
<li class="go-install disabled" onclick="wide.goinstall()">
<span>
{{.i18n.goinstall}}
</span>
</li>
</ul>
</div>
</li>
<!--
<li>
<span>{{.i18n.debug}}</span>
<div class="frame">
<ul>
<li>
<span>{{.i18n.debug}}</span>
</li>
</ul>
</div>
</li>
-->
<li>
<span>
{{.i18n.help}}
</span>
<div class="frame">
<ul>
<li onclick="window.open('https://www.gitbook.io/book/88250/wide-user-guide')">
<span>
{{.i18n.wide_doc}}
</span>
</li>
<li onclick="window.open('https://github.com/b3log/wide/issues/new')">
{{.i18n.report_issues}}
</li>
<li class="hr">
</li>
<li onclick="editors.openStartPage()">
<span>
{{.i18n.start_page}}
</span>
</li>
<li onclick="wide.openAbout()">
<span>
{{.i18n.about}}
</span>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="content">
<div class="side">
<span title="{{.i18n.min}}" class="font-ico ico-min">
</span>
<div class="tabs">
<div class="current" data-index="filreTree">
<span title="{{.i18n.file}}">
{{.i18n.file}}
</span>
</div>
</div>
<div class="tabs-panel">
<div data-index="filreTree">
<ul id="files" tabindex="-1" class="ztree">
</ul>
<!-- 目录右键菜单 -->
<div id="dirRMenu" class="frame">
<ul>
<li onclick="tree.newFile();">
{{.i18n.create_file}}
</li>
<li onclick="tree.newDir();">
{{.i18n.create_dir}}
</li>
<li onclick="tree.removeIt();">
{{.i18n.delete}}
</li>
</ul>
</div>
<!-- 文件右键菜单 -->
<div id="fileRMenu" class="frame">
<ul>
<li onclick="tree.removeIt();">
{{.i18n.delete}}
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="edit-panel">
<div class="toolbars fn-none">
<span onclick="wide.run()" class="font-ico ico-buildrun" title="{{.i18n.build_n_run}}">
</span>
<span onclick="wide.saveFile()" title="{{.i18n.save}}" class="font-ico ico-save">
</span>
<span onclick="wide.fmt(editors.getCurrentPath(), wide.curEditor)" class="ico-format font-ico" title="{{.i18n.format}}">
</span>
<span class="font-ico ico-max" onclick="windows.maxEditor()" title="{{.i18n.max_editor}}">
</span>
</div>
<div class="tabs">
</div>
<div class="tabs-panel">
</div>
</div>
<div class="bottom-window-group">
<span title="{{.i18n.min}}" class="font-ico ico-min">
</span>
<div class="tabs">
<div class="current" data-index="output">
<span title="{{.i18n.output}}">
{{.i18n.output}}
</span>
</div>
<div data-index="search">
<span title="{{.i18n.search}}">
{{.i18n.search}}
</span>
</div>
<div data-index="notification">
<span title="{{.i18n.notification}}">
{{.i18n.notification}}
</span>
</div>
</div>
<div class="tabs-panel">
<div data-index="output">
<textarea class="output">
</textarea>
</div>
<div class="fn-none" data-index="search">
<div tabindex="-1" class="search">
<div class="tabs fn-none">
<div class="current" data-index="first">
<span class="first">
</span>
<span class="ico-close font-ico">
</span>
</div>
</div>
<div class="tabs-panel">
<div data-index="first">
</div>
</div>
</div>
</div>
<div class="fn-none" data-index="notification">
<div tabindex="-1" class="notification">
<table cellpadding="0" cellspacing="0">
</table>
</div>
</div>
</div>
</div>
</div>
<div class="footer fn-clear">
<div class="fn-left">
<span title="{{.i18n.restore_side}}" class="font-ico ico-restore fn-none">
</span>
<span title="{{.i18n.resotre_bottom}}" class="font-ico ico-restore fn-none">
</span>
</div>
<div class="fn-right">
<span class="cursor">
</span>
<span class="notification-count" title="{{.i18n.unread_notification}}">
{{.i18n.notification}}!
</span>
</div>
</div>
<div id="dialogRemoveConfirm" class="fn-none">
{{.i18n.isDelete}}
<b>
</b>
?
</div>
<div id="dialogAlert" class="fn-none">
</div>
<div id="dialogNewFilePrompt" class="dialog-prompt fn-none">
<input/>
</div>
<div id="dialogNewDirPrompt" class="dialog-prompt fn-none">
<input/>
</div>
<div id="dialogGoLinePrompt" class="dialog-prompt fn-none">
<input/>
</div>
<div id="dialogSearchForm" class="dialog-form fn-none">
<input placeholder="{{.i18n.keyword}}" />
<input placeholder="{{.i18n.file_format}}" />
</div>
<script>
var config = {
"pathSeparator": {{.pathSeparator}},
"latestSessionContent": {{.latestSessionContent}},
"label": {
"restore_editor": "{{.i18n.restore_editor}}",
"max_editor": "{{.i18n.max_editor}}",
"delete": "{{.i18n.delete}}",
"cancel": "{{.i18n.cancel}}",
"goto_line": "{{.i18n.goto_line}}",
"goto": "{{.i18n.goto}}",
"create": "{{.i18n.create}}",
"create_file": "{{.i18n.create_file}}",
"create_dir": "{{.i18n.create_dir}}",
"tip": "{{.i18n.tip}}",
"confirm": "{{.i18n.confirm}}",
"build_n_run": "{{.i18n.build_n_run}}",
"stop": "{{.i18n.stop}}",
"usages": "{{.i18n.find_usages}}",
"search_text": "{{.i18n.search_text}}",
"search": "{{.i18n.search}}",
"initialise": "{{.i18n.initialise}}",
"confirm_save": "{{.i18n.confirm_save}}"
},
"channel": {
"editor": '{{.conf.EditorChannel}}',
"shell": '{{.conf.ShellChannel}}',
"output": '{{.conf.OutputChannel}}',
"session": '{{.conf.SessionChannel}}'
},
"wideSessionId": '{{.session.Id}}'
};
// 发往 Wide 的所有 AJAX 请求需要使用该函数创建请求参数.
function newWideRequest() {
var ret = {
sid: config.wideSessionId
}
return ret;
}
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/jquery-2.1.1.min.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/reconnecting-websocket.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/ztree/jquery.ztree.all-3.5.min.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/codemirror.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/lint.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/json-lint.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/selection/active-line.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/overwrite/codemirror-4.5/addon/hint/show-hint.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/hint/anyword-hint.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/display/rulers.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/closebrackets.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/matchbrackets.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/closetag.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/search/searchcursor.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/search/match-highlighter.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldcode.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldgutter.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/brace-fold.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/xml-fold.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/markdown-fold.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/comment-fold.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/go/go.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/xml/xml.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/htmlmixed/htmlmixed.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/javascript/javascript.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/markdown/markdown.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/css/css.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/shell/shell.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/sql/sql.js">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/lint/json-lint.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/lint/go-lint.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/tabs.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/dialog.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/editors.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/notification.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/tree.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/wide.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/session.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/menu.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/windows.js?{{.conf.StaticResourceVersion}}">
</script>
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/hotkeys.js?{{.conf.StaticResourceVersion}}">
</script>
</body>
</html>

View File

@ -1,16 +0,0 @@
package main
import (
"fmt"
"mytest/time/pkg"
"time"
)
func main() {
for i := 0; i < 50; i++ {
fmt.Println("Hello, 世界", pkg.Now())
time.Sleep(time.Second)
}
}

View File

@ -1,17 +0,0 @@
package pkg
import (
"time"
)
func Now() time.Time {
return time.Now()
}
func Now1() time.Time {
return time.Now()
}
func Now2() {
}

View File

@ -1 +0,0 @@
Master workspace.

View File

@ -1,2 +0,0 @@
* [User Guide](https://www.gitbook.io/book/88250/wide-user-guide)
* [Developer Guide](https://www.gitbook.io/book/88250/wide-dev-guide)

View File

@ -1,4 +1,18 @@
// 编辑器操作. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package editor includes editor related manipulations.
package editor package editor
import ( import (
@ -7,147 +21,88 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/file" "github.com/88250/wide/conf"
"github.com/b3log/wide/session" "github.com/88250/wide/file"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
"github.com/golang/glog"
"github.com/gorilla/websocket"
) )
var editorWS = map[string]*websocket.Conn{} // Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// 建立编辑器通道. // AutocompleteHandler handles request of code autocompletion.
func WSHandler(w http.ResponseWriter, r *http.Request) {
session, _ := session.HTTPSession.Get(r, "wide-session")
sid := session.Values["id"].(string)
editorWS[sid], _ = websocket.Upgrade(w, r, nil, 1024, 1024)
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
editorWS[sid].WriteJSON(&ret)
glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(editorWS))
args := map[string]interface{}{}
for {
if err := editorWS[sid].ReadJSON(&args); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Editor WS ERROR: " + err.Error())
return
}
code := args["code"].(string)
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch)
// glog.Infof("offset: %d", offset)
gocode := conf.Wide.GetGocode()
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
var output bytes.Buffer
cmd := exec.Command(gocode, argv...)
cmd.Stdout = &output
stdin, _ := cmd.StdinPipe()
cmd.Start()
stdin.Write([]byte(code))
stdin.Close()
cmd.Wait()
ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"}
if err := editorWS[sid].WriteJSON(&ret); err != nil {
glog.Error("Editor WS ERROR: " + err.Error())
return
}
}
}
// 自动完成(代码补全).
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) { func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body) if conf.Wide.ReadOnly {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
if err := decoder.Decode(&args); err != nil { logger.Error(err)
glog.Error(err) http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return return
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
username := session.Values["username"].(string) if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
path := args["path"].(string) path := args["path"].(string)
fout, err := os.Create(path) fout, err := os.Create(path)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
code := args["code"].(string) code := args["code"].(string)
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
line := int(args["cursorLine"].(float64)) line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64)) ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
logger.Tracef("offset: %d", offset)
// glog.Infof("offset: %d", offset) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace)
userWorkspace := conf.Wide.GetUserWorkspace(username) libPath := ""
for _, workspace := range workspaces {
//glog.Infof("User [%s] workspace [%s]", username, userWorkspace) userLib := workspace + conf.PathSeparator + "pkg" + conf.PathSeparator +
userLib := userWorkspace + conf.PathSeparator + "pkg" + conf.PathSeparator +
runtime.GOOS + "_" + runtime.GOARCH runtime.GOOS + "_" + runtime.GOARCH
libPath += userLib + conf.PathListSeparator
}
libPath := userLib logger.Tracef("gocode set lib-path [%s]", libPath)
//glog.Infof("gocode set lib-path %s", libPath)
// FIXME: 使用 gocode set lib-path 在多工作空间环境下肯定是有问题的,需要考虑其他实现方式 gocode := gulu.Go.GetExecutableInGOBIN("gocode")
gocode := conf.Wide.GetGocode() // FIXME: using gocode set lib-path has some issues while accrossing workspaces
argv := []string{"set", "lib-path", libPath} exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
exec.Command(gocode, argv...).Run()
argv = []string{"-f=json", "autocomplete", strconv.Itoa(offset)} argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
cmd := exec.Command(gocode, argv...) cmd := exec.Command(gocode, argv...)
stdin, _ := cmd.StdinPipe()
stdin.Write([]byte(code))
stdin.Close()
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -156,33 +111,31 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
w.Write(output) w.Write(output)
} }
// 查看表达式信息. // GetExprInfoHandler handles request of getting expression infomation.
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) { func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} result := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
decoder := json.NewDecoder(r.Body)
var args map[string]interface{} var args map[string]interface{}
if err := decoder.Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
path := args["path"].(string) path := args["path"].(string)
curDir := path[:strings.LastIndex(path, conf.PathSeparator)] curDir := filepath.Dir(path)
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:] filename := filepath.Base(path)
fout, err := os.Create(path) fout, err := os.Create(path)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -191,8 +144,8 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -202,61 +155,63 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现 ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ide_stub := conf.Wide.GetIDEStub() argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."} cmd := exec.Command(ideStub, argv...)
cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
exprInfo := strings.TrimSpace(string(output)) exprInfo := strings.TrimSpace(string(output))
if "" == exprInfo { if "" == exprInfo {
data["succ"] = false result.Code = -1
return return
} }
data["info"] = exprInfo result.Data = exprInfo
} }
// 查找声明. // FindDeclarationHandler handles request of finding declaration.
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) { func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} result := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
username := session.Values["username"].(string) if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
decoder := json.NewDecoder(r.Body) return
}
uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := decoder.Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
path := args["path"].(string) path := args["path"].(string)
curDir := path[:strings.LastIndex(path, conf.PathSeparator)] curDir := filepath.Dir(path)
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:] filename := filepath.Base(path)
fout, err := os.Create(path) fout, err := os.Create(path)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -265,8 +220,8 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -276,27 +231,26 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现 ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ide_stub := conf.Wide.GetIDEStub() argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."} cmd := exec.Command(ideStub, argv...)
cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
found := strings.TrimSpace(string(output)) found := strings.TrimSpace(string(output))
if "" == found { if "" == found {
data["succ"] = false result.Code = -1
return return
} }
@ -304,42 +258,49 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
part := found[:strings.LastIndex(found, ":")] part := found[:strings.LastIndex(found, ":")]
cursorSep := strings.LastIndex(part, ":") cursorSep := strings.LastIndex(part, ":")
path = found[:cursorSep] path = found[:cursorSep]
cursorLine := found[cursorSep+1 : strings.LastIndex(found, ":")]
cursorCh := found[strings.LastIndex(found, ":")+1:]
data["path"] = path cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
data := map[string]interface{}{}
result.Data = &data
data["path"] = filepath.ToSlash(path)
data["cursorLine"] = cursorLine data["cursorLine"] = cursorLine
data["cursorCh"] = cursorCh data["cursorCh"] = cursorCh
} }
// 查找使用. // FindUsagesHandler handles request of finding usages.
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) { func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} result := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
username := session.Values["username"].(string) if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
decoder := json.NewDecoder(r.Body) return
}
uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := decoder.Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
filePath := args["path"].(string) filePath := args["path"].(string)
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] curDir := filepath.Dir(filePath)
filename := filePath[strings.LastIndex(filePath, conf.PathSeparator)+1:] filename := filepath.Base(filePath)
fout, err := os.Create(filePath) fout, err := os.Create(filePath)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -348,8 +309,8 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -358,39 +319,38 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
ch := int(args["cursorCh"].(float64)) ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找使用,后续需要重新实现 ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ide_stub := conf.Wide.GetIDEStub() argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."} cmd := exec.Command(ideStub, argv...)
cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
result := strings.TrimSpace(string(output)) out := strings.TrimSpace(string(output))
if "" == result { if "" == out {
data["succ"] = false result.Code = -1
return return
} }
founds := strings.Split(result, "\n") founds := strings.Split(out, "\n")
usages := []*file.Snippet{} usages := []*file.Snippet{}
for _, found := range founds { for _, found := range founds {
found = strings.TrimSpace(found) found = strings.TrimSpace(found)
part := found[:strings.LastIndex(found, ":")] part := found[:strings.LastIndex(found, ":")]
cursorSep := strings.LastIndex(part, ":") cursorSep := strings.LastIndex(part, ":")
path := found[:cursorSep] path := filepath.ToSlash(found[:cursorSep])
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")]) cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:]) cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
@ -398,21 +358,22 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
usages = append(usages, usage) usages = append(usages, usage)
} }
data["founds"] = usages result.Data = usages
} }
// 计算光标偏移位置. // getCursorOffset calculates the cursor offset.
// //
// line 指定了行号(第一行为 0ch 指定了列号(第一列为 0. // line is the line number, starts with 0 that means the first line
// ch is the column number, starts with 0 that means the first column
func getCursorOffset(code string, line, ch int) (offset int) { func getCursorOffset(code string, line, ch int) (offset int) {
lines := strings.Split(code, "\n") lines := strings.Split(code, "\n")
// 计算前几行长度 // calculate sum length of lines before
for i := 0; i < line; i++ { for i := 0; i < line; i++ {
offset += len(lines[i]) offset += len(lines[i])
} }
// 计算当前行、当前列长度 // calculate length of the current line and column
curLine := lines[line] curLine := lines[line]
var buffer bytes.Buffer var buffer bytes.Buffer
r := []rune(curLine) r := []rune(curLine)
@ -420,14 +381,14 @@ func getCursorOffset(code string, line, ch int) (offset int) {
buffer.WriteString(string(r[i])) buffer.WriteString(string(r[i]))
} }
offset += line // 加换行符 offset += len(buffer.String()) // append length of current line
offset += len(buffer.String()) // 加当前行列偏移 offset += line // append number of '\n'
return offset return offset
} }
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, userId string) {
userWorkspace := conf.Wide.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(userId)
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace, "GOPATH="+userWorkspace,

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package editor package editor
import ( import (
@ -6,36 +20,56 @@ import (
"os" "os"
"os/exec" "os/exec"
"github.com/88250/gohtml" "github.com/88250/gulu"
"github.com/b3log/wide/util" "github.com/88250/wide/conf"
"github.com/golang/glog" "github.com/88250/wide/session"
) )
// TODO: 加入 goimports 格式化 Go 源码文件 // GoFmtHandler handles request of formatting Go source code.
//
// gofmt 格式化 Go 源码文件. // This function will select a format tooll based on user's configuration:
// 1. gofmt
// 2. goimports
func GoFmtHandler(w http.ResponseWriter, r *http.Request) { func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} result := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, result)
decoder := json.NewDecoder(r.Body) if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := decoder.Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
filePath := args["file"].(string) filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) {
result.Code = -1
return
}
fout, err := os.Create(filePath) fout, err := os.Create(filePath)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -44,19 +78,29 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
data := map[string]interface{}{}
result.Data = &data
data["code"] = code
result.Data = data
fmt := conf.GetGoFmt(uid)
argv := []string{filePath} argv := []string{filePath}
cmd := exec.Command("gofmt", argv...) cmd := exec.Command(fmt, argv...)
bytes, _ := cmd.Output() bytes, _ := cmd.Output()
output := string(bytes) output := string(bytes)
if "" == output { if "" == output {
data["succ"] = false // format error, returns the original content
result.Code = 0
return return
} }
@ -67,133 +111,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout, err = os.Create(filePath) fout, err = os.Create(filePath)
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return
}
}
// 格式化 HTML 文件.
// FIXME依赖的工具 gohtml 格式化 HTML 时有问题
func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
filePath := args["file"].(string)
fout, err := os.Create(filePath)
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
output := gohtml.Format(code)
if "" == output {
data["succ"] = false
return
}
code = string(output)
data["code"] = code
fout, err = os.Create(filePath)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
}
// 格式化 JSON 文件.
func JSONFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
filePath := args["file"].(string)
fout, err := os.Create(filePath)
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
obj := new(interface{})
if err := json.Unmarshal([]byte(code), &obj); nil != err {
glog.Error(err)
data["succ"] = false
return
}
glog.Info(obj)
bytes, err := json.MarshalIndent(obj, "", " ")
if nil != err {
data["succ"] = false
return
}
code = string(bytes)
data["code"] = code
fout, err = os.Create(filePath)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return return
} }

View File

@ -1,89 +1,119 @@
// 事件处理. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package event includes event related manipulations.
package event package event
import "github.com/golang/glog" import (
"os"
const ( "github.com/88250/gulu"
EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH
EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT
EvtCodeGocodeNotFound // 事件代码:找不到 gocode
EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub
) )
// 事件队列最大长度. const (
const MaxQueueLength = 10 // EvtCodeGOPATHNotFound indicates an event: not found $GOPATH env variable
EvtCodeGOPATHNotFound = iota
// EvtCodeGOROOTNotFound indicates an event: not found $GOROOT env variable
EvtCodeGOROOTNotFound
// EvtCodeGocodeNotFound indicates an event: not found gocode
EvtCodeGocodeNotFound
// EvtCodeIDEStubNotFound indicates an event: not found gotools
EvtCodeIDEStubNotFound
// EvtCodeServerInternalError indicates an event: server internal error
EvtCodeServerInternalError
)
// 事件结构. // Max length of queue.
const maxQueueLength = 10
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// Event represents an event.
type Event struct { type Event struct {
Code int `json:"code"` // 事件代码 Code int `json:"code"` // event code
Sid string `json:"sid"` // 用户会话 id Sid string `json:"sid"` // wide session id related
Data interface{} `json:"data"` // event data
} }
// 全局事件队列. // Global event queue.
// //
// 入队的事件将分发到每个用户的事件队列中. // Every event in this queue will be dispatched to each user event queue.
var EventQueue = make(chan int, MaxQueueLength) var EventQueue = make(chan *Event, maxQueueLength)
// 用户事件队列. // UserEventQueue represents a user event queue.
type UserEventQueue struct { type UserEventQueue struct {
Sid string // 关联的会话 id Sid string // wide session id related
Queue chan int // 队列 Queue chan *Event // queue
Handlers []Handler // 事件处理器集 Handlers []Handler // event handlers
} }
// 事件队列集类型. type queues map[string]*UserEventQueue
type Queues map[string]*UserEventQueue
// 用户事件队列集. // User event queues.
// //
// <sid, *UserEventQueue> // <sid, *UserEventQueue>
var UserEventQueues = Queues{} var UserEventQueues = queues{}
// 加载事件处理. // Load initializes the event handling.
func Load() { func Load() {
go func() { go func() {
for event := range EventQueue { defer gulu.Panic.Recover(nil)
glog.V(5).Info("收到全局事件 [%d]", event)
// 将事件分发到每个用户的事件队列里 for event := range EventQueue {
logger.Debugf("Received a global event [code=%d]", event.Code)
// dispatch the event to each user event queue
for _, userQueue := range UserEventQueues { for _, userQueue := range UserEventQueues {
event.Sid = userQueue.Sid
userQueue.Queue <- event userQueue.Queue <- event
} }
} }
}() }()
} }
// 为用户队列添加事件处理器. // AddHandler adds the specified handlers to user event queues.
func (uq *UserEventQueue) AddHandler(handlers ...Handler) { func (uq *UserEventQueue) AddHandler(handlers ...Handler) {
for _, handler := range handlers { uq.Handlers = append(uq.Handlers, handlers...)
uq.Handlers = append(uq.Handlers, handler)
}
} }
// 初始化一个用户事件队列. // New initializes a user event queue with the specified wide session id.
func (ueqs Queues) New(sid string) *UserEventQueue { func (ueqs queues) New(sid string) *UserEventQueue {
q := ueqs[sid]
if nil != q { if q, ok := ueqs[sid]; ok {
glog.Warningf("Already exist a user queue in session [%s]", sid) logger.Warnf("Already exist a user queue in session [%s]", sid)
return q return q
} }
q = &UserEventQueue{ q := &UserEventQueue{
Sid: sid, Sid: sid,
Queue: make(chan int, MaxQueueLength), Queue: make(chan *Event, maxQueueLength),
} }
ueqs[sid] = q ueqs[sid] = q
go func() { // 队列开始监听事件 go func() { // start listening
for evtCode := range q.Queue { defer gulu.Panic.Recover(nil)
glog.V(5).Infof("Session [%s] received a event [%d]", sid, evtCode)
// 将事件交给事件处理器进行处理 for evt := range q.Queue {
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
// process event by each handlers
for _, handler := range q.Handlers { for _, handler := range q.Handlers {
handler.Handle(&Event{Code: evtCode, Sid: sid}) handler.Handle(evt)
} }
} }
}() }()
@ -91,25 +121,24 @@ func (ueqs Queues) New(sid string) *UserEventQueue {
return q return q
} }
// 关闭一个用户事件队列. // Close closes a user event queue with the specified wide session id.
func (ueqs Queues) Close(sid string) { func (ueqs queues) Close(sid string) {
q := ueqs[sid]
if nil == q {
return
}
if q, ok := ueqs[sid]; ok {
close(q.Queue)
delete(ueqs, sid) delete(ueqs, sid)
}
} }
// 事件处理接口. // Handler represents an event handler.
type Handler interface { type Handler interface {
Handle(event *Event) Handle(event *Event)
} }
// 函数指针包装. // HandleFunc represents a handler function.
type HandleFunc func(event *Event) type HandleFunc func(event *Event)
// 事件处理默认实现. // Default implementation of event handling.
func (fn HandleFunc) Handle(event *Event) { func (fn HandleFunc) Handle(event *Event) {
fn(event) fn(event)
} }

102
file/exporter.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"github.com/88250/gulu"
)
// GetZipHandler handles request of retrieving zip file.
func GetZipHandler(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
path := q["path"][0]
if ".zip" != filepath.Ext(path) {
http.Error(w, "Bad Request", 400)
return
}
if !gulu.File.IsExist(path) {
http.Error(w, "Not Found", 404)
return
}
filename := filepath.Base(path)
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
w.Header().Set("Content-Type", "application/zip")
http.ServeFile(w, r, path)
os.Remove(path)
}
// CreateZipHandler handles request of creating zip.
func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
data := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data.Code = -1
return
}
path := args["path"].(string)
var name string
base := filepath.Base(path)
if nil != args["name"] {
name = args["name"].(string)
} else {
name = base
}
dir := filepath.Dir(path)
if !gulu.File.IsExist(path) {
data.Code = -1
data.Msg = "Can't find file [" + path + "]"
return
}
zipPath := filepath.Join(dir, name)
zipFile, err := gulu.Zip.Create(zipPath + ".zip")
if nil != err {
logger.Error(err)
data.Code = -1
return
}
defer zipFile.Close()
if gulu.File.IsDir(path) {
zipFile.AddDirectory(base, path)
} else {
zipFile.AddEntry(base, path)
}
data.Data = zipPath
}

File diff suppressed because it is too large Load Diff

159
file/outline.go Executable file
View File

@ -0,0 +1,159 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"bytes"
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"net/http"
"strings"
"github.com/88250/gulu"
)
type element struct {
Name string
Line int
Ch int
}
// GetOutlineHandler gets outfile of a go file.
func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
code := args["code"].(string)
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", code, 0)
if err != nil {
result.Code = -1
return
}
data := map[string]interface{}{}
result.Data = &data
// ast.Print(fset, f)
line, ch := getCursor(code, int(f.Name.Pos()))
data["package"] = &element{Name: f.Name.Name, Line: line, Ch: ch}
imports := []*element{}
for _, astImport := range f.Imports {
line, ch := getCursor(code, int(astImport.Path.Pos()))
imports = append(imports, &element{Name: astImport.Path.Value, Line: line, Ch: ch})
}
data["imports"] = imports
funcDecls := []*element{}
varDecls := []*element{}
constDecls := []*element{}
structDecls := []*element{}
interfaceDecls := []*element{}
typeDecls := []*element{}
for _, decl := range f.Decls {
switch decl.(type) {
case *ast.FuncDecl:
funcDecl := decl.(*ast.FuncDecl)
line, ch := getCursor(code, int(funcDecl.Name.Pos()))
funcDecls = append(funcDecls, &element{Name: funcDecl.Name.Name, Line: line, Ch: ch})
case *ast.GenDecl:
genDecl := decl.(*ast.GenDecl)
for _, spec := range genDecl.Specs {
switch genDecl.Tok {
case token.VAR:
variableSpec := spec.(*ast.ValueSpec)
for _, varName := range variableSpec.Names {
line, ch := getCursor(code, int(varName.Pos()))
varDecls = append(varDecls, &element{Name: varName.Name, Line: line, Ch: ch})
}
case token.TYPE:
typeSpec := spec.(*ast.TypeSpec)
line, ch := getCursor(code, int(typeSpec.Pos()))
switch typeSpec.Type.(type) {
case *ast.StructType:
structDecls = append(structDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
case *ast.InterfaceType:
interfaceDecls = append(interfaceDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
case *ast.Ident:
typeDecls = append(typeDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
}
case token.CONST:
constSpec := spec.(*ast.ValueSpec)
for _, constName := range constSpec.Names {
line, ch := getCursor(code, int(constName.Pos()))
constDecls = append(constDecls, &element{Name: constName.Name, Line: line, Ch: ch})
}
}
}
}
}
data["funcDecls"] = funcDecls
data["varDecls"] = varDecls
data["constDecls"] = constDecls
data["structDecls"] = structDecls
data["interfaceDecls"] = interfaceDecls
data["typeDecls"] = typeDecls
}
// getCursor calculates the cursor position (line, ch) by the specified offset.
//
// line is the line number, starts with 0 that means the first line
// ch is the column number, starts with 0 that means the first column
func getCursor(code string, offset int) (line, ch int) {
code = code[:offset]
lines := strings.Split(code, "\n")
line = 0
for range lines {
line++
}
var buffer bytes.Buffer
runes := []rune(lines[line-1])
for _, r := range runes {
buffer.WriteString(string(r))
}
ch = len(buffer.String())
return line - 1, ch - 1
}

23
go.mod Normal file
View File

@ -0,0 +1,23 @@
module github.com/88250/wide
go 1.20
require (
github.com/88250/gulu v1.1.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/parnurzeal/gorequest v0.2.16
)
require (
github.com/davidebianchi/go-jsonclient v1.5.0 // indirect
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // indirect
golang.org/x/text v0.3.2 // indirect
moul.io/http2curl v1.0.0 // indirect
)

44
go.sum Normal file
View File

@ -0,0 +1,44 @@
github.com/88250/gulu v1.1.0 h1:aU8HncW1XNssT1insCOz6rvmTUDrtsDwSnzeqTEqNFA=
github.com/88250/gulu v1.1.0/go.mod h1:a2POIziN+QFeNT4Mj7FHH2lz1HEaFlMRF6wPE0NHM4U=
github.com/davidebianchi/go-jsonclient v1.5.0 h1:PVDunAF/6c30D2SSx711efsrUP3hhml+uGT6oD3lzVY=
github.com/davidebianchi/go-jsonclient v1.5.0/go.mod h1:lXd/hkx23H590Dod74j5GsmpgxF8RIAGZDt1P5+2mqo=
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 h1:TEmChtx8+IeOghiySC8kQIr0JZOdKUmRmmkuRDuYs3E=
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ=
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=

150
gulpfile.js Normal file
View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2014-2018, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file frontend tool.
*
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
*/
var gulp = require('gulp')
var concat = require('gulp-concat')
var cleanCSS = require('gulp-clean-css')
var uglify = require('gulp-uglify')
var sourcemaps = require('gulp-sourcemaps')
function minLibCSS () {
// css
var cssLibs = [
'./static/js/lib/jquery-layout/layout-default-latest.css',
'./static/js/lib/codemirror-5.1/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
'./static/js/lib/codemirror-5.1/addon/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
'./static/js/overwrite/codemirror/theme/*.css']
return gulp.src(cssLibs).
pipe(cleanCSS()).
pipe(concat('lib.min.css')).
pipe(gulp.dest('./static/css/'))
}
function minZTreeStyleCSS () {
return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
pipe(cleanCSS()).
pipe(concat('zTreeStyle.min.css')).
pipe(gulp.dest('./static/js/lib/ztree/'))
}
function minWideCSS () {
var cssWide = [
'./static/css/dialog.css',
'./static/css/base.css',
'./static/css/wide.css',
'./static/css/side.css',
'./static/css/start.css',
'./static/css/about.css',
]
return gulp.src(cssWide).
pipe(cleanCSS()).
pipe(concat('wide.min.css')).
pipe(gulp.dest('./static/css/'))
}
function minLibJS () {
// js
var jsLibs = [
'./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js',
'./static/js/lib/Autolinker.min.js',
'./static/js/lib/emmet.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js']
return gulp.src(jsLibs).
pipe(uglify()).
pipe(concat('lib.min.js')).
pipe(gulp.dest('./static/js/'))
}
function minWideJS () {
var jsWide = [
'./static/js/tabs.js',
'./static/js/tabs.js',
'./static/js/dialog.js',
'./static/js/editors.js',
'./static/js/notification.js',
'./static/js/tree.js',
'./static/js/wide.js',
'./static/js/session.js',
'./static/js/menu.js',
'./static/js/windows.js',
'./static/js/hotkeys.js',
'./static/js/bottomGroup.js',
]
return gulp.src(jsWide).
pipe(sourcemaps.init()).
pipe(uglify()).
pipe(concat('wide.min.js')).
pipe(sourcemaps.write('.')).
pipe(gulp.dest('./static/js/'))
}
gulp.task('default',
gulp.series(
gulp.parallel(minLibCSS, minZTreeStyleCSS, minWideCSS, minLibJS, minWideJS)))

View File

@ -1,18 +1,20 @@
{ {
"colon": ": ",
"wide": "Wide", "wide": "Wide",
"isDelete": "Delete", "wide_title": "Playing golang, anytime, anywhere",
"cancel": "Cancel", "cancel": "Cancel",
"file": "File", "file": "File",
"login": "Login", "login": "Login",
"username": "Username", "username": "Username",
"current_user": "Current User", "current_user": "Current User",
"current_session": "Current Session",
"password": "Password", "password": "Password",
"login_failed": "Login Failed", "login_error": "Login Error",
"run": "Run", "run": "Run",
"debug": "Debug", "debug": "Debug",
"help": "Help", "help": "Help",
"check_update": "Check Update", "check_update": "Check Update",
"report_issues": "Report Issues", "issues": "Issues",
"wide_doc": "Wide Document", "wide_doc": "Wide Document",
"about": "About", "about": "About",
"start_page": "Start Page", "start_page": "Start Page",
@ -20,12 +22,12 @@
"create": "Create", "create": "Create",
"create_dir": "Create Dir", "create_dir": "Create Dir",
"delete": "Delete", "delete": "Delete",
"rename": "Rename",
"save": "Save", "save": "Save",
"exit": "Exit", "exit": "Exit",
"close_all_files": "Close All", "close_all_files": "Close All",
"save_all_files": "Save All", "save_all_files": "Save All",
"format": "Format", "format": "Format",
"goget": "go get",
"goinstall": "go install", "goinstall": "go install",
"build": "Build", "build": "Build",
"build_n_run": "Build&Run", "build_n_run": "Build&Run",
@ -35,7 +37,9 @@
"unread_notification": "Unread", "unread_notification": "Unread",
"notification_2": "Not found [gocode], thereby [Autocomplete] will not work", "notification_2": "Not found [gocode], thereby [Autocomplete] will not work",
"notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work", "notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work",
"notification_4": "Server Internal Error",
"goto_line": "Goto Line", "goto_line": "Goto Line",
"goto_file": "Goto File",
"go": "Go", "go": "Go",
"tip": "Tip", "tip": "Tip",
"confirm": "Confirm", "confirm": "Confirm",
@ -51,7 +55,7 @@
"find_previous": "Find Previous", "find_previous": "Find Previous",
"replace": "Replace", "replace": "Replace",
"replace_all": "Replace All", "replace_all": "Replace All",
"restore_bottom": "Bottom Windows Restore", "restore_bottom": "Restore Bottom Windows",
"file_format": "File Extension", "file_format": "File Extension",
"keyword": "Keyword", "keyword": "Keyword",
"user_guide": "User Guide", "user_guide": "User Guide",
@ -60,7 +64,7 @@
"ver": "Version", "ver": "Version",
"current_ver": "Current Version", "current_ver": "Current Version",
"dev_team": "Dev Team", "dev_team": "Dev Team",
"donate_us": "Donate Us", "donate": "Donate",
"confirm_save": "Confirm Save", "confirm_save": "Confirm Save",
"workspace": "Workspace", "workspace": "Workspace",
"project_address": "Project", "project_address": "Project",
@ -70,6 +74,10 @@
"show_expr_info": "Show Expression Info", "show_expr_info": "Show Expression Info",
"find_usages": "Find Usages", "find_usages": "Find Usages",
"delete_line": "Delete Line", "delete_line": "Delete Line",
"copy_lines_up": "Copy Lines Up",
"copy_lines_down": "Copy Lines Down",
"move_lines_up": "Move Lines Up",
"move_lines_down": "Move Lines Down",
"save_editor_file": "Save File", "save_editor_file": "Save File",
"save_all_editors_files": "Save All", "save_all_editors_files": "Save All",
"close_editor": "Close File", "close_editor": "Close File",
@ -86,13 +94,13 @@
"focus_notification": "Focus to Notification", "focus_notification": "Focus to Notification",
"start-build": "START [go build]", "start-build": "START [go build]",
"build-succ": "[go build] SUCCESS", "build-succ": "[go build] SUCCESS",
"build-failed": "[go build] Failed", "build-error": "[go build] ERROR",
"start-test": "START [go test]",
"test-succ": "[go test] SUCCESS",
"test-error": "[go test] ERROR",
"start-install": "START [go install]", "start-install": "START [go install]",
"install-succ": "[go install] SUCCESS", "install-succ": "[go install] SUCCESS",
"install-failed": "[go install] Fialed", "install-error": "[go install] ERROR",
"start-get": "START [go get]",
"get-succ": "[go get] SUCCESS",
"get-failed": "[go get] Failed",
"check_version": "Checking update", "check_version": "Checking update",
"new_version_available": "new version available", "new_version_available": "new version available",
"go_env": "Go", "go_env": "Go",
@ -101,5 +109,63 @@
"license": "License", "license": "License",
"credits": "Credits", "credits": "Credits",
"uptodate": "it is up to date", "uptodate": "it is up to date",
"colon": ": " "test": "Test",
"sign_up": "Sign Up",
"team": "Team",
"sing_up_error": "Sign Up Error",
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
"password_no_match": "Password doesn't match the confirmation",
"discard": "Discard",
"close": "Close",
"close_other": "Close Other",
"clear": "Clear",
"preference": "Preference",
"appearence": "Appearence",
"gotool": "Go Tool",
"user": "User",
"font": "Font",
"font_size": "Font Size",
"line_height": "Line Height",
"go_format": "Go Format",
"locale": "Locale",
"apply": "Apply",
"clearOutput": "Clear Output",
"export": "Export",
"refresh": "Refresh",
"theme": "Theme",
"tab_size": "Tab Size",
"copy_file_path": "Copy File Path",
"file_tree": "File Tree",
"select": "Select",
"expand": "Expand",
"collapse": "Collapse",
"edit": "Edit",
"undo": "Undo",
"redo": "Redo",
"cut": "Cut",
"copy": "Copy",
"paste": "Paste",
"select_all": "Select All",
"select_identifier": "Select Identifier",
"source": "Source",
"toggle_comment": "Toggle Comment",
"find_in_files": "Find in Files",
"no_empty": "Can not Empty!",
"open": "Open",
"search_no_match": "No matching files were found.",
"outline": "Outline",
"govet": "go vet",
"start-vet": "START [go vet]",
"vet-succ": "[go vet] SUCCESS",
"vet-error": "[go vet] ERROR",
"restore_outline": "Restore Outline",
"share": "Share",
"url": "URL",
"embeded": "Embeded",
"terms": "Terms",
"download": "Download",
"decompress": "Decompress",
"keymap": "Keymap",
"resize": "Resize",
"sponsor": "Sponsor"
} }

171
i18n/ja_JP.json Normal file
View File

@ -0,0 +1,171 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "いつでも、どこでもゴランをプレイする",
"cancel": "取消",
"file": "ファイル",
"login": "ログイン",
"username": "ユーザ名",
"current_user": "現在のユーザ",
"current_session": "現在のセッション",
"password": "パスワード",
"login_error": "ログインエラー",
"run": "実行",
"debug": "デバッグ",
"help": "ヘルプ",
"check_update": "更新をチェック",
"issues": "問題",
"wide_doc": "Wide ドキュメント",
"about": "Wide について",
"start_page": "スタートページ",
"create_file": "新規ファイル",
"create": "作成",
"create_dir": "新規ディレクトリ",
"delete": "削除",
"rename": "名前の変更",
"save": "保存",
"exit": "終了",
"close_all_files": "全てのファイルを閉じる",
"save_all_files": "全てを保存",
"format": "フォーマット",
"goinstall": "go install",
"build": "ビルド",
"build_n_run": "ビルド実行",
"editor": "エディタ",
"max_editor": "最大化",
"restore_editor": "元のサイズに戻す",
"unread_notification": "未読の通知",
"notification_2": "[gocode] が見つかりません。[Autocomplete] は動作しません。",
"notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。",
"notification_4": "内部サーバーエラー",
"goto_line": "指定行にジャンプ",
"goto_file": "ファイルをオープンする",
"go": "Go",
"tip": "ヒント",
"confirm": "確認",
"stop": "停止",
"output": "出力",
"search": "検索",
"notification": "通知",
"min": "最小化",
"restore_side": "ファイルツリーを戻す",
"search_text": "テキストを検索",
"find": "検索",
"find_next": "次を検索",
"find_previous": "前を検索",
"replace": "置換",
"replace_all": "全て置換",
"restore_bottom": "ウィンドウを下に戻す",
"file_format": "ファイルの拡張子",
"keyword": "キーワード",
"user_guide": "ユーザガイド",
"dev_guide": "開発ガイド",
"keyboard_shortcuts": "キーボードショートカット",
"ver": "バージョン",
"current_ver": "現在のバージョン",
"dev_team": "開発チーム",
"donate": "寄付",
"confirm_save": "保存の確認",
"workspace": "ワークスペース",
"project_address": "プロジェクト",
"community": "コミュニティ",
"autocomplete": "自動補完",
"jump_to_decl": "定義へジャンプ",
"show_expr_info": "式の情報を表示",
"find_usages": "使用方法を検索する",
"delete_line": "行を削除",
"copy_lines_up": "フロントへのコピー",
"copy_lines_down": "一番下にコピー",
"move_lines_up": "前面に移動します",
"move_lines_down": "以下に移動",
"save_editor_file": "保存",
"save_all_editors_files": "全てを保存",
"close_editor": "エディタを閉じる",
"full_screen": "全画面",
"auto_indent": "自動インデント",
"indent": "インデントを増やす",
"unindent": "インデントを減らす",
"focus": "フォーカス",
"switch_tab": "タブを切り替える",
"focus_editor": "エディタにフォーカスを与える",
"focus_file_tree": "ファイルツリーにフォーカスを与える",
"focus_output": "出力にフォーカスを与える",
"focus_search": "検索にフォーカスを与える",
"focus_notification": "通知にフォーカスを与える",
"start-build": "[go build] 開始",
"build-succ": "[go build] 成功",
"build-error": "[go build] 失敗",
"start-test": "[go test] 開始",
"test-succ": "[go test] 成功",
"test-error": "[go test] 失敗",
"start-install": "[go install] 開始",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります",
"go_env": "Go",
"os": "OS",
"project": "プロジェクト",
"license": "ライセンス",
"credits": "クレジット",
"uptodate": "最新です",
"test": "テスト",
"sign_up": "登録",
"team": "チーム",
"sing_up_error": "登録に失敗しました",
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
"password_no_match": "一貫性のないパスワード入力",
"discard": "あきらめる",
"close": "クローズ",
"close_other": "閉じるその他",
"clear": "空の",
"preference": "環境設定",
"appearence": "エクステリア",
"gotool": "Go ツール",
"user": "ユーザー",
"font": "フォント",
"font_size": "フォントサイズ",
"line_height": "行の高さ",
"go_format": "Go フォーマット",
"locale": "ロケール",
"apply": "適用する",
"clearOutput": "空の出力",
"export": "輸出",
"refresh": "リフレッシュ",
"theme": "テーマ",
"tab_size": "Tab サイズ",
"copy_file_path": "ファイルパスをコピー",
"file_tree": "ファイルツリー",
"select": "選択する",
"expand": "展開する",
"collapse": "シャットダウン",
"edit": "編集",
"undo": "元に戻す",
"redo": "やり直し",
"cut": "切り取り",
"copy": "コピー",
"paste": "貼り付け",
"select_all": "すべて選択",
"select_identifier": "選択識別子",
"source": "ソース",
"toggle_comment": "トグルコメント",
"find_in_files": "ファイルから検索",
"no_empty": "空ではありません",
"open": "オープン",
"search_no_match": "一致するファイルが見つかりませんでした。",
"outline": "アウトライン",
"govet": "go vet",
"start-vet": "[go vet] 開始",
"vet-succ": "[go vet] 成功",
"vet-error": "[go vet] 失敗",
"restore_outline": "アウトラインを復元",
"share": "シェア",
"url": "リンク",
"embeded": "埋め込む",
"terms": "利用規約",
"download": "ダウンロード",
"decompress": "解凍する",
"keymap": "キーマップ",
"resize": "サイズ変更",
"sponsor": "スポンサー"
}

171
i18n/ko_KR.json Normal file
View File

@ -0,0 +1,171 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "언제 어디서나 골란 놀기",
"cancel": "취소",
"file": "문서",
"login": "로그인",
"username": "아이디",
"current_user": "현제 접속자 ",
"current_session": "현제 세션",
"password": "비밀번호",
"login_error": "로그인 실패",
"run": "실행",
"debug": "디버깅",
"help": "도움말",
"check_update": "업데이트 확인",
"issues": "문제",
"wide_doc": "Wide 문서",
"about": "about",
"start_page": "시작 페이지",
"create_file": "새문서",
"create": "새로만들기",
"create_dir": "새폴더",
"delete": "삭제",
"rename": "이름변경",
"save": "저장",
"exit": "끝내기",
"close_all_files": "모두 닫기",
"save_all_files": "일괄저장",
"format": "포멧",
"goinstall": "go install",
"build": "빌드",
"build_n_run": "빌드후실행",
"editor": "편집기",
"max_editor": "최대화",
"restore_editor": "복구",
"unread_notification": "읽지않은 공지",
"notification_2": "[gocode] 를 찾지 못하였습니다. 자동완성기능이 동작하지 않습니다. ",
"notification_3": "[ide_stub] 를 찾지 못하였습니다. 찾기 기능이 동작하지 않습니다. ",
"notification_4": "서버 오류",
"goto_line": "라인이동",
"goto_file": "문서오픈",
"go": "이동",
"tip": "팁",
"confirm": "확인",
"stop": "정지",
"output": "출력",
"search": "검색",
"notification": "알림",
"min": "최소화",
"restore_side": "좌측창 복구",
"search_text": "문서검색",
"find": "검색",
"find_next": "다음검색",
"find_previous": "이전검색",
"replace": "바꾸기",
"replace_all": "전부바꾸기",
"restore_bottom": "아래창 복구",
"file_format": "확장자",
"keyword": "키워드",
"user_guide": "이용가이드",
"dev_guide": "개발 가이드",
"keyboard_shortcuts": "단축키",
"ver": "버전",
"current_ver": "현재버전",
"dev_team": "개발단체",
"donate": "기부",
"confirm_save": "정장했는지 확인해 주세요. ",
"workspace": "작업창",
"project_address": "항목주소",
"community": "커뮤니티",
"autocomplete": "자동완성",
"jump_to_decl": "알림으로 이동",
"show_expr_info": "Expression 보기",
"find_usages": "사용법찾기",
"delete_line": "현재 줄 삭제",
"copy_lines_up": "위로 복사",
"copy_lines_down": "아래로 복사",
"move_lines_up": "위로이동",
"move_lines_down": "아래로 이동",
"save_editor_file": "현재 편집하고있던 문서 저장",
"save_all_editors_files": "일괄저장",
"close_editor": "편집창 닫기",
"full_screen": "편집기 전체화면",
"auto_indent": "자동 정렬",
"indent": "안쪽이동",
"unindent": "밖으로 이동",
"focus": "포커스",
"switch_tab": "편집기편집/창편집 tab",
"focus_editor": "편집창으로 포커스",
"focus_file_tree": "프로젝트트리로 포커스이동",
"focus_output": "output 으로 포커스 이동",
"focus_search": "검색창으로 포커스 이동",
"focus_notification": "알림창으로 포커스 이동",
"start-build": "시작 [go build]",
"build-succ": "[go build] 성공",
"build-error": "[go build] 실패",
"start-test": "시작 [go test]",
"test-succ": "[go test] 성공",
"test-error": "[go test] 실패",
"start-install": "시작 [go install]",
"install-succ": "[go install] 성공",
"install-error": "[go install] 실패",
"check_version": "최신버전검색중",
"new_version_available": "최신업데이트 사용 가능",
"go_env": "Go 환경",
"os": "운영체제",
"project": "항목",
"license": "라이센스",
"credits": "감사합니다.",
"uptodate": "최신버전입니다.",
"test": "테스트",
"sign_up": "회원가입",
"team": "단체",
"sing_up_error": "가입실패",
"user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,",
"password_no_match": "비밀번호 오류",
"discard": "취소",
"close": "닫기",
"close_other": "현재창 남기고 닫기",
"clear": "청소",
"preference": "설정",
"appearence": "외관",
"gotool": "Go 도구",
"user": "유저",
"font": "폰트",
"font_size": "폰트크기",
"line_height": "줄간격",
"go_format": "Go 포멧",
"locale": "언어설정",
"apply": "적용",
"clearOutput": "ouput 클리어",
"export": "내보내기",
"refresh": "새로고침",
"theme": "주제",
"tab_size": "Tab 크기",
"copy_file_path": "경로복사",
"file_tree": "트리",
"select": "선택",
"expand": "확장",
"collapse": "축소",
"edit": "편집",
"undo": "취소",
"redo": "복원",
"cut": "잘라내기",
"copy": "복사",
"paste": "붙여넣기",
"select_all": "전체선택",
"select_identifier": "표식선택",
"source": "소스",
"toggle_comment": "주석",
"find_in_files": "문서에서 찾기",
"no_empty": "값을 입력해 주세요.",
"open": "열기",
"search_no_match": "해당 문서를 찾지 못하였습니다.",
"outline": "주제",
"govet": "go vet",
"start-vet": "시작 [go vet]",
"vet-succ": "[go vet] 성공",
"vet-error": "[go vet] 실패",
"restore_outline": "주제복구",
"share": "공유",
"url": "하이퍼링크",
"embeded": "삽입",
"terms": "사용계약",
"download": "다운로드",
"decompress": "압축풀기",
"keymap": "단축키",
"resize": "크기조절",
"sponsor": "후원사"
}

View File

@ -1,35 +1,66 @@
// 国际化操作. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package i18n includes internationalization related manipulations.
package i18n package i18n
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"os" "os"
"sort"
"strings"
"github.com/golang/glog" "github.com/88250/gulu"
) )
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// Locale.
type locale struct { type locale struct {
Name string Name string
Langs map[string]interface{} Langs map[string]interface{}
TimeZone string TimeZone string
} }
// 所有的 locales. // All locales.
var Locales = map[string]locale{} var Locales = map[string]locale{}
// 加载国际化配置. // Load loads i18n message configurations.
func Load() { func Load() {
// TODO: 自动加载所有语言配置 f, _ := os.Open("i18n")
names, _ := f.Readdirnames(-1)
f.Close()
load("zh_CN") if len(Locales) == len(names)-1 {
load("en_US") return
}
for _, name := range names {
if !strings.HasSuffix(name, ".json") {
continue
}
loc := name[:strings.LastIndex(name, ".")]
load(loc)
}
} }
func load(localeStr string) { func load(localeStr string) {
bytes, err := ioutil.ReadFile("i18n/" + localeStr + ".json") bytes, err := os.ReadFile("i18n/" + localeStr + ".json")
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
os.Exit(-1) os.Exit(-1)
} }
@ -38,22 +69,33 @@ func load(localeStr string) {
err = json.Unmarshal(bytes, &l.Langs) err = json.Unmarshal(bytes, &l.Langs)
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
os.Exit(-1) os.Exit(-1)
} }
Locales[localeStr] = l Locales[localeStr] = l
glog.V(5).Infof("Loaded [%s] locale configuration", localeStr)
} }
// 获取语言配置项. // Get gets a message with the specified locale and key.
func Get(locale, key string) interface{} { func Get(locale, key string) interface{} {
return Locales[locale].Langs[key] return Locales[locale].Langs[key]
} }
// 获取语言配置. // GetAll gets all messages with the specified locale.
func GetAll(locale string) map[string]interface{} { func GetAll(locale string) map[string]interface{} {
return Locales[locale].Langs return Locales[locale].Langs
} }
// GetLocalesNames gets names of all locales. Returns ["zh_CN", "en_US"] for example.
func GetLocalesNames() []string {
ret := []string{}
for name := range Locales {
ret = append(ret, name)
}
sort.Strings(ret)
return ret
}

View File

@ -1,18 +1,20 @@
{ {
"colon": "",
"wide": "Wide", "wide": "Wide",
"isDelete": "是否删除", "wide_title": "随时随地玩 golang",
"cancel": "取消", "cancel": "取消",
"file": "文件", "file": "文件",
"login": "登录", "login": "登录",
"username": "用户名", "username": "用户名",
"current_user": "当前用户", "current_user": "当前用户",
"current_session": "当前会话",
"password": "密码", "password": "密码",
"login_failed": "登录失败", "login_error": "登录失败",
"run": "运行", "run": "运行",
"debug": "调试", "debug": "调试",
"help": "帮助", "help": "帮助",
"check_update": "检查更新", "check_update": "检查更新",
"report_issues": "建议", "issues": "问题",
"wide_doc": "Wide 文档", "wide_doc": "Wide 文档",
"about": "关于", "about": "关于",
"start_page": "起始页", "start_page": "起始页",
@ -20,12 +22,12 @@
"create": "创建", "create": "创建",
"create_dir": "创建目录", "create_dir": "创建目录",
"delete": "删除", "delete": "删除",
"rename": "重命名",
"save": "保存", "save": "保存",
"exit": "退出", "exit": "退出",
"close_all_files": "关闭所有文件", "close_all_files": "关闭所有文件",
"save_all_files": "保存所有文件", "save_all_files": "保存所有文件",
"format": "格式化", "format": "格式化",
"goget": "go get",
"goinstall": "go install", "goinstall": "go install",
"build": "构建", "build": "构建",
"build_n_run": "构建并运行", "build_n_run": "构建并运行",
@ -35,7 +37,9 @@
"unread_notification": "未读通知", "unread_notification": "未读通知",
"notification_2": "没有检查到 gocode这将会导致 [自动完成] 失效", "notification_2": "没有检查到 gocode这将会导致 [自动完成] 失效",
"notification_3": "没有检查到 ide_stub这将会导致 [跳转到声明]、[查找使用] 失效", "notification_3": "没有检查到 ide_stub这将会导致 [跳转到声明]、[查找使用] 失效",
"notification_4": "服务器内部错误",
"goto_line": "跳转到行", "goto_line": "跳转到行",
"goto_file": "打开文件",
"go": "跳转", "go": "跳转",
"tip": "提示", "tip": "提示",
"confirm": "确定", "confirm": "确定",
@ -60,7 +64,7 @@
"ver": "版本", "ver": "版本",
"current_ver": "当前版本", "current_ver": "当前版本",
"dev_team": "开发团队", "dev_team": "开发团队",
"donate_us": "捐赠我们", "donate": "捐赠我们",
"confirm_save": "请确认所有文件已保存", "confirm_save": "请确认所有文件已保存",
"workspace": "工作空间", "workspace": "工作空间",
"project_address": "项目地址", "project_address": "项目地址",
@ -70,6 +74,10 @@
"show_expr_info": "查看表达式信息", "show_expr_info": "查看表达式信息",
"find_usages": "查找使用", "find_usages": "查找使用",
"delete_line": "删除当前行", "delete_line": "删除当前行",
"copy_lines_up": "复制到上方",
"copy_lines_down": "复制到下方",
"move_lines_up": "移动到上方",
"move_lines_down": "移动到下方",
"save_editor_file": "保存当前编辑器文件", "save_editor_file": "保存当前编辑器文件",
"save_all_editors_files": "保存所有编辑器文件", "save_all_editors_files": "保存所有编辑器文件",
"close_editor": "关闭当前编辑器", "close_editor": "关闭当前编辑器",
@ -86,13 +94,13 @@
"focus_notification": "焦点切换到通知窗口", "focus_notification": "焦点切换到通知窗口",
"start-build": "开始 [go build]", "start-build": "开始 [go build]",
"build-succ": "[go build] 成功", "build-succ": "[go build] 成功",
"build-failed": "[go build] 失败", "build-error": "[go build] 失败",
"start-test": "开始 [go test]",
"test-succ": "[go test] 成功",
"test-error": "[go test] 失败",
"start-install": "开始 [go install]", "start-install": "开始 [go install]",
"install-succ": "[go install] 成功", "install-succ": "[go install] 成功",
"install-failed": "[go install] 失败", "install-error": "[go install] 失败",
"start-get": "开始 [go get]",
"get-succ": "[go get] 成功",
"get-failed": "[go get] 失败",
"check_version": "正在检查更新", "check_version": "正在检查更新",
"new_version_available": "新版本可用", "new_version_available": "新版本可用",
"go_env": "Go 环境", "go_env": "Go 环境",
@ -101,5 +109,63 @@
"license": "许可协议", "license": "许可协议",
"credits": "致谢", "credits": "致谢",
"uptodate": "已是最新版本", "uptodate": "已是最新版本",
"colon": "" "test": "测试",
"sign_up": "注册",
"team": "团队",
"sing_up_error": "注册失败",
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成长度为16",
"password_no_match": "密码输入不一致",
"discard": "放弃",
"close": "关闭",
"close_other": "关闭其它",
"clear": "清空",
"preference": "偏好设定",
"appearence": "外观",
"gotool": "Go 工具",
"user": "用户",
"font": "字体",
"font_size": "字体大小",
"line_height": "行高",
"go_format": "Go 格式化",
"locale": "语言环境",
"apply": "应用",
"clearOutput": "清空输出",
"export": "导出",
"refresh": "刷新",
"theme": "主题",
"tab_size": "Tab 大小",
"copy_file_path": "复制文件路径",
"file_tree": "文件树",
"select": "选择",
"expand": "展开",
"collapse": "收起",
"edit": "编辑",
"undo": "撤销",
"redo": "重做",
"cut": "剪切",
"copy": "复制",
"paste": "粘贴",
"select_all": "全选",
"select_identifier": "选择标识符",
"source": "源码",
"toggle_comment": "注释",
"find_in_files": "在文件中查找",
"no_empty": "不能为空",
"open": "打开",
"search_no_match": "没有发现匹配的文件。",
"outline": "大纲",
"govet": "go vet",
"start-vet": "开始 [go vet]",
"vet-succ": "[go vet] 成功",
"vet-error": "[go vet] 失败",
"restore_outline": "恢复大纲",
"share": "分享",
"url": "链接",
"embeded": "嵌入",
"terms": "使用条款",
"download": "下载",
"decompress": "解压缩",
"keymap": "快捷键",
"resize": "调整大小",
"sponsor": "赞助"
} }

171
i18n/zh_TW.json Normal file
View File

@ -0,0 +1,171 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "隨時隨地玩 golang",
"cancel": "取消",
"file": "檔案",
"login": "登入",
"username": "使用者",
"current_user": "當前使用者",
"current_session": "當前會話",
"password": "密碼",
"login_error": "登入失敗",
"run": "執行",
"debug": "Debug",
"help": "說明書",
"check_update": "檢查更新?",
"issues": "問題",
"wide_doc": "Wide 說明",
"about": "關於",
"start_page": "開始頁面",
"create_file": "開新檔案",
"create": "新建",
"create_dir": "新增資料夾",
"delete": "删除",
"rename": "重新命名",
"save": "儲存",
"exit": "離開",
"close_all_files": "關閉所有檔案",
"save_all_files": "儲存所有檔案",
"format": "格式化",
"goinstall": "go install",
"build": "編譯",
"build_n_run": "編譯並執行",
"editor": "編輯器",
"max_editor": "編輯器最大化",
"restore_editor": "編輯器還原",
"unread_notification": "未讀通知",
"notification_2": "没有檢查到 gocode這將會導致「自動完成」失效",
"notification_3": "没有檢查到 ide_stub這將會導致「跳轉到聲明」、「查找使用」失效",
"notification_4": "伺服器內部錯誤",
"goto_line": "跳轉到行",
"goto_file": "開啟舊檔",
"go": "跳到",
"tip": "提示",
"confirm": "確定",
"stop": "停止",
"output": "輸出",
"search": "搜尋",
"notification": "通知",
"min": "縮到最小",
"restore_side": "左側視窗還原",
"search_text": "尋找",
"find": "尋找",
"find_next": "尋找下一個",
"find_previous": "尋找上一個",
"replace": "取代",
"replace_all": "取代全部",
"restore_bottom": "底部視窗還原",
"file_format": "文件格式",
"keyword": "關鍵字",
"user_guide": "使用者說明文件",
"dev_guide": "開發說明文件",
"keyboard_shortcuts": "鍵盤快捷鍵",
"ver": "版本",
"current_ver": "當前版本",
"dev_team": "開發團隊",
"donate_us": "愛心捐贈",
"confirm_save": "請確認所有檔案都已儲存",
"workspace": "工作空間",
"project_address": "項目地址",
"community": "社區",
"autocomplete": "自動完成",
"jump_to_decl": "跳轉到聲明",
"show_expr_info": "查看表達式信息",
"find_usages": "尋找使用",
"delete_line": "删除當前行",
"copy_lines_up": "複製到上一行",
"copy_lines_down": "複製到下一行",
"move_lines_up": "移動到上一行",
"move_lines_down": "移動到下一行",
"save_editor_file": "儲存當前編輯檔案",
"save_all_editors_files": "儲存所有檔案",
"close_editor": "關閉當前編輯器",
"full_screen": "全螢幕",
"auto_indent": "自動縮進",
"indent": "縮進",
"unindent": "縮進還原",
"focus": "焦點",
"switch_tab": "切換編輯器/視窗组 tab",
"focus_editor": "切換至編輯器",
"focus_file_tree": "切換至檔案樹",
"focus_output": "切換至输出視窗",
"focus_search": "切換至搜索視窗",
"focus_notification": "切換至通知視窗",
"start-build": "開始 [go build]",
"build-succ": "[go build] 成功",
"build-error": "[go build] 失敗",
"start-test": "開始 [go test]",
"test-succ": "[go test] 成功",
"test-error": "[go test] 失敗",
"start-install": "開始 [go install]",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"check_version": "正在檢查更新",
"new_version_available": "可用新版本",
"go_env": "Go 環境",
"os": "操作系统",
"project": "項目",
"license": "許可協議",
"credits": "致謝",
"uptodate": "已是最新版本",
"test": "測試",
"sign_up": "註冊",
"team": "團隊",
"sing_up_error": "註冊失敗",
"user_name_ruler": "帳號只能由 az, AZ, 0-9, _ 組成長度為16",
"password_no_match": "密碼輸入不一致",
"discard": "捨棄",
"close": "關閉",
"close_other": "關閉其它",
"clear": "清空",
"preference": "偏好設定",
"appearence": "外觀",
"gotool": "Go 工具",
"user": "使用者",
"font": "字體",
"font_size": "字體大小",
"line_height": "行高",
"go_format": "Go 格式化",
"locale": "語言環境",
"apply": "應用",
"clearOutput": "清空輸出",
"export": "導出",
"refresh": "刷新",
"theme": "主題",
"tab_size": "Tab 大小",
"copy_file_path": "複製檔案位置",
"file_tree": "文件樹",
"select": "選擇",
"expand": "展開",
"collapse": "收起",
"edit": "編輯",
"undo": "復原",
"redo": "回復",
"cut": "剪下",
"copy": "複製",
"paste": "天上",
"select_all": "全選",
"select_identifier": "選擇標識符",
"source": "原始碼",
"toggle_comment": "註解",
"find_in_files": "在文件中尋找",
"no_empty": "不能為空",
"open": "開啟",
"search_no_match": "沒有發現匹配的文件。",
"outline": "大綱",
"govet": "go vet",
"start-vet": "開始 [go vet]",
"vet-succ": "[go vet] 成功",
"vet-error": "[go vet] 失敗",
"restore_outline": "恢復大綱",
"share": "分享",
"url": "連結",
"embeded": "嵌入",
"terms": "使用條款",
"download": "下載",
"decompress": "解壓縮",
"keymap": "快速鍵",
"resize": "調整大小",
"sponsor": "贊助"
}

513
main.go
View File

@ -1,163 +1,216 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main package main
import ( import (
"encoding/json" "compress/gzip"
"flag" "flag"
"html/template" "html/template"
"math/rand" "io"
"mime"
"net/http" "net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime" "runtime"
"strconv" "strings"
"syscall"
"time" "time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/editor" "github.com/88250/wide/conf"
"github.com/b3log/wide/event" "github.com/88250/wide/editor"
"github.com/b3log/wide/file" "github.com/88250/wide/event"
"github.com/b3log/wide/i18n" "github.com/88250/wide/file"
"github.com/b3log/wide/notification" "github.com/88250/wide/i18n"
"github.com/b3log/wide/output" "github.com/88250/wide/notification"
"github.com/b3log/wide/session" "github.com/88250/wide/output"
"github.com/b3log/wide/shell" "github.com/88250/wide/playground"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
"github.com/golang/glog"
) )
const ( // Logger
Ver = "1.0.0" // 当前 Wide 版本 var logger *gulu.Logger
)
// Wide 中唯一一个 init 函数. // The only one init function in Wide.
func init() { func init() {
// TODO: 默认启动参数 confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
flag.Set("logtostderr", "true") confData := flag.String("data", "", "path of data dir")
flag.Set("v", "3") confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified")
confReadOnly := flag.String("readonly", "", "this will overrite Wide.ReadOnly if specified")
confSiteStatCode := flag.String("site_stat_code", "", "this will overrite Wide.SiteStatCode if specified")
flag.Parse() flag.Parse()
// 加载事件处理 gulu.Log.SetLevel("warn")
logger = gulu.Log.NewLogger(os.Stdout)
//wd := gulu.OS.Pwd()
//if strings.HasPrefix(wd, os.TempDir()) {
// logger.Error("Don't run Wide in OS' temp directory or with `go run`")
//
// os.Exit(-1)
//}
i18n.Load()
event.Load() event.Load()
conf.Load(*confPath, *confData, *confServer, *confLogLevel, *confReadOnly, template.HTML(*confSiteStatCode))
// 加载配置
conf.Load()
// 定时检查运行环境
conf.FixedTimeCheckEnv() conf.FixedTimeCheckEnv()
session.FixedTimeSave()
// 定时保存配置
conf.FixedTimeSave()
// 定时检查无效会话
session.FixedTimeRelease() session.FixedTimeRelease()
session.FixedTimeReport()
logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
} }
// 登录. // Main.
func loginHandler(w http.ResponseWriter, r *http.Request) { func main() {
i18n.Load() initMime()
handleSignal()
if "GET" == r.Method { // IDE
// 展示登录页面 http.HandleFunc("/", handlerGzWrapper(indexHandler))
http.HandleFunc("/start", handlerWrapper(startHandler))
http.HandleFunc("/about", handlerWrapper(aboutHandler))
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale), // static resources
"locale": conf.Wide.Locale, "ver": Ver} http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.Handle("/static/users/", http.StripPrefix("/static/", http.FileServer(http.Dir(conf.Wide.Data+"/static"))))
serveSingle("/favicon.ico", "./static/images/favicon.png")
t, err := template.ParseFiles("views/login.html") // oauth
http.HandleFunc("/login/redirect", session.LoginRedirectHandler)
http.HandleFunc("/login/callback", session.LoginCallbackHandler)
if nil != err { // session
glog.Error(err) http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
http.Error(w, err.Error(), 500) http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
return // run
http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
http.HandleFunc("/run", handlerWrapper(output.RunHandler))
http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
http.HandleFunc("/go/test", handlerWrapper(output.GoTestHandler))
http.HandleFunc("/go/vet", handlerWrapper(output.GoVetHandler))
http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
// cross-compilation
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
// file tree
http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
// outline
http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
// file export
http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
// editor
http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
// notification
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
// user
http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
// playground
http.HandleFunc("/playground", handlerWrapper(playground.IndexHandler))
http.HandleFunc("/playground/", handlerWrapper(playground.IndexHandler))
http.HandleFunc("/playground/ws", handlerWrapper(playground.WSHandler))
http.HandleFunc("/playground/save", handlerWrapper(playground.SaveHandler))
http.HandleFunc("/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc("/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc("/playground/stop", handlerWrapper(playground.StopHandler))
http.HandleFunc("/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server)
err := http.ListenAndServe("0.0.0.0:7070", nil)
if err != nil {
logger.Error(err)
} }
t.Execute(w, model)
return
}
// 非 GET 请求当作是登录请求
succ := false
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
args := struct {
Username string
Password string
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
succ = true
return
}
for _, user := range conf.Wide.Users {
if user.Name == args.Username && user.Password == args.Password {
succ = true
}
}
if !succ {
return
}
// 创建 HTTP 会话
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
glog.Infof("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
} }
// 退出(登出). // indexHandler handles request of Wide index.
func logoutHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// Wide 首页.
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
i18n.Load() if "/" != r.RequestURI {
http.Redirect(w, r, "/", http.StatusFound)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
uid := httpSession.Values["uid"].(string)
if "playground" == uid { // reserved user for Playground
http.Redirect(w, r, "/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
// 创建一个 Wide 会话 user := conf.GetUser(uid)
wideSession := session.WideSessions.New(httpSession) if nil == user {
http.Redirect(w, r, "/login", http.StatusFound)
username := httpSession.Values["username"].(string) return
locale := conf.Wide.GetUser(username).Locale }
wideSessions := session.WideSessions.GetByUsername(username) locale := user.Locale
userConf := conf.Wide.GetUser(username)
wideSessions := session.WideSessions.GetByUserId(uid)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession, "latestSessionContent": userConf.LatestSessionContent, "uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator} "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": []string{"darwin_amd64", "linux_amd64", "windows_amd64"}}
glog.V(3).Infof("User [%s] has [%d] sessions", username, len(wideSessions)) logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
t, err := template.ParseFiles("views/index.html") t, err := template.ParseFiles("views/index.html")
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -165,21 +218,34 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model) t.Execute(w, model)
} }
// 单个文件资源请求处理. // handleSignal handles system signal for graceful shutdown.
func handleSignal() {
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
s := <-c
logger.Tracef("Got signal [%s]", s)
session.SaveOnlineUsers()
logger.Tracef("Saved all online user, exit")
os.Exit(0)
}()
}
// serveSingle registers the handler function for the given pattern and filename.
func serveSingle(pattern string, filename string) { func serveSingle(pattern string, filename string) {
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filename) http.ServeFile(w, r, filename)
}) })
} }
// 起始页请求处理. // startHandler handles request of start page.
func startHandler(w http.ResponseWriter, r *http.Request) { func startHandler(w http.ResponseWriter, r *http.Request) {
i18n.Load() httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/s", http.StatusFound)
return return
} }
@ -187,18 +253,25 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.Wide.GetUser(username).Locale user := conf.GetUser(uid)
userWorkspace := conf.Wide.GetUserWorkspace(username) locale := user.Locale
userWorkspace := conf.GetUserWorkspace(uid)
sid := r.URL.Query()["sid"][0]
wSession := session.WideSessions.Get(sid)
if nil == wSession {
logger.Errorf("Session [%s] not found", sid)
}
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"username": username, "workspace": userWorkspace, "ver": Ver} "uid": uid, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid, "username": user.Name}
t, err := template.ParseFiles("views/start.html") t, err := template.ParseFiles("views/start.html")
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -206,12 +279,9 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model) t.Execute(w, model)
} }
// 键盘快捷键页请求处理. // keyboardShortcutsHandler handles request of keyboard shortcuts page.
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) { func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
i18n.Load() httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -221,16 +291,16 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.GetUser(uid).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale} model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
t, err := template.ParseFiles("views/keyboard_shortcuts.html") t, err := template.ParseFiles("views/keyboard_shortcuts.html")
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -238,12 +308,9 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model) t.Execute(w, model)
} }
// 关于页请求处理. // aboutHandle handles request of about page.
func aboutHandler(w http.ResponseWriter, r *http.Request) { func aboutHandler(w http.ResponseWriter, r *http.Request) {
i18n.Load() httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -253,17 +320,17 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.GetUser(uid).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, "ver": Ver, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()} "ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
t, err := template.ParseFiles("views/about.html") t, err := template.ParseFiles("views/about.html")
if nil != err { if nil != err {
glog.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -271,112 +338,104 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model) t.Execute(w, model)
} }
// 主程序入口. // handlerWrapper wraps the HTTP Handler for some common processes.
func main() {
runtime.GOMAXPROCS(conf.Wide.MaxProcs)
defer glog.Flush()
// IDE
http.HandleFunc("/login", handlerWrapper(loginHandler))
http.HandleFunc("/logout", handlerWrapper(logoutHandler))
http.HandleFunc("/", handlerWrapper(indexHandler))
http.HandleFunc("/start", handlerWrapper(startHandler))
http.HandleFunc("/about", handlerWrapper(aboutHandler))
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
// 静态资源
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
serveSingle("/favicon.ico", "./static/favicon.ico")
// 库资源
http.Handle("/data/", http.StripPrefix("/data/", http.FileServer(http.Dir("data"))))
// 会话
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
http.HandleFunc("/session/save", handlerWrapper(session.SaveContent))
// 运行相关
http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
http.HandleFunc("/run", handlerWrapper(output.RunHandler))
http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
http.HandleFunc("/go/get", handlerWrapper(output.GoGetHandler))
http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
// 文件树
http.HandleFunc("/files", handlerWrapper(file.GetFiles))
http.HandleFunc("/file", handlerWrapper(file.GetFile))
http.HandleFunc("/file/save", handlerWrapper(file.SaveFile))
http.HandleFunc("/file/new", handlerWrapper(file.NewFile))
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFile))
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchText))
// 编辑器
http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler))
http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
http.HandleFunc("/html/fmt", handlerWrapper(editor.HTMLFmtHandler))
http.HandleFunc("/json/fmt", handlerWrapper(editor.JSONFmtHandler))
// Shell
http.HandleFunc("/shell/ws", handlerWrapper(shell.WSHandler))
http.HandleFunc("/shell", handlerWrapper(shell.IndexHandler))
// 通知
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
// 用户
http.HandleFunc("/user/new", handlerWrapper(session.AddUser))
http.HandleFunc("/user/repos/init", handlerWrapper(session.InitGitRepos))
// 文档
http.Handle("/doc/", http.StripPrefix("/doc/", http.FileServer(http.Dir("doc"))))
glog.V(0).Infof("Wide is running [%s]", conf.Wide.Server)
err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil {
glog.Fatal(err)
}
}
// HTTP Handler 包装,完成共性处理.
//
// 共性处理:
// //
// 1. panic recover // 1. panic recover
// 2. 请求计时 // 2. request stopwatch
// 3. i18n
func handlerWrapper(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { func handlerWrapper(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
handler := panicRecover(f) handler := panicRecover(f)
handler = stopwatch(handler) handler = stopwatch(handler)
handler = i18nLoad(handler)
return handler return handler
} }
// Handler 包装请求计时. // handlerGzWrapper wraps the HTTP Handler for some common processes.
//
// 1. panic recover
// 2. gzip response
// 3. request stopwatch
// 4. i18n
func handlerGzWrapper(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
handler := panicRecover(f)
handler = gzipWrapper(handler)
handler = stopwatch(handler)
handler = i18nLoad(handler)
return handler
}
// gzipWrapper wraps the process with response gzip.
func gzipWrapper(f func(http.ResponseWriter, *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
f(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
f(gzr, r)
}
}
// i18nLoad wraps the i18n process.
func i18nLoad(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
i18n.Load()
handler(w, r)
}
}
// stopwatch wraps the request stopwatch process.
func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
start := time.Now() start := time.Now()
defer func() { defer func() {
glog.V(5).Infof("[%s] [%s]", r.RequestURI, time.Since(start)) logger.Tracef("[%s, %s, %s]", r.Method, r.RequestURI, time.Since(start))
}() }()
// Handler 处理
handler(w, r) handler(w, r)
} }
} }
// Handler 包装 recover panic. // panicRecover wraps the panic recover process.
func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer gulu.Panic.Recover(nil)
// Handler 处理
handler(w, r) handler(w, r)
} }
} }
// initMime initializes mime types.
//
// We can't get the mime types on some OS (such as Windows XP) by default, so initializes them here.
func initMime() {
mime.AddExtensionType(".css", "text/css")
mime.AddExtensionType(".js", "application/x-javascript")
mime.AddExtensionType(".json", "application/json")
}
// gzipResponseWriter represents a gzip response writer.
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
// Write writes response with appropriate 'Content-Type'.
func (w gzipResponseWriter) Write(b []byte) (int, error) {
if "" == w.Header().Get("Content-Type") {
// If no content type, apply sniffing algorithm to un-gzipped body.
w.Header().Set("Content-Type", http.DetectContentType(b))
}
return w.Writer.Write(b)
}

View File

@ -1,30 +1,48 @@
// 通知. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package notification includes notification related manipulations.
package notification package notification
import ( import (
"net/http" "net/http"
"os"
"strconv"
"time" "time"
"strconv" "github.com/88250/gulu"
"github.com/b3log/wide/conf" "github.com/88250/wide/conf"
"github.com/b3log/wide/event" "github.com/88250/wide/event"
"github.com/b3log/wide/i18n" "github.com/88250/wide/i18n"
"github.com/b3log/wide/session" "github.com/88250/wide/session"
"github.com/b3log/wide/util" "github.com/88250/wide/util"
"github.com/golang/glog"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const ( const (
Error = "ERROR" // 通知.严重程度:ERROR error = "ERROR" // notification.severity: ERROR
Warn = "WARN" // 通知.严重程度:WARN warn = "WARN" // notification.severity: WARN
Info = "INFO" // 通知.严重程度:INFO info = "INFO" // notification.severity: INFO
Setup = "Setup" // 通知.类型:安装 setup = "Setup" // notification.type: setup
server = "Server" // notification.type: server
) )
// 通知结构. // Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// Notification represents a notification.
type Notification struct { type Notification struct {
event *event.Event event *event.Event
Type string `json:"type"` Type string `json:"type"`
@ -32,75 +50,74 @@ type Notification struct {
Message string `json:"message"` Message string `json:"message"`
} }
// 用户事件处理:将事件转为通知,并通过通知通道推送给前端. // event2Notification processes user event by converting the specified event to a notification, and then push it to front
// // browser with notification channel.
// 当用户事件队列接收到事件时将会调用该函数进行处理.
func event2Notification(e *event.Event) { func event2Notification(e *event.Event) {
if nil == session.NotificationWS[e.Sid] { if nil == session.NotificationWS[e.Sid] {
return return
} }
wsChannel := session.NotificationWS[e.Sid] wsChannel := session.NotificationWS[e.Sid]
if nil == wsChannel {
var notification Notification
switch e.Code {
case event.EvtCodeGocodeNotFound:
notification = Notification{event: e, Type: Setup, Severity: Error}
case event.EvtCodeIDEStubNotFound:
notification = Notification{event: e, Type: Setup, Severity: Error}
default:
glog.Warningf("Can't handle event[code=%d]", e.Code)
return return
} }
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, "wide-session") httpSession, _ := session.HTTPSession.Get(wsChannel.Request, session.CookieName)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.GetUser(uid).Locale
// 消息国际化处理 var notification *Notification
notification.Message = i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string)
wsChannel.Conn.WriteJSON(&notification) switch e.Code {
case event.EvtCodeGocodeNotFound:
fallthrough
case event.EvtCodeIDEStubNotFound:
notification = &Notification{event: e, Type: setup, Severity: error,
Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string)}
case event.EvtCodeServerInternalError:
notification = &Notification{event: e, Type: server, Severity: error,
Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string) + " [" + e.Data.(string) + "]"}
default:
logger.Warnf("Can't handle event[code=%d]", e.Code)
// 更新通道最近使用时间 return
wsChannel.Time = time.Now() }
wsChannel.WriteJSON(notification)
wsChannel.Refresh()
} }
// 建立通知通道. // WSHandler handles request of creating notification channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if nil == wSession {
glog.Errorf("Session [%s] not found", sid)
if nil == wSession {
return return
} }
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"notification": "Notification initialized", "cmd": "init-notification"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
session.NotificationWS[sid] = &wsChan session.NotificationWS[sid] = &wsChan
glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS)) logger.Tracef("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS))
// 添加用户事件处理器 // add user event handler
wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification)) wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification))
input := map[string]interface{}{} input := map[string]interface{}{}
for { for {
if err := wsChan.Conn.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Notification WS ERROR: " + err.Error())
return return
} }
} }

315
output/build.go Normal file
View File

@ -0,0 +1,315 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// BuildHandler handles request of building.
func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
user := conf.GetUser(uid)
locale := user.Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
curDir := filepath.Dir(filePath)
fout, err := os.Create(filePath)
if nil != err {
logger.Error(err)
result.Code = -1
return
}
code := args["code"].(string)
if _, err := fout.WriteString(code); nil != err {
logger.Error(err)
result.Code = -1
return
}
fout.Close()
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go build]" in front-end browser
msg := i18n.Get(locale, "start-build").(string)
msg = strings.Replace(msg, "build]", "build "+fmt.Sprint(user.BuildArgs(runtime.GOOS))+"]", 1)
channelRet["output"] = "<span class='start-build'>" + msg + "</span>\n"
channelRet["cmd"] = "start-build"
wsChannel := session.OutputWS[sid]
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
var goModCmd *exec.Cmd
if !gulu.File.IsExist(filepath.Join(curDir, "go.mod")) {
curDirName := filepath.Base(curDir)
goModCmd = exec.Command("go", "mod", "init", curDirName)
} else {
goModCmd = exec.Command("go", "mod", "tidy")
}
goModCmd.Dir = curDir
setCmdEnv(goModCmd, uid)
outputBytes, err := goModCmd.CombinedOutput()
output := string(outputBytes)
if nil != err && strings.Contains(output, "go.mod already exists") {
logger.Error(err.Error() + ": " + output)
result.Code = -1
return
}
var goBuildArgs []string
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(runtime.GOOS)...)
//if !gulu.Str.Contains("-i", goBuildArgs) {
// goBuildArgs = append(goBuildArgs, "-i")
//}
cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
suffix := ""
if gulu.OS.IsWindows() {
suffix = ".exe"
}
executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
channelRet["cmd"] = "build"
channelRet["executable"] = executable
outReader := bufio.NewReader(stdout)
/////////
go func() {
defer gulu.Panic.Recover(nil)
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := outReader.ReadString('\n')
if io.EOF == err {
break
}
_, ok := err.(*os.PathError)
if ok {
// 构建时报 “read |0: file already closed” https://github.com/b3log/wide/issues/363
break
}
if nil != err {
logger.Warnf("%#v", err)
break
}
channelRet["output"] = line
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}()
errReader := bufio.NewReader(stderr)
var lines []string
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := errReader.ReadString('\n')
if io.EOF == err {
break
}
lines = append(lines, line)
if nil != err {
logger.Warn(err)
break
}
// path process
errOutWithPath := parsePath(curDir, line)
channelRet["output"] = "<span class='stderr'>" + errOutWithPath + "</span>"
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
if nil == cmd.Wait() {
channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
} else {
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n"
// lint process
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 || !strings.Contains(line, ":") {
continue
}
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, err = strconv.Atoi(left[:index])
if nil != err {
continue
}
msg = left[index+2:]
}
lint := &Lint{
File: filepath.ToSlash(filepath.Join(curDir, file)),
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
}
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
return
}
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}

252
output/cross.go Normal file
View File

@ -0,0 +1,252 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// CrossCompilationHandler handles request of cross compilation.
func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["path"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
platform := args["platform"].(string)
goos := strings.Split(platform, "_")[0]
goarch := strings.Split(platform, "_")[1]
curDir := filepath.Dir(filePath)
suffix := ""
if "windows" == goos {
suffix = ".exe"
}
user := conf.GetUser(uid)
goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
for i, env := range cmd.Env {
if strings.HasPrefix(env, "GOOS=") {
cmd.Env[i] = "GOOS=" + goos
continue
}
if strings.HasPrefix(env, "GOARCH=") {
cmd.Env[i] = "GOARCH=" + goarch
continue
}
}
executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable)
name := filepath.Base(curDir) + "-" + goos + "-" + goarch
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go build]" in front-end browser
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
channelRet["cmd"] = "start-build"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer cmd.Wait()
// read all
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "cross-build"
channelRet["executable"] = executable
channelRet["name"] = name
if 0 == len(buf) { // build success
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
} else { // build error
// build gutter lint
errOut := string(buf)
lines := strings.Split(errOut, "\n")
// path process
var errOutWithPath string
for _, line := range lines {
errOutWithPath += parsePath(curDir, line) + "\n"
}
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" +
"<span class='stderr'>" + errOutWithPath + "</span>"
// lint process
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 {
continue
}
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, err = strconv.Atoi(left[:index])
if nil != err {
continue
}
msg = left[index+2:]
}
lint := &Lint{
File: filepath.Join(curDir, file),
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

204
output/install.go Normal file
View File

@ -0,0 +1,204 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// GoInstallHandler handles request of go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
logger.Debugf("go install %s", curDir)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go install]" in front-end browser
channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n"
channelRet["cmd"] = "start-install"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
// read all
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go install"
if 0 != len(buf) { // build error
// build gutter lint
errOut := string(buf)
lines := strings.Split(errOut, "\n")
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 {
continue
}
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, _ = strconv.Atoi(left[:index])
msg = left[index+2:]
}
lint := &Lint{
File: file,
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
channelRet["output"] = "<span class='install-error'>" + i18n.Get(locale, "install-error").(string) + "</span>\n" + errOut
} else {
channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n"
}
if nil != session.OutputWS[sid] {
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,723 +1,132 @@
// 构建、运行、go tool 操作. // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package output includes build, run and go tool related manipulations.
package output package output
import ( import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/i18n" "github.com/88250/wide/conf"
"github.com/b3log/wide/session" "github.com/88250/wide/session"
"github.com/b3log/wide/util" "github.com/88250/wide/util"
"github.com/golang/glog"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const ( const (
lintSeverityError = "error" // Lint 严重级别:错误 lintSeverityError = "error" // lint severity: error
lintSeverityWarn = "warning" // Lint 严重级别:警告 lintSeverityWarn = "warning" // lint severity: warning
) )
// 代码 Lint 结构. // Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// Lint represents a code lint.
type Lint struct { type Lint struct {
File string `json:"file"` File string `json:"file"`
LineNo int `json:"lineNo"` LineNo int `json:"lineNo"`
Severity string `json:"severity"` Severity string `json:"severity"`
Msg string Msg string `json:"msg"`
} }
// 建立输出通道. // WSHandler handles request of creating output channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
session.OutputWS[sid] = &wsChan session.OutputWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"} logger.Tracef("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
wsChan.Conn.WriteJSON(&ret)
glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
} }
// 运行一个可执行文件. // parsePath parses file path in the specified outputLine, and returns new line with front-end friendly.
func RunHandler(w http.ResponseWriter, r *http.Request) { func parsePath(curDir, outputLine string) string {
data := map[string]interface{}{"succ": true} index := strings.Index(outputLine, " ")
defer util.RetJSON(w, r, data) if -1 == index || index >= len(outputLine) {
return outputLine
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
} }
sid := args["sid"].(string) pathPart := outputLine[:index]
wSession := session.WideSessions.Get(sid) msgPart := outputLine[index:]
if nil == wSession {
data["succ"] = false
return parts := strings.Split(pathPart, ":")
if len(parts) < 2 { // no file path info (line & column) found
return outputLine
} }
filePath := args["executable"].(string) file := parts[0]
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)] line := parts[1]
if _, err := strconv.Atoi(line); nil != err {
cmd := exec.Command(filePath) return outputLine
cmd.Dir = curDir
stdout, err := cmd.StdoutPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
} }
stderr, err := cmd.StderrPipe() column := "0"
if nil != err { hasColumn := 4 == len(parts)
glog.Error(err) if hasColumn {
data["succ"] = false column = parts[2]
return
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) tagStart := `<span class="path" data-path="` + filepath.ToSlash(filepath.Join(curDir, file)) + `" data-line="` + line +
`" data-column="` + column + `">`
if err := cmd.Start(); nil != err { text := file + ":" + line
glog.Error(err) if hasColumn {
data["succ"] = false text += ":" + column
return
} }
tagEnd := "</span>:"
// 添加到用户进程集中 return tagStart + text + tagEnd + msgPart
processes.add(wSession, cmd.Process)
channelRet := map[string]interface{}{}
channelRet["pid"] = cmd.Process.Pid
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath)
// 在读取程序输出前先返回一次,使前端获取到 run 状态与 pid
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
for {
buf, err := reader.ReadBytes('\n')
if nil != err || 0 == len(buf) {
// 从用户进程集中移除这个执行完毕(或是被主动停止)的进程
processes.remove(wSession, cmd.Process)
glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath)
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run-done"
channelRet["output"] = "<pre>" + string(buf) + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
break
} else {
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run"
channelRet["output"] = "<pre>" + string(buf) + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
}
}
}(rand.Int())
} }
// 构建可执行文件. func setCmdEnv(cmd *exec.Cmd, uid string) {
func BuildHandler(w http.ResponseWriter, r *http.Request) { userWorkspace := conf.GetUserWorkspace(uid)
data := map[string]interface{}{"succ": true} cache, err := os.UserCacheDir()
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
fout, err := os.Create(filePath)
if nil != err { if nil != err {
glog.Error(err) logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
data["succ"] = false cache = os.TempDir()
return
} }
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
suffix := ""
if "windows" == runtime.GOOS {
suffix = ".exe"
}
executable := "main" + suffix
argv := []string{"build", "-o", executable}
cmd := exec.Command("go", argv...)
cmd.Dir = curDir
setCmdEnv(cmd, username)
glog.V(5).Infof("go build -o %s", executable)
executable = curDir + conf.PathSeparator + executable
// 先把可执行文件删了
err = os.RemoveAll(executable)
if nil != err {
glog.Info(err)
data["succ"] = false
return
}
stdout, err := cmd.StdoutPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
if !data["succ"].(bool) {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始构建”
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
channelRet["cmd"] = "start-build"
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir)
// 一次性读取
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "build"
channelRet["executable"] = executable
if 0 == len(buf) { // 说明构建成功,没有错误信息输出
// 设置下一次执行命令(前端会根据该参数发送请求)
channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
go func() { // 运行 go install生成的库用于 gocode lib-path
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, username)
out, _ := cmd.CombinedOutput()
if len(out) > 0 {
glog.Warning(string(out))
}
}()
} else { // 构建失败
// 解析错误信息,返回给编辑器 gutter lint
errOut := string(buf)
channelRet["output"] = "<span class='build-failed'>" + i18n.Get(locale, "build-failed").(string) + "</span>\n" + errOut
lines := strings.Split(errOut, "\n")
if lines[0][0] == '#' {
lines = lines[1:] // 跳过第一行
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 {
continue
}
if line[0] == '\t' {
// 添加到上一个 lint 中
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")])
msg := left[strings.Index(left, ":")+2:]
lint := &Lint{
File: file,
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
}
if nil != session.OutputWS[sid] {
glog.V(3).Infof("Session [%s] 's build [id=%d, dir=%s] has done", sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
}(rand.Int())
}
// go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
fout, err := os.Create(filePath)
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, username)
glog.V(5).Infof("go install %s", curDir)
stdout, err := cmd.StdoutPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
if !data["succ"].(bool) {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始 go install”
channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n"
channelRet["cmd"] = "start-install"
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir)
// 一次性读取
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go install"
if 0 != len(buf) { // 构建失败
// 解析错误信息,返回给编辑器 gutter lint
errOut := string(buf)
lines := strings.Split(errOut, "\n")
if lines[0][0] == '#' {
lines = lines[1:] // 跳过第一行
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 {
continue
}
if line[0] == '\t' {
// 添加到上一个 lint 中
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")])
msg := left[strings.Index(left, ":")+2:]
lint := &Lint{
File: file,
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
channelRet["output"] = "<span class='install-failed'>" + i18n.Get(locale, "install-failed").(string) + "</span>\n" + errOut
} else {
channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n"
}
if nil != session.OutputWS[sid] {
glog.V(3).Infof("Session [%s] 's running [go install] [id=%d, dir=%s] has done", sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
}(rand.Int())
}
// go get.
func GoGetHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
decoder := json.NewDecoder(r.Body)
var args map[string]interface{}
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
cmd := exec.Command("go", "get")
cmd.Dir = curDir
setCmdEnv(cmd, username)
stdout, err := cmd.StdoutPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
if !data["succ"].(bool) {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始 go get
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
channelRet["cmd"] = "start-get"
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [go get] [runningId=%d]", sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get"
// 一次性读取
buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) {
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done (with error)", sid, runningId)
channelRet["output"] = "<span class='get-failed'>" + i18n.Get(locale, "get-failed").(string) + "</span>\n" + string(buf)
} else {
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done", sid, runningId)
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
// 更新通道最近使用时间
wsChannel.Time = time.Now()
}
}(rand.Int())
}
// 结束正在运行的进程.
func StopHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
return
}
processes.kill(wSession, pid)
}
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.Wide.GetUserWorkspace(username)
masterWorkspace := conf.Wide.GetWorkspace()
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace+conf.PathListSeparator+masterWorkspace, "GOPROXY=https://goproxy.cn",
"GO111MODULE=on",
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS, "GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH, "GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(), "GOROOT="+runtime.GOROOT(),
"GOCACHE="+cache,
"PATH="+os.Getenv("PATH")) "PATH="+os.Getenv("PATH"))
if gulu.OS.IsWindows() {
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized.
cmd.Env = append(cmd.Env, os.Environ()...)
} else {
// 编译链接时找不到依赖的动态库 https://github.com/b3log/wide/issues/352
cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+os.Getenv("LD_LIBRARY_PATH"))
}
} }

View File

@ -1,92 +0,0 @@
package output
import (
"os"
"sync"
"github.com/b3log/wide/session"
"github.com/golang/glog"
)
// 进程集类型.
type procs map[string][]*os.Process
// 所有用户正在运行的程序进程集.
//
// <sid, []*os.Process>
var processes = procs{}
// 排它锁,防止并发修改.
var mutex sync.Mutex
// 添加用户执行进程.
func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.Id
userProcesses := (*procs)[sid]
userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses
// 会话关联进程
wSession.SetProcesses(userProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
}
// 移除用户执行进程.
func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.Id
userProcesses := (*procs)[sid]
var newProcesses []*os.Process
for i, p := range userProcesses {
if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// 会话关联进程
wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
return
}
}
}
// 结束用户正在执行的进程.
func (procs *procs) kill(wSession *session.WideSession, pid int) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.Id
userProcesses := (*procs)[sid]
for i, p := range userProcesses {
if p.Pid == pid {
if err := p.Kill(); nil != err {
glog.Error("Kill a process [pid=%d] of session [%s] failed [error=%v]", pid, sid, err)
} else {
var newProcesses []*os.Process
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// 会话关联进程
wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid)
}
return
}
}
}

30
output/run.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"github.com/88250/wide/session"
"net/http"
)
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) {
session.RunHandler(w, r, session.OutputWS)
}
// StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
session.StopHandler(w, r)
}

149
output/test.go Normal file
View File

@ -0,0 +1,149 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// GoTestHandler handles request of go test.
func GoTestHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command("go", "test", "-v")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go test]" in front-end browser
channelRet["output"] = "<span class='start-test'>" + i18n.Get(locale, "start-test").(string) + "</span>\n"
channelRet["cmd"] = "start-test"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test"
// read all
buf, _ := ioutil.ReadAll(reader)
// waiting for go test finished
cmd.Wait()
if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", uid, sid, runningId)
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", uid, sid, runningId)
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

149
output/vet.go Normal file
View File

@ -0,0 +1,149 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// GoVetHandler handles request of go vet.
func GoVetHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command("go", "vet", ".")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go vet]" in front-end browser
channelRet["output"] = "<span class='start-vet'>" + i18n.Get(locale, "start-vet").(string) + "</span>\n"
channelRet["cmd"] = "start-vet"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go vet"
// read all
buf, _ := ioutil.ReadAll(reader)
// waiting for go vet finished
cmd.Wait()
if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", uid, sid, runningId)
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", uid, sid, runningId)
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

3773
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "wide",
"version": "1.6.0",
"description": "A Web-based Go IDE , do your development anytime, anywhere.",
"homepage": "https://wide.b3log.org",
"repository": {
"type": "git",
"url": "git://github.com/88250/wide.git"
},
"bugs": {
"url": "https://github.com/88250/wide/issues"
},
"license": "Apache License",
"private": true,
"author": "Daniel <d@b3log.org> (http://88250.b3log.org) & Vanessa <v@b3log.org> (http://vanessa.b3log.org)",
"maintainers": [
{
"name": "Daniel",
"email": "d@b3log.org"
},
{
"name": "Vanessa",
"email": "v@b3log.org"
}
],
"scripts": {
"build": "gulp"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp-clean-css": "^4.2.0",
"gulp-concat": "^2.6.1",
"gulp-sourcemaps": "^2.6.5",
"gulp-uglify": "^3.0.1"
}
}

80
pkg.sh Executable file
View File

@ -0,0 +1,80 @@
#!/bin/bash
# Wide package tool.
#
# Command:
# ./pkg.sh ${version} ${target}
# Example:
# ./pkg.sh 1.0.0 /home/daniel/1.0.0/
ver=$1
target=$2
list="conf doc i18n static views README.md TERMS.md LICENSE"
mkdir -p ${target}
echo version=${ver}
echo target=${target}
## darwin
os=darwin
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
## linux
os=linux
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
## windows
os=windows
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe

111
playground/autocomplete.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package playground
import (
"bytes"
"encoding/json"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
)
// AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
if conf.Wide.ReadOnly {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
code := args["code"].(string)
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
file, err := os.Create("wide_autocomplete_" + gulu.Rand.String(16) + ".go")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
file.WriteString(code)
file.Close()
path := file.Name()
defer os.Remove(path)
offset := getCursorOffset(code, line, ch)
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
cmd := exec.Command(gocode, argv...)
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(output)
}
// getCursorOffset calculates the cursor offset.
//
// line is the line number, starts with 0 that means the first line
// ch is the column number, starts with 0 that means the first column
func getCursorOffset(code string, line, ch int) (offset int) {
lines := strings.Split(code, "\n")
// calculate sum length of lines before
for i := 0; i < line; i++ {
offset += len(lines[i])
}
// calculate length of the current line and column
curLine := lines[line]
var buffer bytes.Buffer
r := []rune(curLine)
for i := 0; i < ch; i++ {
buffer.WriteString(string(r[i]))
}
offset += len(buffer.String()) // append length of current line
offset += line // append number of '\n'
return offset
}

81
playground/build.go Normal file
View File

@ -0,0 +1,81 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package playground
import (
"encoding/json"
"html/template"
"net/http"
"os/exec"
"path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
)
// BuildHandler handles request of Playground building.
func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
fileName := args["fileName"].(string)
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
suffix := ""
if gulu.OS.IsWindows() {
suffix = ".exe"
}
data := map[string]interface{}{}
result.Data = &data
executable := filepath.Clean(conf.Wide.Data + "/playground/" + strings.Replace(fileName, ".go", suffix, -1))
cmd := exec.Command("go", "build", "-o", executable, filePath)
out, err := cmd.CombinedOutput()
data["output"] = template.HTML(string(out))
if nil != err {
result.Code = -1
return
}
data["executable"] = executable
}

102
playground/file.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package playground
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
)
// SaveHandler handles request of Playground code save.
func SaveHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
code := args["code"].(string)
// Step1. format code
cmd := exec.Command("gofmt")
stdin, err := cmd.StdinPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
io.WriteString(stdin, code)
stdin.Close()
bytes, _ := cmd.Output()
output := string(bytes)
if "" != output {
code = string(output)
}
data := map[string]interface{}{}
result.Data = &data
data["code"] = code
// Step2. generate file name
hasher := md5.New()
hasher.Write([]byte(code))
fileName := hex.EncodeToString(hasher.Sum(nil))
fileName += ".go"
data["fileName"] = fileName
// Step3. write file
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
fout, err := os.Create(filePath)
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
return
}
}

123
playground/playgrounds.go Normal file
View File

@ -0,0 +1,123 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shell include playground related mainipulations.
package playground
import (
"html/template"
"io/ioutil"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/88250/wide/util"
"github.com/gorilla/websocket"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
// IndexHandler handles request of Playground index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
// create a HTTP session
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Values["uid"] = "playground"
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
locale := conf.Wide.Locale
// try to load file
code := conf.HelloWorld
fileName := "6c5595ec6fbadf4cfce3edbfcfd8c6d0.go" // MD5 of HelloWorld.go
if strings.HasSuffix(r.URL.Path, ".go") {
fileNameArg := r.URL.Path[len("/playground/"):]
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileNameArg)
bytes, err := ioutil.ReadFile(filePath)
if nil != err {
logger.Warn(err)
} else {
code = string(bytes)
fileName = fileNameArg
}
}
query := r.URL.Query()
embed := false
embedArg, ok := query["embed"]
if ok && "true" == embedArg[0] {
embed = true
}
disqus := false
disqusArg, ok := query["disqus"]
if ok && "true" == disqusArg[0] {
disqus = true
}
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"sid": session.WideSessions.GenId(), "pathSeparator": conf.PathSeparator,
"codeMirrorVer": conf.CodeMirrorVer,
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"embed": embed, "disqus": disqus, "fileName": fileName}
wideSessions := session.WideSessions.GetByUserId(uid)
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
t, err := template.ParseFiles("views/playground/index.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
t.Execute(w, model)
}
// WSHandler handles request of creating Playground channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0]
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Playground initialized", "cmd": "init-playground"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
session.PlaygroundWS[sid] = &wsChan
logger.Tracef("Open a new [PlaygroundWS] with session [%s], %d", sid, len(session.PlaygroundWS))
}

30
playground/run.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package playground
import (
"github.com/88250/wide/session"
"net/http"
)
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) {
session.RunHandler(w, r, session.PlaygroundWS)
}
// StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
session.StopHandler(w, r)
}

185
session/oauth.go Normal file
View File

@ -0,0 +1,185 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package session
import (
"fmt"
"html/template"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/util"
)
var states = map[string]string{}
// LoginRedirectHandler redirects to HacPai auth page.
func LoginRedirectHandler(w http.ResponseWriter, r *http.Request) {
loginAuthURL := conf.Wide.OAuthLoginURL + "?response_type=code&redirect_uri=" + conf.Wide.Server + "/login/callback"
// надо будет добавить ttlcache для state и проверять для предотвращения атак CSRF
state := gulu.Rand.String(16)
states[state] = state
path := loginAuthURL + "&state=" + state + "&client_id=" + conf.Wide.OAuthClientID
http.Redirect(w, r, path, http.StatusSeeOther)
}
func LoginCallbackHandler(w http.ResponseWriter, r *http.Request) {
state := r.URL.Query().Get("state")
if _, exist := states[state]; !exist {
http.Error(w, "Get state param failed", http.StatusBadRequest)
return
}
delete(states, state)
code := r.URL.Query().Get("code")
accessToken, err := util.GetOAuthToken(
conf.Wide.OAuthAccessTokenURL,
conf.Wide.OAuthClientID,
conf.Wide.OAuthClientSecret,
code,
conf.Wide.Server+`/login/callback`)
if err != nil {
http.Error(w, fmt.Sprintf(`get access_token failed. error: %s`, err.Error()), http.StatusBadRequest)
return
}
userInfo, err := util.OpenIdUserInfo(conf.Wide.OAuthUserInfoURL, accessToken)
if err != nil {
http.Error(w, fmt.Sprintf(`get user_info failed. error: %s`, err.Error()), http.StatusBadRequest)
return
}
userId := userInfo["userId"].(string)
userName := userInfo["userName"].(string)
avatar := userInfo["avatar"].(string)
user := conf.GetUser(userId)
if nil == user {
msg := addUser(userId, userName, avatar)
if userCreated != msg {
result := gulu.Ret.NewResult()
result.Code = -1
result.Msg = msg
gulu.Ret.RetResult(w, r, result)
return
}
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Values["uid"] = userId
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// LoginHandler handles request of show login page.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()}
t, err := template.ParseFiles("views/login.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
t.Execute(w, model)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// addUser add a user with the specified user id, username and avatar.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(userId, userName, userAvatar string) string {
if "playground" == userId {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Id) == strings.ToLower(userId) {
return userExists
}
}
workspace := filepath.Join(conf.Wide.Data, "workspaces", userId)
newUser := conf.NewUser(userId, userName, userAvatar, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(userId)
logger.Infof("Created a user [%s]", userId)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
func helloWorld(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}

313
session/processes.go Normal file
View File

@ -0,0 +1,313 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package session
import (
"bytes"
"encoding/json"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/util"
)
// Type of process set.
type procs map[string][]*os.Process
// Processse of all users.
//
// <sid, []*os.Process>
var Processes = procs{}
// Exclusive lock.
var procMutex sync.Mutex
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request, channel map[string]*util.WSChannel) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
}
sid := args["sid"].(string)
wSession := WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
}
filePath := args["executable"].(string)
randInt := rand.Int()
rid := strconv.Itoa(randInt)
var cmd *exec.Cmd
if conf.Docker {
fileName := filepath.Base(filePath)
cmd = exec.Command("docker", "run", "--rm", "--name", rid, "-v", filePath+":/"+fileName,
"--memory", "64M", "--cpus", "0.1",
conf.DockerImageGo, "/"+fileName)
} else {
cmd = exec.Command(filePath)
curDir := filepath.Dir(filePath)
cmd.Dir = curDir
}
outBuf := &bytes.Buffer{}
errBuf := &bytes.Buffer{}
cmd.Stdout = outBuf
cmd.Stderr = errBuf
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
}
wsChannel := channel[sid]
channelRet := map[string]interface{}{}
if 0 != result.Code {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
return
}
done := make(chan error)
go func() { done <- cmd.Wait() }()
channelRet["pid"] = cmd.Process.Pid
Processes.Add(wSession, cmd.Process)
shouldExitBuf := false
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
go func() {
defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [id=%s, file=%s]", wSession.UserId, sid, rid, filePath)
go func() {
defer gulu.Panic.Recover(nil)
for {
if shouldExitBuf {
break
}
if 1 > outBuf.Len() {
time.Sleep(7 * time.Millisecond)
continue
}
r, _, err := outBuf.ReadRune()
if nil != err {
time.Sleep(7 * time.Millisecond)
continue
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
channelRet["output"] = oneRuneStr
wsChannel := channel[sid]
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
}()
for {
if shouldExitBuf {
break
}
if 1 > errBuf.Len() {
time.Sleep(7 * time.Millisecond)
continue
}
r, _, err := errBuf.ReadRune()
if nil != err {
time.Sleep(7 * time.Millisecond)
continue
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + oneRuneStr + "</span>"
wsChannel := channel[sid]
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
}()
after := time.After(5 * time.Second)
kill := false
select {
case <-after:
if conf.Docker {
killCmd := exec.Command("docker", "rm", "-f", rid)
if err := killCmd.Run(); nil != err {
logger.Errorf("executes [docker rm -f " + rid + "] failed [" + err.Error() + "], this will cause resource leaking")
}
} else {
cmd.Process.Kill()
}
channelRet["output"] = "\n<span class='stderr'>run program timeout in 5s</span>\n"
kill = true
case <-done:
channelRet["output"] = "\n<span class='stderr'>run program complete</span>\n"
}
shouldExitBuf = true
Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] done running [id=%s, file=%s, kill=%v]", wSession.UserId, sid, rid, filePath, kill)
if nil != wsChannel {
channelRet["cmd"] = "run-done"
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
// StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
return
}
Processes.Kill(wSession, pid)
}
// Add adds the specified process to the user process set.
func (procs *procs) Add(wSession *WideSession, proc *os.Process) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses
// bind process with wide session
wSession.SetProcesses(userProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
}
// Remove removes the specified process from the user process set.
func (procs *procs) Remove(wSession *WideSession, proc *os.Process) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
var newProcesses []*os.Process
for i, p := range userProcesses {
if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
return
}
}
}
// Kill kills a process specified by the given pid.
func (procs *procs) Kill(wSession *WideSession, pid int) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
for i, p := range userProcesses {
if p.Pid == pid {
if err := p.Kill(); nil != err {
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.UserId, sid, err)
} else {
var newProcesses []*os.Process
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.UserId, sid)
}
return
}
}
}

View File

@ -1,127 +1,280 @@
// 会话操作. // Copyright (c) 2014-present, b3log.org
// //
// Wide 服务器端需要维护两种会话: // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// //
// 1. HTTP 会话:主要用于验证登录 // https://www.apache.org/licenses/LICENSE-2.0
// 2. Wide 会话:浏览器 tab 打开/刷新会创建一个,并和 HTTP 会话进行关联
// //
// 当会话失效时:释放所有和该会话相关的资源,例如运行中的程序进程、事件队列等. // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package session includes session related manipulations.
//
// Wide server side needs maintain two kinds of sessions:
//
// 1. HTTP session: mainly used for login authentication
// 2. Wide session: browser tab open/refresh will create one, and associates with HTTP session
//
// When a session gone: release all resources associated with it, such as running processes, event queues.
package session package session
import ( import (
"bytes"
"encoding/json" "encoding/json"
"math/rand" "math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath"
"sort"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/event" "github.com/88250/wide/conf"
"github.com/b3log/wide/util" "github.com/88250/wide/event"
"github.com/golang/glog" "github.com/88250/wide/util"
"github.com/fsnotify/fsnotify"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const ( const (
SessionStateActive = iota // 会话状态:活的 sessionStateActive = iota
SessionStateClosed // 会话状态:已关闭(这个状态目前暂时没有使用到) sessionStateClosed // (not used so far)
CookieName = "wide-sess"
) )
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var ( var (
// 会话通道. <sid, *util.WSChannel> // SessionWS holds all session channels. <sid, *util.WSChannel>
SessionWS = map[string]*util.WSChannel{} SessionWS = map[string]*util.WSChannel{}
// 输出通道. <sid, *util.WSChannel> // EditorWS holds all editor channels. <sid, *util.WSChannel>
EditorWS = map[string]*util.WSChannel{}
// OutputWS holds all output channels. <sid, *util.WSChannel>
OutputWS = map[string]*util.WSChannel{} OutputWS = map[string]*util.WSChannel{}
// 通知通道. <sid, *util.WSChannel> // NotificationWS holds all notification channels. <sid, *util.WSChannel>
NotificationWS = map[string]*util.WSChannel{} NotificationWS = map[string]*util.WSChannel{}
// PlaygroundWS holds all playground channels. <sid, *util.WSChannel>
PlaygroundWS = map[string]*util.WSChannel{}
) )
// 用户 HTTP 会话,用于验证登录. // HTTP session store.
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND")) var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// Wide 会话,对应一个浏览器 tab. // WideSession represents a session associated with a browser tab.
type WideSession struct { type WideSession struct {
Id string // 唯一标识 ID string // id
Username string // 用户名 UserId string // user id
HTTPSession *sessions.Session // 关联的 HTTP 会话 HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // 关联的进程集 Processes []*os.Process // process set
EventQueue *event.UserEventQueue // 关联的事件队列 EventQueue *event.UserEventQueue // event queue
State int // 状态 State int // state
Content *conf.LatestSessionContent // 最近一次会话内容 Content *conf.LatestSessionContent // the latest session content
Created time.Time // 创建时间 FileWatcher *fsnotify.Watcher // files change watcher
Updated time.Time // 最近一次使用时间 Created time.Time // create time
Updated time.Time // the latest use time
} }
// 会话集类型. // Type of wide sessions.
type Sessions []*WideSession type wSessions []*WideSession
// 所有 Wide 会话集. // Wide sessions.
var WideSessions Sessions var WideSessions wSessions
// 排它锁,防止并发修改. // Exclusive lock.
var mutex sync.Mutex var mutex sync.Mutex
// 在一些特殊情况(例如浏览器不间断刷新/在源代码视图刷新)下 Wide 会话集内会出现无效会话该函数定时1 小时)检查并移除这些无效会话. // FixedTimeRelease releases invalid sessions.
// //
// 无效会话:在检查时间内 30 分钟都没有使用过的会话,参考 WideSession.Updated 字段. // In some special cases (such as a browser uninterrupted refresh / refresh in the source code view) will occur
// some invalid sessions, the function checks and removes these invalid sessions periodically (1 hour).
//
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
func FixedTimeRelease() { func FixedTimeRelease() {
go func() { go func() {
// 1 小时进行一次检查 defer gulu.Panic.Recover(nil)
for _ = range time.Tick(time.Hour) { for _ = range time.Tick(time.Hour) {
hour, _ := time.ParseDuration("-30m") hour, _ := time.ParseDuration("-30m")
threshold := time.Now().Add(hour) threshold := time.Now().Add(hour)
for _, s := range WideSessions { for _, s := range WideSessions {
if s.Updated.Before(threshold) { if s.Updated.Before(threshold) {
glog.V(3).Infof("Removes a invalid session [%s]", s.Id) logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.UserId)
WideSessions.Remove(s.Id) WideSessions.Remove(s.ID)
} }
} }
} }
}() }()
} }
// 建立会话通道. 通道断开时销毁会话状态,回收相关资源. // Online user statistic report.
type userReport struct {
userId string
sessionCnt int
processCnt int
updated time.Time
}
// report returns a online user statistics in pretty format.
func (u *userReport) report() string {
return "[" + u.userId + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
}
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
func FixedTimeReport() {
go func() {
defer gulu.Panic.Recover(nil)
for _ = range time.Tick(10 * time.Minute) {
users := userReports{}
processSum := 0
for _, s := range WideSessions {
processCnt := len(s.Processes)
processSum += processCnt
if report, exists := contains(users, s.UserId); exists {
if s.Updated.After(report.updated) {
report.updated = s.Updated
}
report.sessionCnt++
report.processCnt += processCnt
} else {
users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
}
}
var buf bytes.Buffer
buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users, [" + strconv.Itoa(processSum) + "] running processes and [" +
strconv.Itoa(len(WideSessions)) + "] sessions currently\n")
sort.Sort(users)
for _, t := range users {
buf.WriteString(" " + t.report() + "\n")
}
logger.Info(buf.String())
}
}()
}
func contains(reports []*userReport, userId string) (*userReport, bool) {
for _, ur := range reports {
if userId == ur.userId {
return ur, true
}
}
return nil, false
}
type userReports []*userReport
func (f userReports) Len() int { return len(f) }
func (f userReports) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].processCnt }
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
)
// WSHandler handles request of creating session channel.
//
// When a channel closed, releases all resources associated with it.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := WideSessions.Get(sid)
if nil == wSession {
glog.Errorf("Session [%s] not found", sid)
return
}
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Session initialized", "cmd": "init-session"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
SessionWS[sid] = &wsChan SessionWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-session"} wSession := WideSessions.Get(sid)
wsChan.Conn.WriteJSON(&ret) if nil == wSession {
httpSession, _ := HTTPSession.Get(r, CookieName)
glog.V(4).Infof("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS)) if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.UserId)
}
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
input := map[string]interface{}{} input := map[string]interface{}{}
for { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait))
if err := wsChan.Conn.ReadJSON(&input); err != nil { wsChan.Conn.SetPongHandler(func(string) error { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid) ticker := time.NewTicker(pingPeriod)
defer func() {
WideSessions.Remove(sid) WideSessions.Remove(sid)
ticker.Stop()
wsChan.Close()
}()
// send websocket ping message.
go func(t *time.Ticker, channel util.WSChannel) {
for {
select {
case <-t.C:
if err := channel.Conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}(ticker, wsChan)
for {
if err := wsChan.ReadJSON(&input); err != nil {
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.UserId)
return return
} }
ret = map[string]interface{}{"output": "", "cmd": "session-output"} ret = map[string]interface{}{"output": "", "cmd": "session-output"}
if err := wsChan.Conn.WriteJSON(&ret); err != nil { if err := wsChan.WriteJSON(&ret); err != nil {
glog.Error("Session WS ERROR: " + err.Error()) logger.Error("Session WS ERROR: " + err.Error())
return return
} }
@ -129,10 +282,10 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// 会话内容保存. // SaveContentHandler handles request of session content string.
func SaveContent(w http.ResponseWriter, r *http.Request) { func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} result := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, result)
args := struct { args := struct {
Sid string Sid string
@ -140,26 +293,28 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
}{} }{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
wSession := WideSessions.Get(args.Sid) wSession := WideSessions.Get(args.Sid)
if nil == wSession { if nil == wSession {
data["succ"] = false result.Code = -1
return return
} }
wSession.Content = args.LatestSessionContent wSession.Content = args.LatestSessionContent
for _, user := range conf.Wide.Users { for _, user := range conf.Users {
if user.Name == wSession.Username { if user.Id == wSession.UserId {
// 更新配置内存变量conf.FixedTimeSave() 会负责定时持久化 // update the variable in-memory, session.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content user.LatestSessionContent = wSession.Content
user.Lived = time.Now().UnixNano()
wSession.Refresh() wSession.Refresh()
return return
@ -167,54 +322,32 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
} }
} }
// 设置会话关联的进程集. // SetProcesses binds process set with the wide session.
func (s *WideSession) SetProcesses(ps []*os.Process) { func (s *WideSession) SetProcesses(ps []*os.Process) {
s.Processes = ps s.Processes = ps
s.Refresh() s.Refresh()
} }
// 刷新会话最近一次使用时间. // Refresh refreshes the channel by updating its use time.
func (s *WideSession) Refresh() { func (s *WideSession) Refresh() {
s.Updated = time.Now() s.Updated = time.Now()
} }
// 创建一个 Wide 会话. // GenId generates a wide session id.
func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession { func (sessions *wSessions) GenId() string {
mutex.Lock()
defer mutex.Unlock()
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
id := strconv.Itoa(rand.Int()) return strconv.Itoa(rand.Int())
now := time.Now()
// 创建用户事件队列
userEventQueue := event.UserEventQueues.New(id)
ret := &WideSession{
Id: id,
Username: httpSession.Values["username"].(string),
HTTPSession: httpSession,
EventQueue: userEventQueue,
State: SessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
return ret
} }
// 获取 Wide 会话. // Get gets a wide session with the specified session id.
func (sessions *Sessions) Get(sid string) *WideSession { func (sessions *wSessions) Get(sid string) *WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
for _, s := range *sessions { for _, s := range *sessions {
if s.Id == sid { if s.ID == sid {
return s return s
} }
} }
@ -222,35 +355,36 @@ func (sessions *Sessions) Get(sid string) *WideSession {
return nil return nil
} }
// 移除 Wide 会话,释放相关资源. // Remove removes a wide session specified with the given session id, releases resources associated with it.
// //
// 会话相关资源: // Session-related resources:
// //
// 1. 用户事件队列 // 1. user event queue
// 2. 运行中的进程集 // 2. process set
// 3. WebSocket 通道 // 3. websocket channels
func (sessions *Sessions) Remove(sid string) { // 4. file watcher
func (sessions *wSessions) Remove(sid string) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
for i, s := range *sessions { for i, s := range *sessions {
if s.Id == sid { if s.ID == sid {
// 从会话集中移除 // remove from session set
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...) *sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
// 关闭用户事件队列 // close user event queue
event.UserEventQueues.Close(sid) event.UserEventQueues.Close(sid)
// 杀进程 // kill processes
for _, p := range s.Processes { for _, p := range s.Processes {
if err := p.Kill(); nil != err { if err := p.Kill(); nil != err {
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid) logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} else { } else {
glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid) logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} }
} }
// 回收所有通道 // close websocket channels
if ws, ok := OutputWS[sid]; ok { if ws, ok := OutputWS[sid]; ok {
ws.Close() ws.Close()
delete(OutputWS, sid) delete(OutputWS, sid)
@ -266,33 +400,155 @@ func (sessions *Sessions) Remove(sid string) {
delete(SessionWS, sid) delete(SessionWS, sid)
} }
glog.V(3).Infof("Removed a session [%s]", s.Id) if ws, ok := PlaygroundWS[sid]; ok {
ws.Close()
delete(PlaygroundWS, sid)
}
cnt := 0 // 统计当前 HTTP 会话关联的 Wide 会话数量 // file watcher
for _, s := range *sessions { if nil != s.FileWatcher {
if s.HTTPSession.ID == s.HTTPSession.ID { s.FileWatcher.Close()
}
cnt := 0 // count wide sessions associated with HTTP session
for _, ses := range *sessions {
if ses.UserId == s.UserId {
cnt++ cnt++
} }
} }
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt)
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
return return
} }
} }
} }
// 获取 username 指定的用户的所有 Wide 会话. // GetByUsername gets wide sessions.
func (sessions *Sessions) GetByUsername(username string) []*WideSession { func (sessions *wSessions) GetByUserId(userId string) []*WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
ret := []*WideSession{} ret := []*WideSession{}
for _, s := range *sessions { for _, s := range *sessions {
if s.Username == username { if s.UserId == userId {
ret = append(ret, s) ret = append(ret, s)
} }
} }
return ret return ret
} }
// new creates a wide session.
func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
uid := httpSession.Values["uid"].(string)
now := time.Now()
ret := &WideSession{
ID: sid,
UserId: uid,
HTTPSession: httpSession,
EventQueue: nil,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
if "playground" == uid {
return ret
}
// create user event queue
ret.EventQueue = event.UserEventQueues.New(sid)
// add a filesystem watcher to notify front-end after the files changed
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Error(err)
return ret
}
go func() {
defer gulu.Panic.Recover(nil)
for {
ch := SessionWS[sid]
if nil == ch {
return // release this goroutine
}
select {
case event := <-watcher.Events:
path := filepath.ToSlash(event.Name)
dir := filepath.ToSlash(filepath.Dir(path))
ch = SessionWS[sid]
if nil == ch {
return // release this goroutine
}
logger.Trace(event)
if event.Op&fsnotify.Create == fsnotify.Create {
fileType := "f"
if gulu.File.IsDir(path) {
fileType = "d"
if err = watcher.Add(path); nil != err {
logger.Warn(err, path)
}
}
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
ch.WriteJSON(&cmd)
}
case err := <-watcher.Errors:
if nil != err {
logger.Error("File watcher ERROR: ", err)
}
}
}
}()
go func() {
defer gulu.Panic.Recover(nil)
workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
return filepath.SkipDir
}
if f.IsDir() {
if err = watcher.Add(dirPath); nil != err {
logger.Error(err, dirPath)
}
logger.Tracef("File watcher added a dir [%s]", dirPath)
}
return nil
})
}
ret.FileWatcher = watcher
}()
return ret
}

View File

@ -1,83 +1,220 @@
package session // Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO: 这个文件内的功能目前没有使用,只是开了个头 :p package session
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"path/filepath"
"runtime"
"strings"
"sync"
"text/template"
"time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/util" "github.com/88250/wide/conf"
"github.com/golang/glog" "github.com/88250/wide/i18n"
) )
const ( const (
USER_EXISTS = "user exists" // TODO: i18n
USER_CREATED = "user created"
USER_CREATE_FAILED = "user create failed" userExists = "user exists"
userCreated = "user created"
userCreateError = "user create error"
) )
// 添加用户. // Exclusive lock for adding user.
func AddUser(w http.ResponseWriter, r *http.Request) { var addUserMutex sync.Mutex
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
decoder := json.NewDecoder(r.Body) // PreferenceHandler handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, CookieName)
var args map[string]interface{} if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
if err := decoder.Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return return
} }
username := args["username"].(string) httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
password := args["password"].(string) httpSession.Save(r, w)
msg := addUser(username, password) uid := httpSession.Values["uid"].(string)
if USER_CREATED != msg { user := conf.GetUser(uid)
data["succ"] = false
data["msg"] = msg if "GET" == r.Method {
tmpLinux := user.GoBuildArgsForLinux
tmpWindows := user.GoBuildArgsForWindows
tmpDarwin := user.GoBuildArgsForDarwin
user.GoBuildArgsForLinux = strings.Replace(user.GoBuildArgsForLinux, `"`, `&quot;`, -1)
user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `&quot;`, -1)
user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `&quot;`, -1)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
"locales": i18n.GetLocalesNames(), "gofmts": gulu.Go.GetGoFormats(),
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
t, err := template.ParseFiles("views/preference.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return
}
t.Execute(w, model)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return
}
// non-GET request as save request
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
args := struct {
FontFamily string
FontSize string
GoFmt string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
Keymap string
Workspace string
Username string
Locale string
Theme string
EditorFontFamily string
EditorFontSize string
EditorLineHeight string
EditorTheme string
EditorTabSize string
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
user.FontFamily = args.FontFamily
user.FontSize = args.FontSize
user.GoFormat = args.GoFmt
user.GoBuildArgsForLinux = args.GoBuildArgsForLinux
user.GoBuildArgsForWindows = args.GoBuildArgsForWindows
user.GoBuildArgsForDarwin = args.GoBuildArgsForDarwin
user.Keymap = args.Keymap
// XXX: disallow change workspace at present
// user.Workspace = args.Workspace
user.Locale = args.Locale
user.Theme = args.Theme
user.Editor.FontFamily = args.EditorFontFamily
user.Editor.FontSize = args.EditorFontSize
user.Editor.LineHeight = args.EditorLineHeight
user.Editor.Theme = args.EditorTheme
user.Editor.TabSize = args.EditorTabSize
conf.UpdateCustomizedConf(uid)
now := time.Now().UnixNano()
user.Lived = now
user.Updated = now
if user.Save() {
result.Code = 0
} else {
result.Code = -1
} }
} }
// 初始化用户 git 仓库. // FixedTimeSave saves online users' configurations periodically (1 minute).
func InitGitRepos(w http.ResponseWriter, r *http.Request) { //
data := map[string]interface{}{"succ": true} // Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
defer util.RetJSON(w, r, data) func FixedTimeSave() {
go func() {
defer gulu.Panic.Recover(nil)
session, _ := HTTPSession.Get(r, "wide-session") for _ = range time.Tick(time.Minute) {
SaveOnlineUsers()
username := session.Values["username"].(string) }
userRepos := conf.Wide.GetUserWorkspace(username) + conf.PathSeparator + "src" }()
glog.Info(userRepos)
// TODO: git clone
} }
func addUser(username, password string) string { // CanAccess determines whether the user specified by the given user id can access the specified path.
// TODO: https://github.com/b3log/wide/issues/23 func CanAccess(userId, path string) bool {
conf.Load() path = filepath.FromSlash(path)
// XXX: 新建用户校验增强 userWorkspace := conf.GetUserWorkspace(userId)
for _, user := range conf.Wide.Users { workspaces := filepath.SplitList(userWorkspace)
if user.Name == username {
return USER_EXISTS for _, workspace := range workspaces {
if strings.HasPrefix(path, workspace) {
return true
} }
} }
// FIXME: 新建用户时保存工作空间 return false
newUser := &conf.User{Name: username, Password: password, Workspace: ""} }
conf.Wide.Users = append(conf.Wide.Users, newUser)
// SaveOnlineUsers saves online users' configurations at once.
if !conf.Save() { func SaveOnlineUsers() {
return USER_CREATE_FAILED users := getOnlineUsers()
} for _, u := range users {
if u.Save() {
glog.Infof("Created a user [%s]", username) logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
return USER_CREATED }
}
func getOnlineUsers() []*conf.User {
ret := []*conf.User{}
uids := map[string]string{} // distinct uid
for _, s := range WideSessions {
uids[s.UserId] = s.UserId
}
for _, uid := range uids {
u := conf.GetUser(uid)
if "playground" == uid { // user [playground] is a reserved mock user
continue
}
if nil == u {
logger.Warnf("Not found user [%s]", uid)
continue
}
ret = append(ret, u)
}
return ret
} }

View File

@ -1,176 +0,0 @@
// Shell.
package shell
import (
"html/template"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/golang/glog"
"github.com/gorilla/websocket"
)
// Shell 通道.
//
// <sid, *util.WSChannel>>
var ShellWS = map[string]*util.WSChannel{}
// Shell 首页.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
i18n.Load()
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
// 创建一个 Wide 会话
wideSession := session.WideSessions.New(httpSession)
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession}
wideSessions := session.WideSessions.GetByUsername(username)
glog.V(3).Infof("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/shell.html")
if nil != err {
glog.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
}
// 建立 Shell 通道.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
username := httpSession.Values["username"].(string)
sid := r.URL.Query()["sid"][0]
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ShellWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
wsChan.Conn.WriteJSON(&ret)
glog.V(4).Infof("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
input := map[string]interface{}{}
for {
if err := wsChan.Conn.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Shell WS ERROR: " + err.Error())
return
}
inputCmd := input["cmd"].(string)
cmds := strings.Split(inputCmd, "|")
commands := []*exec.Cmd{}
for _, cmdWithArgs := range cmds {
cmdWithArgs = strings.TrimSpace(cmdWithArgs)
cmdWithArgs := strings.Split(cmdWithArgs, " ")
args := []string{}
if len(cmdWithArgs) > 1 {
args = cmdWithArgs[1:]
}
cmd := exec.Command(cmdWithArgs[0], args...)
commands = append(commands, cmd)
}
output := ""
if !strings.Contains(inputCmd, "clear") {
output = pipeCommands(username, commands...)
}
ret = map[string]interface{}{"output": output, "cmd": "shell-output"}
if err := wsChan.Conn.WriteJSON(&ret); err != nil {
glog.Error("Shell WS ERROR: " + err.Error())
return
}
// 更新通道最近使用时间
wsChan.Time = time.Now()
}
}
// 以管道方式执行多个命令.
func pipeCommands(username string, commands ...*exec.Cmd) string {
for i, command := range commands[:len(commands)-1] {
setCmdEnv(command, username)
stdout, err := command.StdoutPipe()
if nil != err {
return err.Error()
}
command.Start()
commands[i+1].Stdin = stdout
}
last := commands[len(commands)-1]
setCmdEnv(last, username)
out, err := last.CombinedOutput()
// 结束进程,释放资源
for _, command := range commands[:len(commands)-1] {
command.Wait()
}
if err != nil {
return err.Error()
}
return string(out)
}
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.Wide.GetUserWorkspace(username)
cmd.Env = append(cmd.Env,
"TERM="+os.Getenv("TERM"),
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(),
"PATH="+os.Getenv("PATH"))
cmd.Dir = userWorkspace
}

View File

@ -1,12 +1,32 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#dialogAboutDialog .dialog-main { #dialogAboutDialog .dialog-main {
background-color: #FFF; background-color: #FFF;
} }
#dialogAbout { #dialogAbout {
margin: 35px 100px 0 100px; margin: 35px 20px;
line-height: 28px; line-height: 28px;
} }
#dialogAbout .item {
margin: 0 10px;
}
#dialogAbout a { #dialogAbout a {
color: #4183c4; color: #4183c4;
text-decoration: none; text-decoration: none;
@ -28,23 +48,17 @@
#dialogAbout .space { #dialogAbout .space {
margin-bottom: 6px; margin-bottom: 6px;
border-bottom: 1px solid #f1f1f1; border-bottom: 1px solid #919191;
padding-bottom: 6px; padding-bottom: 6px;
} }
#dialogAbout .thx {
border-left: 1px solid #f1f1f1;
padding-left: 24px;
}
#dialogAbout .thx,
#dialogAbout .thx ul { #dialogAbout .thx ul {
margin-left: 50px; margin-left: 50px;
} }
#dialogAbout .thx a { #dialogAbout .thx a {
width: 100px; width: 80px;
display: inline-block; display: inline-block;
} }
@ -53,5 +67,6 @@
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
height: 85px; height: 85px;
overflow: auto; overflow-x: hidden;
word-wrap: break-word;
} }

View File

@ -1,10 +1,56 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* themes for base.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
*/
/* start reset & function */ /* start reset & function */
::-webkit-scrollbar {
background: none;
width: 16px;
height: 16px;
}
::-webkit-scrollbar-corner {
display: none;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
border: solid 0 rgba(0, 0, 0, 0);
border-right-width: 4px;
border-left-width: 4px;
border-radius: 9px;
box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.2), inset 0 0 0 4px rgba(128, 128, 128, 0.2);
}
::-webkit-scrollbar-thumb:horizontal {
border-bottom-width: 4px;
border-top-width: 4px;
}
body { body {
font-size: 13px; font-size: 13px;
margin: 0; margin: 0;
color: #000; color: #000;
overflow: hidden; overflow: hidden;
font-family: Helvetica, 'Microsoft Yahei'; font-family: Helvetica;
} }
ul { ul {
@ -17,9 +63,22 @@ ul {
box-sizing: border-box; box-sizing: border-box;
} }
a {
color: #4183c4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
vertical-align: middle;
}
input, input,
button { button {
font-family: Helvetica, 'Microsoft Yahei'; font-family: Helvetica;
} }
.fn-left { .fn-left {
@ -44,3 +103,203 @@ button {
display: none; display: none;
} }
/* end reset & function */ /* end reset & function */
/* start common */
.ft-small {
color: #999;
font-size: 12px;
}
.ft-red {
color: #9d0000;
}
.list li {
cursor: pointer;
line-height: 20px;
padding: 0 3px;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.list li.selected,
.list li:hover {
background-color: #3875d7;
color: #FFF;
}
.list li.selected .ft-small,
.list li:hover .ft-small {
color: #FFF;
}
/* end common */
/* start icon */
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?lqk80d');
src: url('fonts/icomoon.eot?lqk80d#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?lqk80d') format('truetype'),
url('fonts/icomoon.woff?lqk80d') format('woff'),
url('fonts/icomoon.svg?lqk80d#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="ico-"], [class*=" ico-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
font-size: 13px;
line-height: 20px;
}
.ico-qqz:before {
content: "\e900";
}
.ico-find:before {
content: "\e602";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-editor:before {
content: "\e604";
}
.ico-notification:before {
content: "\e607";
}
.ico-price:before {
content: "\e616";
}
.ico-report:before {
content: "\e605";
}
.ico-git:before {
content: "\e624";
}
.ico-book:before {
content: "\e623";
}
.ico-start:before {
content: "\e9d7";
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
}
.ico-tree:before {
content: "\e600";
}
.ico-build:before {
content: "\e601";
}
.ico-export:before {
content: "\f0ed";
}
.ico-import:before {
content: "\f0ee";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
}
.ico-weibo:before {
content: "\e621";
}
.ico-uniE608:before {
content: "\e608";
}
.ico-max:before {
content: "\e609";
}
.ico-remove:before {
content: "\e60b";
}
.ico-buildrun:before {
content: "\e60c";
}
.ico-about:before {
content: "\e60d";
}
.ico-undo:before {
content: "\e60e";
}
.ico-stop:before {
content: "\e60f";
}
.ico-close:before {
content: "\e611";
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
}
.ico-format:before {
content: "\e612";
}
.ico-restore:before {
content: "\e613";
}
.toolbars .ico-restore:before {
content: "\e60a";
}
.ico-min:before {
content: "\e614";
position: absolute;
right: 5px;
}
.ico-redo:before {
content: "\e615";
}
.ico-uniE617:before {
content: "\e617";
}
.ico-signout:before {
content: "\e618";
}
.ico-email:before {
content: "\e619";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-info:before {
content: "\e61d";
}
.ico-goline:before {
content: "\e61e";
}
.ico-share:before {
content: "\e61f";
}
.ico-comment:before {
content: "\e620";
}
.ico-github:before {
content: "\f00a";
}
.ico-refresh:before {
content: "\f021";
}
.ico-save:before {
content: "\f0c7";
}
/* end ico */

View File

@ -1,7 +1,23 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** /**
* dialig style * dialig style
* *
* @author <a href="mailto:LLY219@gmail.com">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.0.0.6, Jun 3, 2012 * @version 0.0.0.6, Jun 3, 2012
*/ */
@ -24,23 +40,21 @@
display: none; display: none;
-moz-user-select: none; -moz-user-select: none;
user-select: none; user-select: none;
box-shadow: 0 2px 10px 1px #000000;
} }
.dialog-title { .dialog-title {
float: left; float: left;
line-height: 22px; line-height: 22px;
margin-left: 3px; margin-left: 3px;
color: #000; font-weight: bold;
} }
.dialog-header-bg { .dialog-header-bg {
height: 23px; height: 23px;
background-color: #CCD5E5; background-color: #bbb;
cursor: move; cursor: move;
width: 100%; width: 100%;
border: 1px solid #9B9B9B;
border-top-color: #8E97A7;
border-bottom-color: #8891A1;
} }
.dialog-close-icon { .dialog-close-icon {
@ -49,10 +63,8 @@
text-decoration: none; text-decoration: none;
} }
.dialog-main { .dialog-close-icon:hover {
border: 1px solid #9B9B9B; text-decoration: none;
border-top-width: 0px;
background-color: #F0F0F0;
} }
.dialog-main > div { .dialog-main > div {
@ -64,7 +76,8 @@
text-align: right; text-align: right;
} }
.dialog-footer button { .dialog-footer button,
#dialogCloseEditor button {
margin: 0 5px; margin: 0 5px;
} }
@ -76,7 +89,34 @@
overflow: hidden; overflow: hidden;
} }
.dialog-main input { .dialog-main input,
.dialog-main select {
width: 100%; width: 100%;
margin: 2px auto; margin: 2px auto;
} }
#dialogGoFilePrompt > ul {
position: relative;
height: 260px;
overflow: auto;
margin-top: 5px;
background-color: #FFF;
border: 1px solid #919191;
}
#dialogPreference {
margin: 10px;
}
#dialogPreference .tabs-panel {
padding: 10px;
}
#dialogPreference .preference {
margin-bottom: 10px;
}
#dialogPreference img.gravatar {
width: 48px;
height: 48px;
}

BIN
static/css/fonts/icomoon.eot Normal file → Executable file

Binary file not shown.

72
static/css/fonts/icomoon.svg Normal file → Executable file
View File

@ -3,27 +3,53 @@
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata> <metadata>Generated by IcoMoon</metadata>
<defs> <defs>
<font id="icomoon" horiz-adv-x="512"> <font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="512" ascent="480" descent="-32" /> <font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="512" /> <missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="256" /> <glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe600;" d="M219.464 448.036q7.428 0 12.857-5.428t5.429-12.857v-64h84l49.428 49.428q5.428 5.428 12.857 5.428t12.857-5.428 5.428-12.857-5.428-12.857l-49.428-49.428v-241.143l49.428-49.428q5.428-5.428 5.428-12.857t-5.428-12.857-12.857-5.428-12.857 5.428l-49.428 49.428h-84v-64q0-7.428-5.428-12.857t-12.857-5.428-12.857 5.428-5.428 12.857v64q-48.857 0-82.857 19.143l-59.715-59.428q-5.428-5.428-12.857-5.428t-12.857 5.428q-5.428 5.143-5.428 12.857t5.428 12.857l56.285 56.572q-1.428 1.428-3.715 4.286t-8.143 12-10.428 18.572-8.285 23.428-3.715 27.714h256v36.572h-256q0 14.572 3.857 29t9.428 24.857 11.143 18.857 9.285 12.428l4 4.286-59.143 52.285q-6 5.715-6 13.715 0 6.857 4.572 12.285 5.143 5.429 12.715 5.857t13.285-4.428l64.857-57.714q32.572 16.571 78.285 16.571v64q0 7.429 5.429 12.857t12.857 5.428zM384.036 310.893q38 0 64.714-26.714t26.714-64.714-26.714-64.714-64.714-26.714v182.857z" horiz-adv-x="476" /> <glyph unicode="&#xe600;" glyph-name="tree" horiz-adv-x="1170" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" />
<glyph unicode="&#xe601;" d="M512 480v-192l-69.13 69.13-106-106-53.74 53.74 106 106-69.13 69.13zM122.87 410.87l106-106-53.74-53.74-106 106-69.13-69.13v192h192zM442.87 90.87l69.13 69.13v-192h-192l69.13 69.13-106 106 53.74 53.74zM228.87 143.13l-106-106 69.13-69.13h-192v192l69.13-69.13 106 106z" /> <glyph unicode="&#xe601;" glyph-name="build" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" />
<glyph unicode="&#xe602;" d="M32 192h192v-192l-69.13 69.13-101-101-53.74 53.74 101 101zM410.87 122.87l101-101-53.74-53.74-101 101-69.13-69.13v192h192zM480 256h-192v192l69.13-69.13 101 101 53.74-53.74-101-101zM154.87 378.87l69.13 69.13v-192h-192l69.13 69.13-101 101 53.74 53.74z" /> <glyph unicode="&#xe602;" glyph-name="find" d="M632.576 661.376c46.080-10.666 82.752-49.92 93.418-92.8 3.648-20.054 21.12-35.2 42.048-35.2 23.466 0 42.666 19.2 42.666 42.666 0 30.72-25.814 77.44-61.014 112.426-34.56 34.134-75.734 58.24-109.654 58.24-23.466 0-42.666-19.84-42.666-43.306 0.002-20.884 15.148-38.4 35.202-42.026zM46.122 102.016c-33.066-33.046-33.066-86.826 0-119.872 33.066-33.088 86.826-33.088 119.68 0l245.098 244.906c57.194-35.67 124.586-56.342 197.12-56.342 206.294 0 373.334 167.040 373.334 373.334s-167.040 373.334-373.334 373.334-373.334-167.040-373.334-373.334c0-72.32 20.714-139.946 56.342-197.12l-244.906-244.906zM341.376 544.042c0 147.2 119.488 266.666 266.666 266.666 147.2 0 266.666-119.466 266.666-266.666s-119.466-266.666-266.666-266.666c-147.178 0.022-266.666 119.488-266.666 266.666z" />
<glyph unicode="&#xe603;" d="M504.998 65.512l-150.772 150.772c-9.334 9.334-24.607 9.334-33.941 0l-11.313-11.313-92 92 151.028 151.029h-160l-71.029-71.029-7.030 7.029h-33.941v-33.941l7.029-7.029-103.029-103.030 80-80 103.029 103.029 92-92-11.313-11.313c-9.334-9.334-9.334-24.607 0-33.941l150.772-150.772c9.334-9.334 24.607-9.334 33.941 0l56.568 56.568c9.335 9.333 9.335 24.607 0.001 33.941z" /> <glyph unicode="&#xe603;" glyph-name="findfiles" d="M0 21.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 341.332h128v-341.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 554.668c0 47.146-38.186 85.334-85.334 85.334h-42.666v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-256v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-42.666c-47.146 0-85.334-38.186-85.334-85.334v-554.666zM554.666 469.334h-85.334c-23.466 0-42.666 19.2-42.666 42.666s19.2 42.666 42.666 42.666h85.334c23.466 0 42.666-19.2 42.666-42.666s-19.198-42.666-42.666-42.666z" />
<glyph unicode="&#xe604;" d="M400 416h-288c-26.51 0-48-21.49-48-48v-16h384v16c0 26.51-21.49 48-48 48zM316.16 448l7.058-50.5h-134.436l7.057 50.5h120.321zM320 480h-128c-13.2 0-25.495-10.696-27.321-23.769l-9.357-66.962c-1.827-13.073 7.478-23.769 20.678-23.769h160c13.2 0 22.505 10.696 20.679 23.769l-9.357 66.962c-1.827 13.073-14.122 23.769-27.322 23.769v0zM408 320h-304c-17.6 0-30.696-14.341-29.103-31.869l26.206-288.263c1.593-17.527 17.297-31.868 34.897-31.868h240c17.6 0 33.304 14.341 34.897 31.868l26.205 288.263c1.594 17.528-11.502 31.869-29.102 31.869zM192 32h-48l-16 224h64v-224zM288 32h-64v224h64v-224zM368 32h-48v224h64l-16-224z" /> <glyph unicode="&#xe604;" glyph-name="editor" d="M118.634 528.918c-20.886-20.886-30.934-50.774-36.032-81.066l-81.322-384.428c-11.52-68.288 56.96-136.982 126.102-126.314l384.874 80.234c28.16 5.098 58.666 13.866 79.574 34.774l407.68 408.128c32.64 32.832 32.64 85.952 0 118.806l-356.054 356.48c-32.854 32.64-85.974 32.64-118.634 0l-406.188-406.614zM509.888 118.678l-199.254-38.208-165.12 165.334 37.952 199.488 296.768 297.174c16.426 16.448 42.88 16.448 59.306 0 16.448-16.426 16.448-43.094 0-59.52l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.218 20.48-20.502 53.546-20.502 74.026 0l274.582 274.774c16.448 16.426 42.88 16.426 59.306 0 16.448-16.448 16.448-42.902 0-59.306l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.24 20.48-20.48 53.568-20.48 74.026 0l274.582 274.774c16.234 16.234 42.902 16.234 59.328 0 16.426-16.426 16.426-43.094 0-59.498l-296.766-297.006z" />
<glyph unicode="&#xe605;" d="M96 416l320-192-320-192z" /> <glyph unicode="&#xe605;" glyph-name="report" d="M622.858 799.808l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" />
<glyph unicode="&#xe606;" d="M360.704 429.209c-29.209 21.069-65.843 31.591-109.978 31.591-33.587 0-61.901-7.424-84.915-22.221-36.531-23.194-55.936-62.566-58.291-118.118h84.633c0 16.179 4.71 31.769 14.157 46.771s25.472 22.502 48.077 22.502c22.963 0 38.81-6.093 47.462-18.253 8.678-12.211 13.005-25.702 13.005-40.499 0-12.877-6.451-24.653-14.233-35.379-4.275-6.247-9.933-11.981-16.921-17.255 0 0-45.901-29.44-66.073-53.095-11.699-13.722-12.749-34.253-13.773-63.719-0.077-2.099 0.717-6.425 8.064-6.425s59.315 0 65.843 0 7.885 4.839 7.987 6.963c0.461 10.726 1.664 16.205 3.635 22.4 3.712 11.699 13.747 21.913 25.063 30.695l23.296 16.077c21.017 16.384 37.811 29.824 45.209 40.371 12.647 17.357 21.529 38.707 21.529 64.025 0 41.344-14.618 72.525-43.776 93.568zM249.369 104.345c-29.184 0.87-53.248-19.303-54.169-50.944-0.922-31.616 21.965-52.505 51.149-53.376 30.464-0.896 53.888 18.637 54.81 50.253 0.896 31.642-21.325 53.171-51.789 54.067z" /> <glyph unicode="&#xe606;" glyph-name="signup" d="M697.83 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 401.542c-108.479 0-325.203-54.355-325.203-162.601v-116.144h650.404v116.144c0 108.245-216.724 162.601-325.203 162.601zM697.83 401.542c-13.472 0-28.571-0.929-44.831-2.555 53.89-38.792 91.289-91.057 91.289-160.047v-116.144h278.744v116.144c0 108.245-216.724 162.601-325.203 162.601z" />
<glyph unicode="&#xe607;" d="M80.231 302.016c23.91 18.56 43.725 5.786 70.169-24.832 2.995-3.43 6.989 0.589 9.242 2.56 2.253 1.997 37.171 33.382 38.861 34.841 1.715 1.51 3.763 4.327 1.049 7.475s-12.647 16-19.021 24.32c-46.285 60.544 126.618 101.607 100.045 102.247-13.491 0.358-67.738 0.973-75.827 0.128-32.845-3.481-74.087-34.176-94.848-48.461-27.136-18.662-37.299-29.491-38.963-31.001-7.68-6.733-1.229-22.195-15.155-34.406-14.72-12.902-23.885-3.149-32.41-10.624-4.25-3.738-16.051-12.57-19.456-15.565-3.379-2.969-3.994-8.013-0.538-12.058 0 0 32.333-35.737 35.072-38.886 2.714-3.149 10.009-5.811 14.541-1.817 4.531 3.968 16.154 14.183 18.125 15.923 1.997 1.715-1.28 22.093 9.114 30.157zM226.381 288.807c-3.072 3.558-6.861 3.635-10.163 0.717l-36.736-32.025c-2.867-2.56-3.251-7.271-0.666-10.24l212.327-241.613c4.941-5.735 13.543-6.323 19.251-1.357l24.858 20.787c5.658 4.966 6.247 13.671 1.305 19.379l-210.176 244.352zM509.491 391.642c-1.894 12.647-8.448 9.984-11.853 4.608-3.353-5.351-18.457-28.186-24.653-38.502-6.144-10.291-21.248-30.489-49.51-10.547-29.389 20.787-19.149 35.303-14.055 45.056 5.146 9.805 20.941 37.248 23.245 40.704 2.253 3.456-0.409 13.491-9.498 9.293-9.139-4.224-64.589-26.265-72.295-57.856-7.859-32.179 6.605-60.903-21.76-89.447l-34.355-35.84 34.509-40.141 42.342 40.218c10.112 10.137 31.642 19.994 51.149 15.565 41.805-9.472 64.589 6.247 78.361 32.179 12.339 23.219 10.291 72.064 8.371 84.71zM70.144 41.894c-5.325-5.376-5.325-14.080 0-19.43l24.346-23.808c5.325-5.351 13.773-3.098 19.097 2.253l125.619 123.52-38.502 43.853-130.56-126.387z" /> <glyph unicode="&#xe607;" glyph-name="notification" d="M977.76 152.992l-400.608 685.952c-33.408 33.408-87.616 33.408-121.024 0l-400.64-685.952c-33.408-33.376-33.408-87.552 0-120.992h922.24c33.472 33.44 33.472 87.616 0.032 120.992zM479.744 592.704c0 26.528 21.504 48 48 48s48-21.472 48-48v-224c0-26.496-21.504-48-48-48s-48 21.504-48 48v224zM528.032 160.448c-26.496 0-48 21.44-48 48 0 26.496 21.504 48 48 48s48-21.504 48-48c0-26.56-21.504-48-48-48z" />
<glyph unicode="&#xe608;" d="M64 416h384v-384h-384z" /> <glyph unicode="&#xe608;" glyph-name="uniE608" horiz-adv-x="952" d="M438.928 896.072q14.856 0 25.714-10.856t10.858-25.714v-128h168l98.856 98.856q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714-10.856-25.714l-98.856-98.856v-482.286l98.856-98.856q10.856-10.856 10.856-25.714t-10.856-25.714-25.714-10.856-25.714 10.856l-98.856 98.856h-168v-128q0-14.856-10.856-25.714t-25.714-10.856-25.714 10.856-10.856 25.714v128q-97.714 0-165.714 38.286l-119.43-118.856q-10.856-10.856-25.714-10.856t-25.714 10.856q-10.856 10.286-10.856 25.714t10.856 25.714l112.57 113.144q-2.856 2.856-7.43 8.572t-16.286 24-20.856 37.144-16.57 46.856-7.43 55.428h512v73.144h-512q0 29.144 7.714 58t18.856 49.714 22.286 37.714 18.57 24.856l8 8.572-118.286 104.57q-12 11.43-12 27.43 0 13.714 9.144 24.57 10.286 10.858 25.43 11.714t26.57-8.856l129.714-115.428q65.144 33.142 156.57 33.142v128q0 14.858 10.858 25.714t25.714 10.856zM768.072 621.786q76 0 129.428-53.428t53.428-129.428-53.428-129.428-129.428-53.428v365.714z" />
<glyph unicode="&#xe609;" d="M181.861 118.974l20.649 28.908-22.627 22.628-28.909-20.648c-5.361 2.997-11.102 5.387-17.133 7.096l-5.841 35.042h-32l-5.84-35.043c-6.031-1.709-11.772-4.099-17.133-7.096l-28.909 20.649-22.628-22.628 20.649-28.908c-2.997-5.36-5.387-11.103-7.096-17.133l-35.043-5.841v-32l35.043-5.841c1.709-6.030 4.099-11.772 7.096-17.133l-20.649-28.908 22.627-22.628 28.909 20.648c5.361-2.997 11.102-5.387 17.133-7.096l5.841-35.042h32l5.84 35.043c6.031 1.709 11.772 4.099 17.133 7.096l28.909-20.648 22.627 22.628-20.649 28.908c2.997 5.36 5.387 11.103 7.096 17.133l35.044 5.84v32l-35.043 5.841c-1.709 6.030-4.099 11.772-7.096 17.133zM112 48c-17.674 0-32 14.327-32 32s14.326 32 32 32 32-14.327 32-32-14.326-32-32-32zM512 288v32l-33.691 6.125c-0.621 4.023-1.416 7.989-2.362 11.895l28.779 18.55-12.246 29.564-33.472-7.234c-2.107 3.455-4.363 6.81-6.746 10.065l19.503 28.171-22.628 22.627-28.171-19.503c-3.256 2.383-6.61 4.638-10.065 6.747l7.234 33.472-29.564 12.247-18.55-28.779c-3.906 0.946-7.872 1.741-11.895 2.362l-6.126 33.691h-32l-6.126-33.691c-4.023-0.621-7.988-1.416-11.895-2.362l-18.549 28.779-29.564-12.246 7.234-33.472c-3.455-2.108-6.81-4.364-10.065-6.747l-28.171 19.503-22.627-22.627 19.503-28.171c-2.383-3.255-4.639-6.61-6.747-10.065l-33.472 7.234-12.246-29.564 28.779-18.55c-0.946-3.906-1.741-7.871-2.362-11.895l-33.692-6.126v-32l33.691-6.125c0.621-4.023 1.416-7.989 2.362-11.895l-28.779-18.55 12.246-29.564 33.472 7.234c2.108-3.455 4.364-6.809 6.747-10.065l-19.503-28.171 22.627-22.628 28.171 19.503c3.255-2.383 6.61-4.638 10.065-6.746l-7.234-33.472 29.564-12.246 18.551 28.779c3.905-0.946 7.871-1.741 11.894-2.362l6.126-33.692h32l6.126 33.691c4.022 0.621 7.988 1.416 11.895 2.362l18.55-28.779 29.564 12.246-7.234 33.472c3.455 2.108 6.81 4.363 10.065 6.746l28.171-19.503 22.628 22.628-19.503 28.171c2.383 3.256 4.638 6.61 6.746 10.065l33.472-7.234 12.246 29.565-28.779 18.55c0.946 3.906 1.741 7.871 2.362 11.895l33.691 6.125zM336 234.4c-38.439 0-69.6 31.161-69.6 69.6s31.16 69.6 69.6 69.6 69.6-31.161 69.6-69.6c0-38.439-31.16-69.6-69.6-69.6z" /> <glyph unicode="&#xe609;" glyph-name="max" d="M1024 960v-384l-138.26 138.26-212-212-107.48 107.48 212 212-138.26 138.26zM245.74 821.74l212-212-107.48-107.48-212 212-138.26-138.26v384h384zM885.74 181.74l138.26 138.26v-384h-384l138.26 138.26-212 212 107.48 107.48zM457.74 286.26l-212-212 138.26-138.26h-384v384l138.26-138.26 212 212z" />
<glyph unicode="&#xe60a;" d="M367.334 149.709l-70.605 80.691 70.605 80.691c12.007 12.007 12.007 31.463 0 43.443s-31.462 11.981-43.443 0l-67.891-77.593-67.865 77.568c-12.006 12.006-31.463 12.006-43.443 0s-11.981-31.463 0-43.443l70.579-80.666-70.605-80.691c-11.981-12.007-11.981-31.411 0-43.392 12.006-12.007 31.463-12.007 43.443 0l67.891 77.543 67.865-77.543c12.007-12.007 31.462-12.007 43.443 0s12.007 31.385 0.026 43.392z" /> <glyph unicode="&#xe60a;" glyph-name="uniE60A" d="M64 384h384v-384l-138.26 138.26-202-202-107.48 107.48 202 202zM821.74 245.74l202-202-107.48-107.48-202 202-138.26-138.26v384h384zM960 512h-384v384l138.26-138.26 202 202 107.48-107.48-202-202zM309.74 757.74l138.26 138.26v-384h-384l138.26 138.26-202 202 107.48 107.48z" />
<glyph unicode="&#xe60b;" d="M224 82.745l-102.627 118.627 29.254 29.255 73.373-57.372 137.372 121.372 29.256-29.254zM415.886 416c0.039-0.033 0.081-0.075 0.114-0.115v-383.771c-0.033-0.039-0.075-0.081-0.114-0.114h-319.772c-0.040 0.033-0.081 0.075-0.114 0.114v383.772c0.033 0.040 0.075 0.081 0.115 0.114h-64.115v-384c0-35.2 28.8-64 64-64h320c35.2 0 64 28.8 64 64v384h-64.114zM320 416v32c0 17.673-14.327 32-32 32h-64c-17.673 0-32-14.327-32-32v-32h-64v-64h256v64h-64zM288 416h-64v32h64v-32z" /> <glyph unicode="&#xe60b;" glyph-name="remove" d="M800 832h-576c-53.020 0-96-42.98-96-96v-32h768v32c0 53.020-42.98 96-96 96zM632.32 896l14.116-101h-268.872l14.114 101h240.642zM640 960h-256c-26.4 0-50.99-21.392-54.642-47.538l-18.714-133.924c-3.654-26.146 14.956-47.538 41.356-47.538h320c26.4 0 45.010 21.392 41.358 47.538l-18.714 133.924c-3.654 26.146-28.244 47.538-54.644 47.538v0zM816 640h-608c-35.2 0-61.392-28.682-58.206-63.738l52.412-576.526c3.186-35.054 34.594-63.736 69.794-63.736h480c35.2 0 66.608 28.682 69.794 63.736l52.41 576.526c3.188 35.056-23.004 63.738-58.204 63.738zM384 64h-96l-32 448h128v-448zM576 64h-128v448h128v-448zM736 64h-96v448h128l-32-448z" />
<glyph unicode="&#xe60c;" d="M64 341.333v64h448v-320h-64v-64h-448v320h64zM448 128h21.334v234.667h-362.666v-21.333h341.334v-213.334zM42.666 64h362.666v234.667h-362.666v-234.667z" /> <glyph unicode="&#xe60c;" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe60d;" d="M0 272v-96c0-8.836 7.164-16 16-16h480c8.836 0 16 7.164 16 16v96c0 8.836-7.164 16-16 16h-480c-8.836 0-16-7.164-16-16z" /> <glyph unicode="&#xe60d;" glyph-name="about" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" />
<glyph unicode="&#xf021;" d="M431.714 178.286q0-1.428-0.286-2-18.286-76.572-76.572-124.143t-136.571-47.572q-41.715 0-80.715 15.714t-69.572 44.857l-36.857-36.857q-5.428-5.428-12.857-5.428t-12.857 5.428-5.428 12.857v128q0 7.428 5.428 12.857t12.857 5.428h128q7.428 0 12.857-5.428t5.429-12.857-5.428-12.857l-39.143-39.143q20.285-18.857 46-29.143t53.429-10.286q38.286 0 71.428 18.572t53.143 51.143q3.143 4.857 15.143 33.428 2.286 6.572 8.572 6.572h54.857q3.714 0 6.428-2.714t2.714-6.428zM438.857 406.857v-128q0-7.428-5.428-12.857t-12.857-5.428h-128q-7.428 0-12.857 5.428t-5.428 12.857 5.428 12.857l39.428 39.428q-42.286 39.143-99.714 39.143-38.286 0-71.428-18.571t-53.143-51.143q-3.143-4.857-15.143-33.429-2.285-6.572-8.572-6.572h-56.857q-3.715 0-6.428 2.714t-2.715 6.428v2q18.572 76.572 77.143 124.143t137.143 47.571q41.714 0 81.143-15.857t70-44.714l37.143 36.857q5.428 5.428 12.857 5.428t12.857-5.428 5.428-12.857z" /> <glyph unicode="&#xe60e;" glyph-name="undo" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
<glyph unicode="&#xf05b;" d="M342 187.428h-31.143q-7.428 0-12.857 5.428t-5.428 12.857v36.572q0 7.428 5.428 12.857t12.857 5.429h31.143q-9.143 30.857-32.143 53.857t-53.857 32.143v-31.143q0-7.429-5.429-12.857t-12.857-5.428h-36.571q-7.428 0-12.857 5.428t-5.429 12.857v31.143q-30.857-9.143-53.857-32.143t-32.143-53.857h31.143q7.429 0 12.857-5.428t5.428-12.857v-36.571q0-7.428-5.428-12.857t-12.857-5.428h-31.143q9.143-30.857 32.143-53.857t53.857-32.143v31.143q0 7.428 5.428 12.857t12.857 5.428h36.571q7.428 0 12.857-5.428t5.429-12.857v-31.143q30.857 9.143 53.857 32.143t32.143 53.857zM438.857 242.286v-36.572q0-7.428-5.428-12.857t-12.857-5.428h-40.857q-10.572-46-44.143-79.572t-79.572-44.143v-40.857q0-7.428-5.428-12.857t-12.857-5.428h-36.571q-7.428 0-12.857 5.428t-5.429 12.857v40.857q-46 10.572-79.572 44.143t-44.143 79.572h-40.857q-7.428 0-12.857 5.428t-5.428 12.857v36.572q0 7.428 5.428 12.857t12.857 5.429h40.857q10.572 46 44.143 79.572t79.572 44.143v40.857q0 7.428 5.428 12.857t12.857 5.428h36.571q7.428 0 12.857-5.428t5.429-12.857v-40.857q46-10.571 79.572-44.143t44.143-79.572h40.857q7.428 0 12.857-5.428t5.428-12.857z" /> <glyph unicode="&#xe60f;" glyph-name="stop" d="M128 832h768v-768h-768z" />
<glyph unicode="&#xf096;" d="M320 390.857h-237.714q-18.857 0-32.285-13.428t-13.428-32.285v-237.715q0-18.857 13.428-32.286t32.285-13.428h237.714q18.857 0 32.286 13.428t13.428 32.286v237.714q0 18.857-13.428 32.286t-32.286 13.428zM402.286 345.143v-237.715q0-34-24.143-58.143t-58.143-24.143h-237.714q-34 0-58.143 24.143t-24.143 58.143v237.714q0 34 24.143 58.143t58.143 24.143h237.714q34 0 58.143-24.143t24.143-58.143z" horiz-adv-x="403" /> <glyph unicode="&#xe611;" glyph-name="close" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" />
<glyph unicode="&#xf0c7;" d="M109.715 41.143h219.429v109.714h-219.429v-109.714zM365.714 41.143h36.572v256q0 4-2.857 11t-5.714 9.857l-80.286 80.285q-2.857 2.857-9.714 5.715t-11.143 2.857v-118.857q0-11.429-8-19.429t-19.428-8h-164.571q-11.428 0-19.428 8t-8 19.428v118.857h-36.572v-365.715h36.572v118.857q0 11.428 8 19.428t19.428 8h237.715q11.428 0 19.428-8t8-19.428v-118.857zM256 306.286v91.428q0 3.714-2.714 6.429t-6.428 2.714h-54.857q-3.714 0-6.429-2.714t-2.714-6.429v-91.428q0-3.714 2.714-6.428t6.428-2.714h54.857q3.714 0 6.429 2.714t2.714 6.428zM438.857 297.143v-265.143q0-11.428-8-19.428t-19.428-8h-384q-11.428 0-19.428 8t-8 19.428v384q0 11.428 8 19.428t19.428 8h265.143q11.428 0 25.143-5.714t21.714-13.714l80-80q8-8 13.714-21.714t5.714-25.143z" /> <glyph unicode="&#xe612;" glyph-name="format" d="M448 165.49l-205.254 237.254 58.508 58.51 146.746-114.744 274.744 242.744 58.512-58.508zM831.772 832c0.078-0.066 0.162-0.15 0.228-0.23v-767.542c-0.066-0.078-0.15-0.162-0.228-0.228h-639.544c-0.080 0.066-0.162 0.15-0.228 0.228v767.544c0.066 0.080 0.15 0.162 0.23 0.228h-128.23v-768c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v768h-128.228zM640 832v64c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-64h-128v-128h512v128h-128zM576 832h-128v64h128v-64z" />
<glyph unicode="&#xf0e5;" d="M256 370.286q-58.286 0-109-19.857t-80.572-53.572-29.857-72.857q0-32 20.428-61t57.572-50.143l24.857-14.286-7.714-27.428q-6.857-26-20-49.143 43.428 18 78.572 48.857l12.286 10.857 16.286-1.714q19.714-2.286 37.143-2.286 58.285 0 109 19.857t80.572 53.572 29.857 72.857-29.857 72.857-80.572 53.572-109 19.857zM512 224q0-49.714-34.286-91.857t-93.143-66.572-128.572-24.428q-20 0-41.428 2.286-56.572-50-131.429-69.143-14-4-32.572-6.286h-1.428q-4.285 0-7.715 3t-4.572 7.857v0.286q-0.857 1.143-0.143 3.428t0.572 2.857 1.285 2.714l1.715 2.572t2 2.428 2.285 2.572q2 2.286 8.857 9.857t9.857 10.857 8.857 11.286 9.285 14.572 7.715 16.857 7.428 21.714q-44.857 25.428-70.715 62.857t-25.857 80.286q0 49.714 34.285 91.857t93.143 66.571 128.571 24.429 128.571-24.429 93.143-66.571 34.286-91.857z" /> <glyph unicode="&#xe613;" glyph-name="restore" d="M128 682.666v128h896v-640h-128v-128h-896v640h128zM896 256h42.668v469.334h-725.332v-42.666h682.668v-426.668zM85.332 128h725.332v469.334h-725.332v-469.334z" />
<glyph unicode="&#xe614;" glyph-name="min" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
<glyph unicode="&#xe615;" glyph-name="redo" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
<glyph unicode="&#xe616;" glyph-name="price" d="M501.443 506.062c-119.819 31.143-158.353 63.078-158.353 113.223 0 57.535 53.047 97.916 142.518 97.916 93.957 0 128.794-44.867 131.961-110.848h116.654c-3.431 91.053-59.119 173.925-169.439 201.109v115.599h-158.353v-114.015c-102.402-22.433-184.746-88.413-184.746-190.553 0-121.933 101.082-182.636 248.086-217.999 132.225-31.671 158.353-77.856 158.353-127.474 0-36.159-25.601-94.219-142.518-94.219-108.736 0-151.755 48.826-157.297 110.848h-116.389c6.597-115.599 92.901-180.26 194.511-202.165v-114.544h158.353v113.487c102.667 19.794 184.746 79.178 184.746 187.65 0 149.38-128.266 200.581-248.086 231.987z" />
<glyph unicode="&#xe617;" glyph-name="uniE617" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
<glyph unicode="&#xe618;" glyph-name="signout" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
<glyph unicode="&#xe619;" glyph-name="email" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM256 704h512c9.138 0 18.004-1.962 26.144-5.662l-282.144-329.168-282.144 329.17c8.14 3.696 17.006 5.66 26.144 5.66zM192 256v384c0 1.34 0.056 2.672 0.14 4l187.664-218.942-185.598-185.598c-1.444 5.336-2.206 10.886-2.206 16.54zM768 192h-512c-5.654 0-11.202 0.762-16.54 2.208l182.118 182.118 90.422-105.498 90.424 105.494 182.116-182.12c-5.34-1.44-10.886-2.202-16.54-2.202zM832 256c0-5.654-0.762-11.2-2.206-16.54l-185.6 185.598 187.666 218.942c0.084-1.328 0.14-2.66 0.14-4v-384z" />
<glyph unicode="&#xe61a;" glyph-name="googleplus" d="M437.006 141.838c0-75.068-46.39-134.392-177.758-139.176-76.984 43.786-141.49 106.952-186.908 182.866 23.69 58.496 97.692 103.046 182.316 102.114 24.022-0.252 46.41-4.114 66.744-10.7 55.908-38.866 101-63.152 112.324-107.448 2.114-8.964 3.282-18.206 3.282-27.656zM512 960c-147.94 0-281.196-62.77-374.666-163.098 36.934 20.452 80.538 32.638 126.902 32.638 67.068 0 256.438 0 256.438 0l-57.304-60.14h-67.31c47.496-27.212 72.752-83.248 72.752-145.012 0-56.692-31.416-102.38-75.78-137.058-43.28-33.802-51.492-47.966-51.492-76.734 0-24.542 51.722-61.098 75.5-78.936 82.818-62.112 99.578-101.184 99.578-178.87 0-78.726-68.936-157.104-185.866-183.742 56.348-21.338 117.426-33.048 181.248-33.048 282.77 0 512 229.23 512 512s-229.23 512-512 512zM768 576v-128h-64v128h-128v64h128v128h64v-128h128v-64h-128zM365.768 620.528c11.922-90.776-27.846-149.19-96.934-147.134-69.126 2.082-134.806 65.492-146.74 156.242-11.928 90.788 34.418 160.254 103.53 158.196 69.090-2.074 128.22-76.542 140.144-167.304zM220.886 317.932c-74.68 0-138.128-25.768-182.842-63.864-24.502 59.82-38.044 125.29-38.044 193.932 0 56.766 9.256 111.368 26.312 162.396 7.374-99.442 77.352-176.192 192.97-176.192 8.514 0 16.764 0.442 24.874 1.022-7.95-15.23-13.622-32.19-13.622-49.982 0-29.97 16.488-47.070 36.868-66.894-15.402 0-30.27-0.418-46.516-0.418z" />
<glyph unicode="&#xe61b;" glyph-name="facebook" d="M512 960c282.77 0 512-229.23 512-512 0-261.094-195.438-476.53-448-508.026v380.026h176l16 128h-192v64c0 35.346 28.654 64 64 64h128v128h-128c-106.040 0-192-85.96-192-192v-64h-96v-128h96v-380.026c-252.562 31.496-448 246.932-448 508.026 0 282.77 229.23 512 512 512z" />
<glyph unicode="&#xe61c;" glyph-name="twitter" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM806.242 598.912c0.292-6.508 0.442-13.056 0.442-19.638 0-200.622-152.708-431.964-431.958-431.964-85.736 0-165.536 25.136-232.726 68.214 11.876-1.408 23.962-2.126 36.216-2.126 71.13 0 136.59 24.276 188.55 64.994-66.434 1.22-122.5 45.122-141.824 105.432 9.274-1.768 18.784-2.722 28.566-2.722 13.846 0 27.258 1.856 39.998 5.324-69.452 13.952-121.786 75.312-121.786 148.868 0 0.64 0 1.278 0.016 1.91 20.47-11.37 43.878-18.2 68.764-18.988-40.74 27.222-67.54 73.692-67.54 126.368 0 27.822 7.488 53.904 20.556 76.324 74.878-91.854 186.748-152.292 312.924-158.628-2.588 11.118-3.93 22.702-3.93 34.604 0 83.84 67.98 151.812 151.818 151.812 43.666 0 83.124-18.436 110.818-47.94 34.58 6.808 67.074 19.442 96.412 36.84-11.336-35.454-35.41-65.206-66.752-83.994 30.71 3.668 59.968 11.832 87.194 23.904-20.35-30.448-46.090-57.186-75.758-78.594z" />
<glyph unicode="&#xe61d;" glyph-name="info" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" />
<glyph unicode="&#xe61e;" glyph-name="goline" d="M384 128h640v-128h-640zM384 512h640v-128h-640zM384 896h640v-128h-640zM192 960v-256h-64v192h-64v64zM128 434v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM256 256v-320h-192v64h128v64h-128v64h128v64h-128v64z" />
<glyph unicode="&#xe61f;" glyph-name="share" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z" />
<glyph unicode="&#xe620;" glyph-name="comment" d="M0 343.808l321.344-206.624v149.152l-191.328 118.144 191.328 118.144v150.176l-321.344-207.68v-121.312zM389.312 64h110.592l162.048 768h-111.328l-161.312-768zM702.688 672.8v-150.176l191.264-118.144-191.264-118.112v-149.152l321.312 206.592v121.312l-321.312 207.68z" />
<glyph unicode="&#xe621;" glyph-name="weibo" d="M428.222 493.19c-144.194-6.656-260.742-83.888-260.742-180.224 0-96.202 116.574-168.528 260.742-161.792 144.302 6.576 261.094 96.876 261.094 193.078 0 96.12-116.79 155.594-261.094 148.938zM526.472 250.88c-44.168-56.994-131.53-84.802-216.36-38.858-40.394 21.908-38.884 64.916-38.884 64.916s-16.76 135.842 128.27 152.818c145.192 16.816 171.116-121.856 126.976-178.876zM429.19 341.854c-9.298-6.736-11.182-19.618-6.144-27.62 4.85-8.22 16.142-9.162 25.276-2.318 8.974 7.086 12.45 19.428 7.572 27.62-4.798 7.976-15.952 10.294-26.704 2.318zM360.448 323.154c-27.108-2.802-46.484-26.408-46.484-48.99 0-22.636 21.826-38.264 48.882-35.086 26.974 3.072 48.908 23.928 48.908 46.484 0.054 22.636-20.184 40.582-51.308 37.592zM868.082 960h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22c0 86.096-69.768 155.89-155.89 155.89zM818.202 280.386c-59.446-126.274-255.462-187.716-400.734-176.344-138.050 10.86-315.526 56.724-333.878 223.798 0 0-9.7 75.668 63.65 173.568 0 0 105.498 147.322 228.378 189.36 122.988 41.85 137.35-28.968 137.35-70.818-6.548-35.49-18.782-56.374 27.38-42.038 0 0 120.886 56.076 170.658 6.332 40.126-40.152 6.63-95.42 6.63-95.42s-16.654-18.404 17.624-25.034c34.358-6.872 142.362-56.914 82.944-183.404zM698.988 629.274c-13.15 0-23.714 10.644-23.714 23.688 0 13.286 10.562 23.956 23.714 23.956 0 0 148.212 27.404 130.48-131.852 0-0.942-0.108-1.698-0.322-2.534-1.672-11.29-11.586-19.94-23.256-19.94-13.204 0-23.956 10.562-23.956 23.74 0-0.026 23.498 106.416-82.944 82.944zM949.518 501.894h-0.216c-3.908-26.948-17.274-29.104-33.198-29.104-19.052 0-34.438 11.964-34.438 31.044 0 16.52 6.846 33.308 6.846 33.308 2.020 6.952 18.136 50.176-10.644 114.796-52.708 88.522-158.856 89.816-171.384 84.776-12.638-4.958-31.286-7.436-31.286-7.436-19.188 0-34.548 15.602-34.548 34.572 0 15.926 10.644 29.4 25.196 33.524 0 0 0.322 0.538 0.808 0.62 1.052 0.216 2.13 1.268 3.26 1.374 14.794 2.83 67.476 13.178 118.702 1.186 91.648-21.396 217.52-110.026 160.904-298.658z" />
<glyph unicode="&#xe623;" glyph-name="book" d="M512 768c0 0-128 128-512 128v-768c388 0 512-128 512-128s124 128 512 128v768c-384 0-512-128-512-128zM128 768c162.688-13.632 262.496-51.264 320-81.76v-515.488c-57.504 30.368-157.312 68-320 81.76v515.488zM896 252.512c-162.752-13.76-262.496-51.328-320-81.76v515.488c57.504 30.496 157.248 68.128 320 81.76v-515.488z" />
<glyph unicode="&#xe624;" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
<glyph unicode="&#xe900;" glyph-name="qqz" d="M761.562 521.546l-305.314-229.928s122.534-19.085 307.335-16.875l-8.413 38.021 263.313 239.505c4.84 4.404 6.694 11.413 4.708 17.804-1.974 6.394-7.405 10.93-13.783 11.494l-347.126 31.289-135.62 336.932c-2.479 6.184-8.268 10.213-14.657 10.213-6.397 0-12.184-4.029-14.665-10.213l-135.621-336.932-347.128-31.289c-6.374-0.563-11.801-5.1-13.781-11.494-1.987-6.392-0.13-13.401 4.713-17.804l263.31-239.505-78.925-356.295c-1.445-6.521 0.962-13.347 6.142-17.317 5.196-3.9 12.107-4.202 17.608-0.752l298.345 188.885 298.347-188.885c2.554-1.655 5.395-2.401 8.235-2.401 3.299 0 6.583 1.049 9.366 3.146 5.182 3.98 7.593 10.803 6.149 17.325l-62.412 281.762c26.77 14.131 56.254 38.051 56.254 38.051s-116.838-59.919-536.289-30.65l303.994 231.311s-11.952 19.514-392.923 33.442c-25.494 0.938 310.6 66.909 558.84 11.159z" />
<glyph unicode="&#xe9d7;" glyph-name="start" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xf00a;" glyph-name="github" d="M512 960c-282.75 0-512-229.25-512-512 0-226.25 146.688-418.126 350.156-485.812 25.594-4.688 34.938 11.126 34.938 24.626 0 12.188-0.468 52.562-0.718 95.312-142.376-30.938-172.468 60.376-172.468 60.376-23.312 59.126-56.844 74.876-56.844 74.876-46.532 31.75 3.53 31.126 3.53 31.126 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.876-55.626 149-42.5 4.654 33 17.904 55.626 32.5 68.376-113.656 12.938-233.218 56.876-233.218 253.062 0 55.938 19.968 101.562 52.656 137.406-5.218 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.032 128.126 17.22 43.5-0.188 87.312-5.876 128.188-17.28 97.688 66.312 140.688 52.5 140.688 52.5 28-70.532 10.376-122.562 5.126-135.5 32.812-35.844 52.626-81.47 52.626-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.876 34.75-47 34.75-94.75 0-68.438-0.688-123.626-0.688-140.5 0-13.626 9.312-29.562 35.25-24.562 203.312 67.812 349.876 259.688 349.876 485.812 0 282.75-229.25 512-512 512z" />
<glyph unicode="&#xf021;" glyph-name="refresh" d="M863.428 356.572q0-2.856-0.572-4-36.572-153.144-153.144-248.286t-273.142-95.144q-83.43 0-161.43 31.428t-139.144 89.714l-73.714-73.714q-10.856-10.856-25.714-10.856t-25.714 10.856-10.856 25.714v256q0 14.856 10.856 25.714t25.714 10.856h256q14.856 0 25.714-10.856t10.858-25.714-10.856-25.714l-78.286-78.286q40.57-37.714 92-58.286t106.858-20.572q76.572 0 142.856 37.144t106.286 102.286q6.286 9.714 30.286 66.856 4.572 13.144 17.144 13.144h109.714q7.428 0 12.856-5.428t5.428-12.856zM877.714 813.714v-256q0-14.856-10.856-25.714t-25.714-10.856h-256q-14.856 0-25.714 10.856t-10.856 25.714 10.856 25.714l78.856 78.856q-84.572 78.286-199.428 78.286-76.572 0-142.856-37.142t-106.286-102.286q-6.286-9.714-30.286-66.858-4.57-13.144-17.144-13.144h-113.714q-7.43 0-12.856 5.428t-5.43 12.856v4q37.144 153.144 154.286 248.286t274.286 95.142q83.428 0 162.286-31.714t140-89.428l74.286 73.714q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714z" />
<glyph unicode="&#xf0c7;" glyph-name="save" d="M219.43 82.286h438.858v219.428h-438.858v-219.428zM731.428 82.286h73.144v512q0 8-5.714 22t-11.428 19.714l-160.572 160.57q-5.714 5.714-19.428 11.43t-22.286 5.714v-237.714q0-22.858-16-38.858t-38.856-16h-329.142q-22.856 0-38.856 16t-16 38.856v237.714h-73.144v-731.43h73.144v237.714q0 22.856 16 38.856t38.856 16h475.43q22.856 0 38.856-16t16-38.856v-237.714zM512 612.572v182.856q0 7.428-5.428 12.858t-12.856 5.428h-109.714q-7.428 0-12.858-5.428t-5.428-12.858v-182.856q0-7.428 5.428-12.856t12.856-5.428h109.714q7.428 0 12.858 5.428t5.428 12.856zM877.714 594.286v-530.286q0-22.856-16-38.856t-38.856-16h-768q-22.856 0-38.856 16t-16 38.856v768q0 22.856 16 38.856t38.856 16h530.286q22.856 0 50.286-11.428t43.428-27.428l160-160q16-16 27.428-43.428t11.428-50.286z" />
<glyph unicode="&#xf0ed;" glyph-name="export" horiz-adv-x="1097" d="M731.429 429.714q0 8-5.143 13.143t-13.143 5.143h-128v201.143q0 7.429-5.429 12.857t-12.857 5.429h-109.714q-7.429 0-12.857-5.429t-5.429-12.857v-201.143h-128q-7.429 0-12.857-5.429t-5.429-12.857q0-8 5.143-13.143l201.143-201.143q5.143-5.143 13.143-5.143t13.143 5.143l200.571 200.571q5.714 6.857 5.714 13.714zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf0ee;" glyph-name="import" horiz-adv-x="1097" d="M731.429 466.286q0 8-5.143 13.143l-201.143 201.143q-5.143 5.143-13.143 5.143t-13.143-5.143l-200.571-200.571q-5.714-6.857-5.714-13.714 0-8 5.143-13.143t13.143-5.143h128v-201.143q0-7.429 5.429-12.857t12.857-5.429h109.714q7.429 0 12.857 5.429t5.429 12.857v201.143h128q7.429 0 12.857 5.429t5.429 12.857zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf11c;" glyph-name="keyboard" horiz-adv-x="1097" d="M219.429 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM292.571 438.857v-54.857q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h128q9.143 0 9.143-9.143zM219.429 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 292.571v-54.857q0-9.143-9.143-9.143h-493.714q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h493.714q9.143 0 9.143-9.143zM438.857 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM365.714 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM585.143 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM512 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM731.429 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM658.286 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 585.143v-201.143q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h64v137.143q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM1024 155.429v512h-950.857v-512h950.857zM1097.143 667.429v-512q0-30.286-21.429-51.714t-51.714-21.429h-950.857q-30.286 0-51.714 21.429t-21.429 51.714v512q0 30.286 21.429 51.714t51.714 21.429h950.857q30.286 0 51.714-21.429t21.429-51.714z" />
<glyph unicode="&#xf148;" glyph-name="moveup" horiz-adv-x="585" d="M580.891 578.932q-10.269-21.109-33.090-21.109h-109.537v-492.917q0-7.987-5.135-13.122t-13.122-5.135h-401.637q-11.981 0-16.544 10.269-4.564 11.411 2.282 19.968l91.282 109.537q5.135 6.276 14.263 6.276h182.562v365.124h-109.537q-22.82 0-33.090 21.109-9.698 21.109 5.135 38.794l182.562 219.075q10.269 12.551 27.955 12.551t27.955-12.551l182.562-219.075q15.404-18.257 5.135-38.794z" />
<glyph unicode="&#xf149;" glyph-name="movedown" horiz-adv-x="585" d="M18.286 813.714h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" />
</font></defs></svg> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
static/css/fonts/icomoon.ttf Normal file → Executable file

Binary file not shown.

BIN
static/css/fonts/icomoon.woff Normal file → Executable file

Binary file not shown.

40
static/css/lib.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,99 +0,0 @@
.wrapper {
margin: 0 auto;
width: 980px;
}
.header .logo {
float: left;
height: 32px;
margin-top: 3px;
}
.header {
margin: 8px 0;
}
.header li {
float: left;
}
.header a {
display: block;
font-weight: bold;
padding: 4px 8px;
color: #333;
line-height: 30px;
text-decoration: none;
}
.header a:hover {
color: #4183C4;
}
.content {
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #919191;
background-color: #202021;
}
.content h2 {
color: #FFF;
font-size: 70px;
margin: 0 0 60px;
}
.content h3 {
color: #4183c4;
font-size: 21px;
}
.content .form {
width: 320px;
margin-top: 28px;
position: relative;
}
.content .form input {
width: 100%;
background-color: #fafafa;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset;
color: #333;
min-height: 34px;
outline: medium none;
vertical-align: middle;
font-size: 16px;
border: 1px solid #FFF;
padding: 10px;
margin-top: 20px;
}
.content .form input:focus {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
}
#msg {
background-color: #fcdede;
border: 1px solid #d2b2b2;
padding: 15px;
font-size: 14px;
color: #911;
position: absolute;
width: 100%;
top: -48px;
}
.footer {
line-height: 30px;
color: #777;
font-size: 12px;
text-align: center;
}
.footer a {
text-decoration: none;
color: #4183c4;
}
.footer a:hover {
text-decoration: underline;
}

158
static/css/playground.css Normal file
View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
body {
display: flex;
flex-direction: column;
max-height: 100vh;
}
.main {
flex: 1;
min-height: 1px;
display: flex;
}
.header {
margin: 0;
padding: 5px;
}
.header li {
margin-left: 0;
margin-right: 20px;
}
.header .gravatar {
width: 26px;
border-radius: 13px;
}
.header .logo {
height: 28px;
margin-top: -6px;
}
.header .font-ico {
font-size: 18px;
line-height: 28px;
}
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
padding: 5px 0px;
right: 0px;
line-height: normal;
top: 38px;
border-color: #999;
}
.share-panel .font-ico {
transition: all .2s ease-out 0s;
margin: 0 5px;
width: 24px;
}
.share-panel .font-ico:hover {
transform: rotate(360deg);
}
.footer {
height: 30px;
text-shadow: 0 0 0;
}
#editorDiv {
width: 100%;
height: 70%;
}
.bottom-window-group {
height: 30%;
}
#output {
height: 100%;
width: 100%;
border-width: 0;
margin: 0;
padding: 2px 5px;
border-top: 1px solid #919191;
}
#dialogShare {
margin: 10px 15px 0;
line-height: 28px;
}
#dialogShare a {
white-space: pre-wrap;
word-wrap: break-word;
}
.wrapper {
width: auto;
}
.btn {
padding: 3px 8px;
font-size: 13px;
line-height: 20px;
height: 28px;
}
#editorDivWrap {
width: 70%;
}
#goNews {
width: 30%;
overflow: auto;
border-left: 1px solid #919191;
}
#goNews::-webkit-scrollbar {
display: none;
}
#goNews li a {
display: block;
padding: 8px 10px;
text-shadow: 0 1px 0 #fff;
border-bottom: 1px solid #eee;
color: #666;
}
#goNews li a.fn-right {
padding: 0;
border: 0;
color: #4285f4;
}
#goNews li a:hover {
text-decoration: none;
background-color: #f9f9f9;
}
#goNews li.title {
border-bottom: 1px solid #e5e5e5;
padding: 10px;
font-size: 14px;
line-height: 18px;
background-color: #f9fafb;
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#shellOutput, #shellInput { #shellOutput, #shellInput {
width: 100%; width: 100%;
} }

View File

@ -1,27 +1,89 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* themes for side
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
/* start side */ /* start side */
.side { .side {
background-color: #FFF;
width: 20%; width: 20%;
position: absolute; position: absolute;
border-right: 1px solid #9B9B9B;
height: 100%; height: 100%;
z-index: 8; z-index: 8;
flex-flow: column;
display: flex;
} }
.side-max { .side-max {
width: 100%; width: 100%;
z-index: 11; z-index: 11;
} }
/* end side */
.side .tabs-panel { /* start side right */
.side-right .tabs-panel > div {
overflow: auto; overflow: auto;
} }
/* start side */
.side-right {
flex-flow: column;
}
#outline .ico {
margin: 1px 5px 0 5px;
}
.ico-func {
background-position: -123px -21px;
}
.ico-interface {
background-position: -143px -21px;
}
.ico-const {
background-position: -103px -21px;
}
.ico-var {
background-position: -63px -21px;
}
.ico-struct {
background-position: -83px -21px;
}
.ico-type {
background-position: -163px -21px;
}
.ico-package {
background-position: -183px -21px;
}
/* end side right */
/* start tree */ /* start tree */
.ztree { .ztree {
width: 100%; width: 100%;
padding: 0; padding: 0;
outline: 0px;
border: 0px;
} }
.ztree li a.curSelectedNode { .ztree li a.curSelectedNode {
@ -29,6 +91,7 @@
border-width: 0; border-width: 0;
color: #fff; color: #fff;
height: 18px; height: 18px;
opacity: 1;
} }
.ztree li a:hover { .ztree li a:hover {
@ -36,7 +99,9 @@
} }
.ztree li > a > span.button, .ztree li > a > span.button,
.ztree li > a > span.button.ico-ztree-dir { .ztree li > a > span.button.ico-ztree-dir,
.ztree li > a > span.button.ico-ztree-dir-api,
.ztree li > a > span.button.ico-ztree-dir-workspace {
margin-right: 2px; margin-right: 2px;
} }
@ -49,6 +114,14 @@
background-position: -2px -23px; background-position: -2px -23px;
} }
.ico-ztree-dir-api {
background-position: -22px -23px;
}
.ico-ztree-dir-workspace {
background-position: -42px -23px;
}
.ico-ztree-html { .ico-ztree-html {
background-position: -4px -2px; background-position: -4px -2px;
} }

241
static/css/sign.css Normal file
View File

@ -0,0 +1,241 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.wrapper {
margin: 0 auto;
width: 980px;
display: flex;
align-items: center;
}
.header .logo {
float: left;
height: 32px;
margin-top: 3px;
}
.header {
margin: 8px 0;
}
.header li {
float: left;
margin-left: 25px;
}
.header a {
display: block;
font-weight: bold;
padding: 5px 0;
color: #333;
line-height: 30px;
text-decoration: none;
}
.header a:hover {
color: #4285f4;
}
.fn-left {
flex: 1;
}
.content {
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #919191;
background-color: #3b3e43;
flex: 1;
display: flex;
align-items: center;
}
.content h2 {
color: #FFF;
font-size: 70px;
margin: 0 0 60px;
}
.content h3 {
color: #4285f4;
font-size: 21px;
}
.content .form {
padding: 24px 15px;
background-color: #fff;
border-radius: 6px;
width: 320px;
position: relative;
}
.content a {
color: #4285f4;
}
.login__icon {
width: 200px;
transition: all .15s ease-in-out;
padding-right: 24px;
color: #3b3e43;
fill: currentColor;
}
.login__icon:hover {
transform: scale(1.1);
}
.btn {
width: 100%;
color: #fff;
background-color: #2ebc4f;
padding: 10px;
border-radius: 3px;
cursor: pointer;
border: 0;
display: block;
text-align: center;
}
.btn:hover {
text-decoration: none;
background-color: #28a745;
}
.btn:focus {
box-shadow: 0 0 0 0.2em rgba(40, 167, 69, .3);
}
.btn-blue {
background-color: #4285f4;
}
.btn-blue:hover {
background-color: #2a75f3;
}
.btn.btn-white,
.btn.btn-red {
color: #333;
background-color: #fff;
}
.btn.btn-red {
color: #d23f31;
}
.btn.btn-white:hover {
background-color: #ddd;
}
.btn.btn-red:hover {
color: #fff;
background-color: #d23f31;
}
.desc {
color: #6a737d;
font-size: 12px;
}
.more-detail {
display: none;
margin: 0 0 8px 60px;
}
.checkbox {
margin-top: 8px;
display: block;
}
.view-more {
cursor: pointer;
display: block;
text-align: center;
color: #4285f4;
margin-top: 8px;
}
.footer {
line-height: 30px;
color: #777;
font-size: 12px;
text-align: center;
position: relative;
}
.footer a {
text-decoration: none;
color: #4285f4;
}
.footer a:hover {
text-decoration: underline;
}
.footer .github-btns {
height: 25px;
position: absolute;
top: 5px;
right: 0;
}
/* start sign up */
.dir {
color: #4285f4;
font-size: 18px;
word-wrap: break-word;
margin-top: 20px;
}
#dir {
color: #999;
font-size: 13px;
}
.form.sign-up {
margin-top: -108px;
}
#loginBtn,
#signUpBtn {
margin-top: 20px;
font-size: 16px;
}
.start {
text-align: center;
}
.start .btn {
color: #fff;
}
.start svg {
fill: currentColor;
}
.start__aciton {
display: flex;
align-items: center;
}
/* end sign up */

View File

@ -1,8 +1,25 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#startPage { #startPage {
padding: 50px 70px; padding: 50px 70px;
line-height: 28px; line-height: 28px;
white-space: normal; white-space: normal;
word-wrap: break-word; word-wrap: break-word;
overflow: auto;
} }
#startPage a { #startPage a {
@ -15,12 +32,13 @@
} }
#startPage .title { #startPage .title {
background-color: #f5f5f5; background-color: #BBB;
border-bottom-width: 0 !important; border-bottom-width: 0 !important;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
font-size: 15px; font-size: 15px;
margin-bottom: 10px; margin-bottom: 10px;
padding: 5px 10px; padding: 5px 10px;
color: #FFF;
} }
#startPage .details { #startPage .details {
@ -35,7 +53,7 @@
#startPage .details li.border { #startPage .details li.border {
padding-bottom: 5px; padding-bottom: 5px;
margin-bottom: 5px; margin-bottom: 5px;
border-bottom: 1px solid #f1f1f1; border-bottom: 1px solid #919191;
} }
#startPage .details li.border.workspace { #startPage .details li.border.workspace {
@ -47,19 +65,22 @@
} }
#startPage .news { #startPage .news {
height: 300px;
width: 60%; width: 60%;
float: right; float: right;
border-left: 1px solid #f1f1f1; border-left: 1px solid #f1f1f1;
margin-left: 10%; margin-left: 10%;
padding-left: 10%; padding-left: 10%;
white-space: nowrap;
overflow: hidden;
} }
#startPage .news li { #startPage .news li {
border-bottom: 1px solid #f1f1f1; border-bottom: 1px solid #919191;
} }
#startPage .date { #startPage .date {
color: #bbb; color: #bbb;
font-size: 13px; font-size: 13px;
word-wrap: normal;
white-space: nowrap;
} }

179
static/css/themes/dark.css Normal file
View File

@ -0,0 +1,179 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* themes for dark.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
.side {
background-color: #303130;
}
.side-right {
background-color: #303130;
color: #999;
}
.footer {
background-color: #181B1D;
border-top: 1px solid #000000;
color: rgba(255, 255, 255, 0.5);
}
.edit-panel {
background-color: #181818;
color: #EBEBEB;
}
.font-ico,
.ico-restore {
color: rgba(255, 255, 255, 0.5);
}
.font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico {
color: #e1e1e1;
}
.menu {
background: #252525;
box-shadow: 0 1px 0 0 #353535 inset;
border-bottom: 1px solid #000000;
}
.menu > ul > li > span {
color: #cecece;
text-shadow: #292a2b 0px 1px 0px;
}
.menu > ul > li.selected {
background-color: #494949;
color: #d4d4d4;
box-shadow: 1px 0 0 0 #000000 inset, 1px 0 0 0 #000000, 0 1px 0 0 rgba(255, 255, 255, 0.15) inset;
}
.menu .split {
border-color: #000000;
}
.frame {
color: #f1f1f1;
border: 1px solid #00040a;
box-shadow: 0px 3px 15px 0px rgba(0, 0, 0, 0.65);
background-color: #494949;
text-shadow: 0px 1px 0px #2c2c2c;
}
.frame li:hover,
.zeroclipboard-is-hover {
background-color: #3875D7;
color: #FFF;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.frame li.disabled:hover {
background-color: #494949;
color: #999;
}
.frame .hr {
font-size: 1px;
margin: 2px 3px;
border-top: 1px solid #353535;
border-bottom: 1px solid #565656;
height: 0;
}
.tabs {
box-shadow: 0 -1px 0 0 #000000 inset, 0 1px 0 0 rgba(255, 255, 255, 0.06) inset, 0 1px 0 rgba(255, 255, 255, 0.06);
background-color: #252525;
}
.tabs > div {
background-color: #303030;
color: #adadad;
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
border-right: 1px solid #181B1D;
}
.tabs > div:hover {
background-color: #363636;
}
.tabs > div.current {
color: rgba(255, 255, 255, 0.85);
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
background-color: #555;
}
.dialog-header-bg {
border-bottom: 1px solid #000000;
box-shadow: 0 1px #5b5c5e inset;
background-image: -webkit-linear-gradient(top, #404143 0%, #333537 52%, #252729 52%, #252729 100%);
}
.dialog-title {
font-weight: bold;
color: #ffffff;
}
.dialog-main {
background-color: #DEDEDF;
}
.dialog-footer {
box-shadow: 0 1px 0 0 #353535 inset;
border-top: 1px solid #000000;
background-color: #2B2B2B;
}
.ztree li a {
color: #e1e1e1;
}
.bottom-window-group .tabs-panel {
background-color: #181818;
color: #ebebeb;
}
#dialogPreference .preference {
border: 1px solid #000;
}
/* start layout resize */
.ui-layout-resizer,
.ui-layout-resizer-dragging,
.ui-layout-resizer-open-hover,
.ui-layout-resizer-dragging {
background: #000;
}
.ui-layout-resizer-closed {
background-color: #555555;
}
.ui-layout-resizer-west-closed {
border-right: 1px solid #000;
}
.ui-layout-resizer-south-closed {
border-top: 1px solid #000;
}
.ui-layout-resizer-east-closed {
border-left: 1px solid #000;
}
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
background: #000;
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* themes for default.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
.side {
background-color: #FFF;
}
.side-right {
background-color: #FFF;
}
.footer {
border-top: 1px solid #919191;
background-color: #F0F0F0;
color: #919191;
}
.edit-panel {
background-color: #FFF;
}
.font-ico,
.ico-restore {
color: #666;
}
.font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico {
color: #333;
}
.menu {
background: #F0F0F0;
box-shadow: 0 1px 0 0 #E7E7E7 inset;
border-bottom: 1px solid #919191;
}
.menu > ul > li > span {
color: #000;
text-shadow: 0px 1px 0px #efefef;
}
.menu > ul > li.selected {
background-color: #cfcfcf;
box-shadow: 1px 0 0 0 #b6b6b6 inset, 1px 0 0 0 #b6b6b6, 0 1px 0 0 rgba(255, 255, 255, 0.15) inset;
color: #393939;
}
.menu .split {
border-color: #b6b6b6;
}
.frame {
border: 1px solid #5F5F5F;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
background-color: #F8F8F8;
}
.frame li:hover,
.zeroclipboard-is-hover {
background-color: #3875D7;
color: #FFF;
}
.frame li.disabled:hover {
background-color: #F8F8F8;
color: #999;
}
.frame .hr {
font-size: 1px;
margin: 2px 3px;
border-top: 1px solid #e6e6e6;
border-bottom: 1px solid #F8F8F8;
height: 0;
}
.tabs {
box-shadow: 0 -1px 0 0 rgba(6, 6, 6, 0.1) inset, 0 1px 0 0 rgba(255, 255, 255, 0.44999999999999996) inset, 0 1px 0 transparent;
background-color: #E6E6E6;
}
.tabs > div {
background-color: #DDD;
color: #8B8B8B;
border-right: 1px solid #ADADAD;
}
.tabs > div:hover {
background-color: #E4E4E4;
}
.tabs > div.current {
background-color: #bbb;
color: #FFF;
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
}
.dialog-header-bg {
border-bottom: 1px solid #8891A1;
}
.dialog-title {
color: #000;
}
.dialog-main {
background-color: #F0F0F0;
}
#dialogPreference .preference {
border: 1px solid #A4A4A4;
}
/* start layout resize */
.ui-layout-resizer,
.ui-layout-resizer-dragging {
background: #bbb;
}
.ui-layout-resizer-open-hover , /* hover-color to 'resize' */
.ui-layout-resizer-dragging { /* resizer beging 'dragging' */
background: #919191;
}
.ui-layout-resizer-west-closed {
border-right: 1px solid #919191;
}
.ui-layout-resizer-south-closed {
border-top: 1px solid #919191;
}
.ui-layout-resizer-east-closed {
border-left: 1px solid #919191;
}
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
background: #919191;
}

View File

@ -1,74 +1,28 @@
/* start icon */ /*
@font-face { * Copyright (c) 2014-present, b3log.org
font-family: 'icomoon'; *
src:url('fonts/icomoon.eot?35cb2z'); * Licensed under the Apache License, Version 2.0 (the "License");
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'), * you may not use this file except in compliance with the License.
url('fonts/icomoon.woff?35cb2z') format('woff'), * You may obtain a copy of the License at
url('fonts/icomoon.ttf?35cb2z') format('truetype'), *
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg'); * https://www.apache.org/licenses/LICENSE-2.0
font-weight: normal; *
font-style: normal; * Unless required by applicable law or agreed to in writing, software
} * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.font-ico { * See the License for the specific language governing permissions and
font-family: 'icomoon'; * limitations under the License.
/* Better Font Rendering =========== */ */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #666;
cursor: pointer;
font-size: 13px;
line-height: 18px;
}
.font-ico:hover {
color: #333;
}
.ico-play:before {
content: "\e605";
}
.ico-save:before {
content: "\f0c7";
}
.ico-max:before {
content: "\f096";
}
.ico-format:before {
content: "\e60b";
}
.ico-buildrun:before {
content: "\e607";
}
.ico-stop:before {
content: "\e608";
}
.ico-restore:before {
content: "\e60c";
}
.ico-min:before {
content: "\e60d";
position: absolute;
right: 5px;
}
.ico-close:before {
content: "\e60a";
}
/* end ico */
/*
* themes for wide.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.1, Dec 15, 2015
*/
/* start frame */ /* start frame */
.frame { .frame {
position: absolute; position: absolute;
border: 1px solid #5F5F5F;
background-color: #F8F8F8;
width: 320px; width: 320px;
z-index: 21; z-index: 21;
display: none; display: none;
@ -80,17 +34,9 @@
cursor: pointer; cursor: pointer;
} }
.frame li.disabled { .frame li.disabled,
color: #999; .frame li.disabled .font-ico,
} .frame li.disabled:hover .font-ico {
.frame li:hover {
background-color: #3875D7;
color: #FFF;
}
.frame li.disabled:hover {
background-color: #F8F8F8;
color: #999; color: #999;
} }
@ -104,51 +50,51 @@
color: #FFF; color: #FFF;
} }
.frame .hr { .frame .space {
background-color: #bdbdbd; display: inline-block;
height: 1px; width: 20px;
margin: 0 1px; height: 15px;
}
.frame .font-ico {
margin-right: 5px;
width: 15px;
display: inline-block;
text-align: center;
} }
/* end frame */ /* end frame */
/* start tabs */ /* start tabs */
.tabs { .tabs {
height: 20px; height: 21px;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
background-color: #E6E6E6;
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #9D9D9D;
} }
.tabs > div { .tabs > div {
float: left; float: left;
line-height: 18px; line-height: 20px;
height: 18px; height: 20px;
padding: 0 5px; padding: 0 5px;
cursor: pointer; cursor: pointer;
background-color: #DDD;
color: #8B8B8B;
border-right: 1px solid #ADADAD;
} }
.tabs > div.current { .tabs > div > span.changed {
background-color: #9F9F9F; font-weight: bold;
color: #FFF; }
.tabs-panel {
overflow: auto;
flex: 1;
height: 100%;
} }
/* end tabs */ /* end tabs */
/* start framework */
.content {
position: relative;
overflow: hidden;
}
/* end framework */
/* start menu */ /* start menu */
.menu { .menu {
background-color: #F0F0F0; display: block !important;
height: 24px;
} }
.menu > ul > li { .menu > ul > li {
@ -156,34 +102,67 @@
} }
.menu > ul > li > span { .menu > ul > li > span {
color: #000;
font-size: 12px; font-size: 12px;
line-height: 24px; line-height: 21px;
padding: 5px;
text-decoration: none;
cursor: pointer; cursor: pointer;
padding: 4px 7px;
}
.menu .split {
float: left;
border-left: 1px solid #919191;
height: 21px;
margin: 0 5px 0 0px
}
.menu img.gravatar {
float: left;
margin: 2px 8px;
height: 17px;
width: 17px;
border-radius: 9px;
}
#buildRun {
color: #6DB14C;
font-size: 19px;
}
#buildRun.ico-stop {
color: #9d0000;
font-size: 16px;
}
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
padding: 5px 0;
right: 0px;
top: 21px;
}
.share-panel .font-ico {
font-size: 20px;
transition:all .2s ease-out 0s;
margin: 0 5px;
width: 24px;
}
.share-panel .font-ico:hover {
transform:rotate(360deg);
} }
/* end menu */ /* end menu */
/* start editor */ /* start editor */
.edit-panel { .edit-panel {
width: 80%;
position: absolute; position: absolute;
left: 20%; left: 20%;
width: 80%; width: 60%;
height: 70%; height: 70%;
overflow: hidden; overflow: hidden;
} flex-flow: column;
display: flex;
.edit-panel .tabs > div {
background-color: #d1d1d1;
border-right-color: #9b9b9b;
color: #333;
cursor: auto;
}
.edit-panel .tabs > div.current {
background-color: #F7F7F7;
} }
.toolbars { .toolbars {
@ -192,7 +171,7 @@
top: 1px; top: 1px;
} }
.edit-panel .tabs .ico { .ico {
background-image: url("../images/ico-file.png"); background-image: url("../images/ico-file.png");
float: left; float: left;
height: 16px; height: 16px;
@ -200,7 +179,7 @@
width: 16px; width: 16px;
} }
/* 统一为 static/js/lib/codemirror-4.5/addon/hint/show-hint.css 中的.CodeMirror-hints */ /* 统一为 static/js/lib/codemirror-x.x/addon/hint/show-hint.css 中的.CodeMirror-hints */
.edit-exprinfo { .edit-exprinfo {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
@ -218,7 +197,6 @@
background: white; background: white;
font-size: 90%; font-size: 90%;
font-family: Consolas, Courier New, monospace;
max-height: 20em; max-height: 20em;
overflow-y: auto; overflow-y: auto;
@ -226,7 +204,11 @@
.CodeMirror, .CodeMirror,
.CodeMirror-hints { .CodeMirror-hints {
font-family: Consolas, Courier New, monospace; font-family: Consolas, 'Courier New', monospace;
}
.CodeMirror-hints .ico {
margin: -1px 2px 0 -1px;
} }
.CodeMirror-focused .cm-matchhighlight { .CodeMirror-focused .cm-matchhighlight {
@ -244,58 +226,63 @@
background: #08f; background: #08f;
color: white; color: white;
} }
.CodeMirror div.CodeMirror-cursor {
border-left: 2px solid #333;
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: transparent;
}
/* end editor */ /* end editor */
/* start bottom-window-group */ /* start bottom-window-group */
.bottom-window-group { .bottom-window-group {
width: 80%;
position: absolute;
left: 20%;
width: 80%;
height: 30%;
top: 70%;
z-index: 7;
background-color: #fff; background-color: #fff;
} flex-flow: column;
.bottom-window-group-max {
height: 100%;
left: 0;
top: 0;
width: 100%;
z-index: 11;
}
.bottom-window-group > div > div {
overflow: auto;
} }
.bottom-window-group .output { .bottom-window-group .output {
font-family: Consolas, Courier New, monospace;
padding: 0 5px; padding: 0 5px;
line-height: 16px; line-height: 16px;
font-size: 12px; font-size: 12px;
overflow-x: scroll;
outline: 0;
} }
.bottom-window-group .output pre { .bottom-window-group .output pre {
margin: 0; margin: 0;
font-family: Consolas, 'Courier New', monospace
} }
.bottom-window-group .output .start-build, .bottom-window-group .output .start-build,
.bottom-window-group .output .start-install, .bottom-window-group .output .start-test, .start-vet,
.bottom-window-group .output .start-get { .bottom-window-group .output .start-install {
color: #999; color: #999;
} }
.bottom-window-group .output .build-succ, .bottom-window-group .output .build-succ,
.bottom-window-group .output .install-succ, .bottom-window-group .output .test-succ, .vet-succ,
.bottom-window-group .output .get-succ { .bottom-window-group .output .install-succ {
color: rgb(0,153,0); color: rgb(0,153,0);
} }
.bottom-window-group .output .build-failed, .bottom-window-group .output .build-error,
.bottom-window-group .output .install-failed, .bottom-window-group .output .test-error, .vet-error,
.bottom-window-group .output .get-failed { .bottom-window-group .output .install-error {
color: red; color: #9d0000;
}
.bottom-window-group .output .stderr {
color: gray;
font-style: italic;
}
.bottom-window-group .output .path {
text-decoration: underline;
cursor: pointer;
} }
.bottom-window-group table { .bottom-window-group table {
@ -303,8 +290,13 @@
} }
.bottom-window-group td { .bottom-window-group td {
border-bottom: 1px solid #DDD; border-bottom: 1px solid #919191;
line-height: 20px; font-size: 12px;
line-height: 19px;
}
.bottom-window-group .notification {
outline: 0;
} }
.bottom-window-group .notification .type, .bottom-window-group .notification .type,
@ -313,39 +305,19 @@
padding: 0 5px; padding: 0 5px;
} }
.bottom-window-group .search li { .bottom-window-group .search {
cursor: pointer; display: flex;
line-height: 20px; flex-flow: column;
padding: 0 3px; outline: 0;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bottom-window-group .search li.selected {
background-color: #3875d7;
color: #FFF;
}
.bottom-window-group .search .path {
color: #999;
font-size: 12px;
}
.bottom-window-group .search li.selected .path {
color: #FFF;
} }
/* end bottom-window-group */ /* end bottom-window-group */
/* start footer */ /* start footer */
.footer { .footer {
border-top: 1px solid #919191; box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
background-color: #F0F0F0;
padding-left: 5px; padding-left: 5px;
height: 19px;
line-height: 18px; line-height: 18px;
display: block !important;
} }
.footer .cursor { .footer .cursor {
@ -356,7 +328,7 @@
float: right; float: right;
display: none; display: none;
cursor: pointer; cursor: pointer;
background-color: red; background-color: #9d0000;
color: #FFF; color: #FFF;
margin: 1px 5px; margin: 1px 5px;
padding: 0 2px; padding: 0 2px;

6
static/css/wide.min.css vendored Normal file
View File

@ -0,0 +1,6 @@
.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px;text-decoration:none}.dialog-close-icon:hover{text-decoration:none}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#fff;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px}
::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:solid 0 transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;margin:0;color:#000;overflow:hidden;font-family:Helvetica}ul{padding:0;margin:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}button,input{font-family:Helvetica}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#fff}.list li.selected .ft-small,.list li:hover .ft-small{color:#fff}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?lqk80d);src:url(fonts/icomoon.eot?lqk80d#iefix) format('embedded-opentype'),url(fonts/icomoon.ttf?lqk80d) format('truetype'),url(fonts/icomoon.woff?lqk80d) format('woff'),url(fonts/icomoon.svg?lqk80d#icomoon) format('svg');font-weight:400;font-style:normal}[class*=" ico-"],[class^=ico-]{font-family:icomoon!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-qqz:before{content:"\e900"}.ico-find:before{content:"\e602"}.ico-findfiles:before{content:"\e603"}.ico-editor:before{content:"\e604"}.ico-notification:before{content:"\e607"}.ico-price:before{content:"\e616"}.ico-report:before{content:"\e605"}.ico-git:before{content:"\e624"}.ico-book:before{content:"\e623"}.ico-start:before{content:"\e9d7";text-shadow:0 0 rgba(0,0,0,.4)}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-export:before{content:"\f0ed"}.ico-import:before{content:"\f0ee"}.ico-keyboard:before{content:"\f11c"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-weibo:before{content:"\e621"}.ico-uniE608:before{content:"\e608"}.ico-max:before{content:"\e609"}.ico-remove:before{content:"\e60b"}.ico-buildrun:before{content:"\e60c"}.ico-about:before{content:"\e60d"}.ico-undo:before{content:"\e60e"}.ico-stop:before{content:"\e60f"}.ico-close:before{content:"\e611";text-shadow:0 0 rgba(0,0,0,.4)}.ico-format:before{content:"\e612"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-redo:before{content:"\e615"}.ico-uniE617:before{content:"\e617"}.ico-signout:before{content:"\e618"}.ico-email:before{content:"\e619"}.ico-googleplus:before{content:"\e61a"}.ico-facebook:before{content:"\e61b"}.ico-twitter:before{content:"\e61c"}.ico-info:before{content:"\e61d"}.ico-goline:before{content:"\e61e"}.ico-share:before{content:"\e61f"}.ico-comment:before{content:"\e620"}.ico-github:before{content:"\f00a"}.ico-refresh:before{content:"\f021"}.ico-save:before{content:"\f0c7"}
.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{padding:0 5px;line-height:25px;cursor:pointer}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#fff}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{float:left;line-height:20px;height:20px;padding:0 5px;cursor:pointer}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.menu{display:block!important}.menu>ul>li{float:left}.menu>ul>li>span{font-size:12px;line-height:21px;cursor:pointer;padding:4px 7px}.menu .split{float:left;border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6db14c;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:190px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{position:absolute;left:20%;width:60%;height:70%;overflow:hidden;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);float:left;height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{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:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.footer .cursor{cursor:pointer}.notification-count{float:right;display:none;cursor:pointer;background-color:#9d0000;color:#fff;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px}
.side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0 5px}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px}
#startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#bbb;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#fff}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px;border-bottom:1px solid #919191}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .news li{border-bottom:1px solid #919191}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap}
#dialogAboutDialog .dialog-main{background-color:#fff}#dialogAbout{margin:35px 20px;line-height:28px}#dialogAbout .item{margin:0 10px}#dialogAbout a{color:#4183c4;text-decoration:none}#dialogAbout a:hover{text-decoration:underline}#dialogAbout label{color:#666}#dialogAbout img{width:100px;float:left;margin-right:60px}#dialogAbout .space{margin-bottom:6px;border-bottom:1px solid #919191;padding-bottom:6px}#dialogAbout .thx ul{margin-left:50px}#dialogAbout .thx a{width:80px;display:inline-block}#dialogAbout .license{color:#999;font-size:12px;line-height:normal;height:85px;overflow-x:hidden;word-wrap:break-word}

BIN
static/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/images/hacpai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

95
static/js/bottomGroup.js Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @file bottomGroup.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.1.1, Mar 15, 2017
*/
var bottomGroup = {
tabs: undefined,
searchTab: undefined,
init: function () {
this._initTabs();
this._initFrame();
$('.bottom-window-group .output').click(function () {
$(this).focus();
});
$('.bottom-window-group .output').on('click', '.path', function (event) {
var $path = $(this),
tId = tree.getTIdByPath($path.data("path"));
tree.openFile(tree.fileTree.getNodeByTId(tId),
CodeMirror.Pos($path.data("line") - 1, $path.data("column") - 1));
event.preventDefault();
return false;
});
},
_initFrame: function () {
$(".bottom-window-group .output").parent().mouseup(function (event) {
event.stopPropagation();
if (event.button === 0) { // 左键
$(".bottom-window-group .frame").hide();
return;
}
// event.button === 2 右键
var left = event.screenX,
$it = $(this);
if ($(".side").css("left") === "auto" || $(".side").css("left") === "0px") {
left = event.screenX - $(".side").width();
}
$(".bottom-window-group .frame").show().css({
"left": left + "px",
"top": (event.offsetY + event.target.offsetTop - $it.scrollTop() - 10) + "px"
});
return;
});
},
clear: function (id) {
$('.bottom-window-group .' + id + ' > div').text('');
},
resetOutput: function () {
this.clear('output');
bottomGroup.tabs.setCurrent("output");
windows.flowBottom();
},
_initTabs: function () {
this.tabs = new Tabs({
id: ".bottom-window-group",
clickAfter: function (id) {
this._$tabsPanel.find("." + id).focus();
}
});
},
fillOutput: function (data) {
var $output = $('.bottom-window-group .output');
data = data.replace(/\r/g, '');
data = data.replace(/\n/g, '<br/>');
if (-1 !== data.indexOf("<br/>")) {
data = Autolinker.link(data);
}
$output.find("div").html(data);
$output.parent().scrollTop($output[0].scrollHeight);
}
};

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (C) 2011, Liyuan Li * Copyright (c) 2014-present, b3log.org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -13,11 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* @file dialog.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 1.0.0.1, Dec 8, 2015
*/
(function ($) { (function ($) {
$.fn.extend({ $.fn.extend({
dialog: { dialog: {
version: "0.0.1.7", version: "0.0.1.7",
author: "lly219@gmail.com" author: "v@b3log.org"
} }
}); });
@ -37,7 +44,7 @@
"closeIconHover": "dialog-close-icon-hover", "closeIconHover": "dialog-close-icon-hover",
"title": "dialog-title" "title": "dialog-title"
} }
} };
}; };
$.extend(Dialog.prototype, { $.extend(Dialog.prototype, {
@ -134,22 +141,6 @@
$($("#" + id + "Dialog ." + styleClass.main + " div").get(0)).append(cloneObj); $($("#" + id + "Dialog ." + styleClass.main + " div").get(0)).append(cloneObj);
$(cloneObj).show(); $(cloneObj).show();
// Sets position.
var top = "", left = "",
$dialog = $("#" + id + "Dialog");
if (settings.position) {
top = settings.position.top;
left = settings.position.left;
} else {
// 20(footer) + 23(header)
top = parseInt((windowH - dialogH - 43) / 2);
left = parseInt((windowW - dialogW) / 2);
}
$dialog.css({
"top": top + "px",
"left": left + "px"
});
// Bind event. // Bind event.
$("#" + id + "Dialog ." + styleClass.closeIcon).bind("click", function () { $("#" + id + "Dialog ." + styleClass.closeIcon).bind("click", function () {
$.dialog._close(id, settings); $.dialog._close(id, settings);
@ -174,6 +165,15 @@
$.dialog._close(id, settings); $.dialog._close(id, settings);
} }
}); });
$(window).resize(function () {
var height = $("body").height() > $(window).height() ? $("body").height() : $(window).height();
$(".dialog-background").height(height);
});
if (typeof settings.afterInit === "function") {
settings.afterInit();
}
}, },
_bindMove: function (id, className) { _bindMove: function (id, className) {
$("#" + id + "Dialog ." + className).mousedown(function (event) { $("#" + id + "Dialog ." + className).mousedown(function (event) {
@ -206,12 +206,12 @@
if (positionX > $(window).width() - $(dialog).width()) { if (positionX > $(window).width() - $(dialog).width()) {
positionX = $(window).width() - $(dialog).width(); positionX = $(window).width() - $(dialog).width();
} }
if (positionY < 0) {
positionY = 0;
}
if (positionY > $(window).height() - $(dialog).height()) { if (positionY > $(window).height() - $(dialog).height()) {
positionY = $(window).height() - $(dialog).height(); positionY = $(window).height() - $(dialog).height();
} }
if (positionY < 0) {
positionY = 0;
}
dialog.style.left = positionX + "px"; dialog.style.left = positionX + "px";
dialog.style.top = positionY + "px"; dialog.style.top = positionY + "px";
}; };
@ -227,7 +227,7 @@
_document.ondragstart = null; _document.ondragstart = null;
_document.onselectstart = null; _document.onselectstart = null;
_document.onselect = null; _document.onselect = null;
} };
}); });
}, },
_close: function (id, settings) { _close: function (id, settings) {
@ -251,20 +251,41 @@
_openDialog: function (target, msg) { _openDialog: function (target, msg) {
var inst = this._getInst(target); var inst = this._getInst(target);
var id = inst.id, var id = inst.id,
settings = inst.settings; settings = inst.settings,
top = "", left = "",
$dialog = $("#" + id + "Dialog"),
windowH = $(window).height(),
windowW = $(window).width(),
dialogH = settings.height ? settings.height : parseInt(windowH * 0.6),
dialogW = settings.width ? settings.width : parseInt(windowW * 0.6);
$("#" + id + "Dialog").show(); // Sets position.
if (settings.position) {
top = settings.position.top;
left = settings.position.left;
} else {
// 20(footer) + 23(header)
top = parseInt((windowH - dialogH - 43) / 2);
if (top < 0) {
top = 0;
}
left = parseInt((windowW - dialogW) / 2);
}
$dialog.css({
"top": top + "px",
"left": left + "px"
}).show();
if (settings.modal) { if (settings.modal) {
var styleClass = this._getDefaults($.dialog._defaults, settings, "styleClass"); var styleClass = this._getDefaults($.dialog._defaults, settings, "styleClass");
$("." + styleClass.background).show(); $("." + styleClass.background).show();
} }
$("#" + id + "Dialog .dialog-footer button:eq(0)").focus();
if (typeof settings.afterOpen === "function") { if (typeof settings.afterOpen === "function") {
settings.afterOpen(msg); settings.afterOpen(msg);
} }
$("#" + id + "Dialog .dialog-footer button:eq(0)").focus();
}, },
_updateDialog: function (target, data) { _updateDialog: function (target, data) {
var inst = this._getInst(target); var inst = this._getInst(target);

View File

@ -1,84 +1,224 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @file editor.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.1.0, Jan 12, 2016
*/
var editors = { var editors = {
autocompleteMutex: false,
data: [], data: [],
tabs: {}, tabs: {},
init: function () { getEditorByPath: function (path) {
editors.tabs = new Tabs({ for (var i = 0, ii = editors.data.length; i < ii; i++) {
id: ".edit-panel", if (editors.data[i].editor.options.path === path) {
clickAfter: function (id) { return editors.data[i].editor;
if (id === 'startPage') { }
}
},
close: function () {
$('.edit-panel .tabs > div[data-index="' + $('.edit-panel .frame').data('index') + ']').find('.ico-close').click();
},
closeOther: function () {
var currentIndex = $(".edit-panel .frame").data("index");
// 设置全部关闭标识
var removeData = [];
$(".edit-panel .tabs > div").each(function (i) {
if (currentIndex !== $(this).data("index")) {
removeData.push($(this).data("index"));
}
});
if (removeData.length === 0) {
return false;
}
var firstIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData);
// 开始关闭
$('.edit-panel .tabs > div[data-index="' + firstIndex + '"]').find(".ico-close").click();
},
_removeAllMarker: function () {
var removeData = $("#dialogCloseEditor").data("removeData");
if (removeData && removeData.length > 0) {
var removeIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData);
$('.edit-panel .tabs > div[data-index="' + removeIndex + '"] .ico-close').click();
}
if (wide.curEditor) {
wide.curEditor.focus();
}
},
_initClose: function () {
new ZeroClipboard($("#copyFilePath"));
// 关闭、关闭其他、关闭所有
$(".edit-panel").on("mouseup", '.tabs > div', function (event) {
event.stopPropagation();
if (event.button === 0) { // 左键
$(".edit-panel .frame").hide();
return false; return false;
} }
// set tree node selected // event.button === 2 右键
var node = tree.fileTree.getNodeByTId(id); var left = event.screenX;
tree.fileTree.selectNode(node); if ($(".side").css("left") === "auto" || $(".side").css("left") === "0px") {
wide.curNode = node; left = event.screenX - $(".side").width();
}
$(".edit-panel .frame").show().css({
"left": left + "px",
"top": "21px"
}).data('index', $(this).data("index"));
$("#copyFilePath").attr('data-clipboard-text', $(this).find("span:eq(0)").attr("title"));
return false;
});
},
init: function () {
$("#dialogCloseEditor").dialog({
"modal": true,
"height": 90,
"width": 260,
"title": config.label.tip,
"hideFooter": true,
"afterOpen": function (fileName) {
$("#dialogCloseEditor > div:eq(0)").html(config.label.file
+ ' <b>' + fileName + '</b>. ' + config.label.confirm_save + '?');
$("#dialogCloseEditor button:eq(0)").focus();
},
"afterInit": function () {
$("#dialogCloseEditor button.save").click(function () {
var i = $("#dialogCloseEditor").data("index");
wide.fmt(editors.data[i].id, editors.data[i].editor);
editors.tabs.del(editors.data[i].id);
$("#dialogCloseEditor").dialog("close");
editors._removeAllMarker();
});
$("#dialogCloseEditor button.discard").click(function () {
var i = $("#dialogCloseEditor").data("index");
editors.tabs.del(editors.data[i].id);
$("#dialogCloseEditor").dialog("close");
editors._removeAllMarker();
});
$("#dialogCloseEditor button.cancel").click(function (event) {
$("#dialogCloseEditor").dialog("close");
editors._removeAllMarker();
});
}
});
editors.tabs = new Tabs({
id: ".edit-panel",
setAfter: function () {
if (wide.curEditor) {
wide.curEditor.focus();
}
},
clickAfter: function (id) {
if (id === 'startPage') {
wide.curEditor = undefined;
$(".footer .cursor").text('');
wide.refreshOutline();
return false;
}
},
removeBefore: function (id) {
if (id === 'startPage') { // 当前关闭的 tab 是起始页
editors._removeAllMarker();
return true;
}
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) { if (editors.data[i].id === id) {
wide.curEditor = editors.data[i].editor; if (editors.data[i].editor.doc.isClean()) {
editors._removeAllMarker();
return true;
} else {
$("#dialogCloseEditor").dialog("open", $('.edit-panel .tabs > div[data-index="'
+ editors.data[i].id + '"] > span:eq(0)').text());
$("#dialogCloseEditor").data("index", i);
return false;
}
break; break;
} }
} }
wide.curEditor.focus();
}, },
removeAfter: function (id, nextId) { removeAfter: function (id, nextId) {
if (id === 'startPage') { if ($(".edit-panel .tabs > div").length === 0) {
return false; // 全部 tab 都关闭时才 disables 菜单中“全部关闭”的按钮
menu.disabled(['close-all']);
} }
// 移除编辑器
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) { if (editors.data[i].id === id) {
wide.fmt(tree.fileTree.getNodeByTId(editors.data[i].id).path, editors.data[i].editor);
editors.data.splice(i, 1); editors.data.splice(i, 1);
break; break;
} }
} }
if (!nextId) { if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
// 不存在打开的编辑器 menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']);
// remove selected tree node // remove selected tree node
tree.fileTree.cancelSelectedNode(); tree.fileTree.cancelSelectedNode();
wide.curNode = undefined; wide.curNode = undefined;
wide.curEditor = undefined; wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
return false;
}
menu.disabled(['save-all', 'close-all', 'run', 'go-get', 'go-install']); if (!nextId) {
$(".toolbars").hide(); // 编辑器区域不存在打开的 Tab
// remove selected tree node
tree.fileTree.cancelSelectedNode();
wide.curNode = undefined;
wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
return false; return false;
} }
if (nextId === editors.tabs.getCurrentId()) { if (nextId === editors.tabs.getCurrentId()) {
// 关闭的不是当前编辑器
return false; return false;
} }
// set tree node selected
var node = tree.fileTree.getNodeByTId(nextId);
tree.fileTree.selectNode(node);
wide.curNode = node;
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === nextId) {
wide.curEditor = editors.data[i].editor;
break;
}
}
}
});
$(".edit-panel .tabs").on("dblclick", function () {
if ($(".toolbars .ico-max").length === 1) {
windows.maxEditor();
} else {
windows.restoreEditor();
} }
}); });
this._initCodeMirrorHotKeys(); this._initCodeMirrorHotKeys();
this.openStartPage() this.openStartPage();
this._initClose();
}, },
openStartPage: function () { openStartPage: function () {
wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
var dateFormat = function (time, fmt) { var dateFormat = function (time, fmt) {
var date = new Date(time); var date = new Date(time);
var dateObj = { var dateObj = {
@ -102,12 +242,13 @@ var editors = {
editors.tabs.add({ editors.tabs.add({
id: "startPage", id: "startPage",
title: '<span title="' + config.label.start_page + '">' + config.label.start_page + '</span>', title: '<span title="' + config.label.start_page
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
content: '<div id="startPage"></div>', content: '<div id="startPage"></div>',
after: function () { after: function () {
$("#startPage").load('/start'); $("#startPage").load('/start?sid=' + config.wideSessionId);
$.ajax({ $.ajax({
url: "http://symphony.b3log.org/apis/articles?tags=wide,golang&p=1&size=30", url: "https://ld246.com/apis/articles?tags=wide,golang&p=1&size=20",
type: "GET", type: "GET",
dataType: "jsonp", dataType: "jsonp",
jsonp: "callback", jsonp: "callback",
@ -117,21 +258,22 @@ var editors = {
return; return;
} }
// 按 size = 30 取,但只保留最多 10 // 按 size = 20 取,但只保留最多 9
var length = articles.length; var length = articles.length;
if (length > 10) { if (length > 9) {
length = 10; length = 9;
} }
var listHTML = "<ul><li class='title'>" + config.label.community + "</li>"; var listHTML = "<ul><li class='title'>" + config.label.community +
"<a href='https://ld246.com/article/1437497122181' target='_blank' class='fn-right'>边看边练</li>";
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var article = articles[i]; var article = articles[i];
listHTML += "<li>" listHTML += "<li>"
+ "<a target='_blank' href='http://symphony.b3log.org" + "<a target='_blank' href='"
+ article.articlePermalink + "'>" + article.articlePermalink + "'>"
+ article.articleTitle + "</a>&nbsp; <span class='date'>" + article.articleTitle + "</a>&nbsp; <span class='date'>"
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd hh:mm'); + dateFormat(article.articleCreateTime, 'yyyy-MM-dd');
+"</span></li>" +"</span></li>";
} }
$("#startPage .news").html(listHTML + "</ul>"); $("#startPage .news").html(listHTML + "</ul>");
@ -141,11 +283,12 @@ var editors = {
}); });
}, },
getCurrentId: function () { getCurrentId: function () {
var currentId = editors.tabs.getCurrentId(); var ret = editors.tabs.getCurrentId();
if (currentId === 'startPage') { if (ret === 'startPage') {
currentId = null; ret = null;
} }
return currentId;
return ret;
}, },
getCurrentPath: function () { getCurrentPath: function () {
var currentPath = $(".edit-panel .tabs .current span:eq(0)").attr("title"); var currentPath = $(".edit-panel .tabs .current span:eq(0)").attr("title");
@ -156,6 +299,7 @@ var editors = {
}, },
_initCodeMirrorHotKeys: function () { _initCodeMirrorHotKeys: function () {
CodeMirror.registerHelper("hint", "go", function (editor) { CodeMirror.registerHelper("hint", "go", function (editor) {
editor = wide.curEditor; // 使用当前编辑器覆盖实参,因为异步调用的原因,实参不一定正确
var word = /[\w$]+/; var word = /[\w$]+/;
var cur = editor.getCursor(), curLine = editor.getLine(cur.line); var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
@ -176,6 +320,12 @@ var editors = {
var autocompleteHints = []; var autocompleteHints = [];
if (editors.autocompleteMutex && editor.state.completionActive) {
return;
}
editors.autocompleteMutex = true;
$.ajax({ $.ajax({
async: false, // 同步执行 async: false, // 同步执行
type: 'POST', type: 'POST',
@ -187,43 +337,72 @@ var editors = {
if (autocompleteArray) { if (autocompleteArray) {
for (var i = 0; i < autocompleteArray.length; i++) { for (var i = 0; i < autocompleteArray.length; i++) {
var displayText = ''; var displayText = '',
text = autocompleteArray[i].name;
switch (autocompleteArray[i].class) { switch (autocompleteArray[i].class) {
case "type": case "type":
case "const": displayText = '<span class="fn-clear"><span class="ico-type ico"></span>'// + autocompleteArray[i].class
case "var": + '<b>' + autocompleteArray[i].name + '</b> '
case "package": + autocompleteArray[i].type + '</span>';
displayText = '<span class="fn-clear">'// + autocompleteArray[i].class break;
+ '<b class="fn-left">' + autocompleteArray[i].name + '</b> ' case "const":
displayText = '<span class="fn-clear"><span class="ico-const ico"></span>'// + autocompleteArray[i].class
+ '<b>' + autocompleteArray[i].name + '</b> '
+ autocompleteArray[i].type + '</span>';
break;
case "var":
displayText = '<span class="fn-clear"><span class="ico-var ico"></span>'// + autocompleteArray[i].class
+ '<b>' + autocompleteArray[i].name + '</b> '
+ autocompleteArray[i].type + '</span>';
break;
case "package":
displayText = '<span class="fn-clear"><span class="ico-package ico"></span>'// + autocompleteArray[i].class
+ '<b>' + autocompleteArray[i].name + '</b> '
+ autocompleteArray[i].type + '</span>'; + autocompleteArray[i].type + '</span>';
break; break;
case "func": case "func":
displayText = '<span>'// + autocompleteArray[i].class displayText = '<span><span class="ico-func ico"></span>'// + autocompleteArray[i].class
+ '<b>' + autocompleteArray[i].name + '</b>' + '<b>' + autocompleteArray[i].name + '</b>'
+ autocompleteArray[i].type.substring(4) + '</span>'; + autocompleteArray[i].type.substring(4) + '</span>';
text += '()';
break; break;
default: default:
console.warn("Can't handle autocomplete [" + autocompleteArray[i].class + "]"); console.warn("Can't handle autocomplete [" + autocompleteArray[i].class + "]");
break; break;
} }
autocompleteHints[i] = { autocompleteHints[i] = {
displayText: displayText, displayText: displayText,
text: autocompleteArray[i].name text: text
}; };
} }
} }
editor.doc.markClean();
$(".edit-panel .tabs .current > span:eq(0)").removeClass("changed");
} }
}); });
setTimeout(function () {
editors.autocompleteMutex = false;
}, 20);
return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
}); });
CodeMirror.commands.autocompleteAfterDot = function (cm) { CodeMirror.commands.autocompleteAfterDot = function (cm) {
var mode = cm.getMode();
if (mode && "go" !== mode.name) {
return CodeMirror.Pass;
}
var token = cm.getTokenAt(cm.getCursor());
if ("comment" === token.type || "string" === token.type) {
return CodeMirror.Pass;
}
setTimeout(function () { setTimeout(function () {
if (!cm.state.completionActive) { if (!cm.state.completionActive) {
cm.showHint({hint: CodeMirror.hint.go, completeSingle: false}); cm.showHint({hint: CodeMirror.hint.go, completeSingle: false});
@ -259,18 +438,140 @@ var editors = {
url: '/exprinfo', url: '/exprinfo',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (result) {
if (!data.succ) { if (0 != result.code) {
return; return;
} }
var position = wide.curEditor.cursorCoords(); var position = wide.curEditor.cursorCoords();
$("body").append('<div style="top:' $("body").append('<div style="top:'
+ (position.top + 15) + 'px;left:' + position.left + (position.top + 15) + 'px;left:' + position.left
+ 'px" class="edit-exprinfo">' + data.info + '</div>'); + 'px" class="edit-exprinfo">' + result.data + '</div>');
} }
}); });
}; };
CodeMirror.commands.copyLinesDown = function (cm) {
var content = '',
selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
for (var i = from.line, max = to.line; i <= max; i++) {
if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
var offset = replaceToLine - from.line + 1;
cm.setSelection(CodeMirror.Pos(from.line + offset, from.ch),
CodeMirror.Pos(to.line + offset, to.ch));
};
CodeMirror.commands.copyLinesUp = function (cm) {
var content = '',
selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
for (var i = from.line, max = to.line; i <= max; i++) {
if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
cm.setSelection(CodeMirror.Pos(from.line, from.ch),
CodeMirror.Pos(to.line, to.ch));
};
CodeMirror.commands.moveLinesUp = function (cm) {
var selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
if (from.line === 0) {
return false;
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange('\n' + cm.getLine(from.line - 1), CodeMirror.Pos(replaceToLine));
if (from.line === 1) {
// 移除第一行的换行
cm.replaceRange('', CodeMirror.Pos(0, 0),
CodeMirror.Pos(1, 0));
} else {
cm.replaceRange('', CodeMirror.Pos(from.line - 2, cm.getLine(from.line - 2).length),
CodeMirror.Pos(from.line - 1, cm.getLine(from.line - 1).length));
}
cm.setSelection(CodeMirror.Pos(from.line - 1, from.ch),
CodeMirror.Pos(to.line - 1, to.ch));
};
CodeMirror.commands.moveLinesDown = function (cm) {
var selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
if (to.line === cm.lastLine()) {
return false;
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
// 把选中的下一行添加到选中区域的上一行
if (from.line === 0) {
cm.replaceRange(cm.getLine(replaceToLine + 1) + '\n', CodeMirror.Pos(0, 0));
} else {
cm.replaceRange('\n' + cm.getLine(replaceToLine + 1), CodeMirror.Pos(from.line - 1));
}
// 删除选中的下一行
cm.replaceRange('', CodeMirror.Pos(replaceToLine + 1, cm.getLine(replaceToLine + 1).length),
CodeMirror.Pos(replaceToLine + 2, cm.getLine(replaceToLine + 2).length));
cm.setSelection(CodeMirror.Pos(from.line + 1, from.ch),
CodeMirror.Pos(to.line + 1, to.ch));
};
CodeMirror.commands.jumpToDecl = function (cm) { CodeMirror.commands.jumpToDecl = function (cm) {
var cur = wide.curEditor.getCursor(); var cur = wide.curEditor.getCursor();
@ -285,38 +586,18 @@ var editors = {
url: '/find/decl', url: '/find/decl',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (result) {
if (!data.succ) { if (0 != result.code) {
return; return;
} }
var cursorLine = data.cursorLine; var data = result.data;
var cursorCh = data.cursorCh;
var request = newWideRequest();
request.path = data.path;
$.ajax({
type: 'POST',
url: '/file',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogAlert").dialog("open", data.msg);
return false;
}
var tId = tree.getTIdByPath(data.path); var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId); wide.curNode = tree.fileTree.getNodeByTId(tId);
tree.fileTree.selectNode(wide.curNode); tree.fileTree.selectNode(wide.curNode);
data.cursorLine = cursorLine; tree.openFile(wide.curNode, CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1));
data.cursorCh = cursorCh;
editors.newEditor(data);
}
});
} }
}); });
}; };
@ -335,32 +616,52 @@ var editors = {
url: '/find/usages', url: '/find/usages',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (result) {
if (!data.succ) { if (0 != result.code) {
return; return;
} }
editors.appendSearch(data.founds, 'usages', ''); editors.appendSearch(result.data, 'usages', '');
} }
}); });
}; };
CodeMirror.commands.selectIdentifier = function (cm) {
var cur = cm.getCursor();
var word = cm.findWordAt(cur);
cm.extendSelection(word.anchor, word.head);
};
}, },
appendSearch: function (data, type, key) { appendSearch: function (data, type, key) {
var searcHTML = '<ul>'; var searcHTML = '<ul class="list">',
key = key.toLowerCase();
for (var i = 0, ii = data.length; i < ii; i++) { for (var i = 0, ii = data.length; i < ii; i++) {
var contents = data[i].contents[0], var contents = '',
index = contents.indexOf(key); lowerCaseContents = data[i].contents[0].toLowerCase(),
contents = contents.substring(0, index) matches = lowerCaseContents.split(key),
+ '<b>' + key + '</b>' startIndex = 0,
+ contents.substring(index + key.length); endIndex = 0;
for (var j = 0, max = matches.length; j < max; j++) {
startIndex = endIndex + matches[j].length;
endIndex = startIndex + key.length;
var keyWord = data[i].contents[0].substring(startIndex, endIndex);
if (keyWord !== '') {
keyWord = '<b>' + keyWord + '</b>';
}
contents += data[i].contents[0].substring(startIndex - matches[j].length, startIndex) + keyWord;
}
searcHTML += '<li title="' + data[i].path + '">' searcHTML += '<li title="' + data[i].path + '">'
+ contents + "&nbsp;&nbsp;&nbsp;&nbsp;<span class='path'>" + data[i].path + contents + "&nbsp;&nbsp;&nbsp;&nbsp;<span class='ft-small'>" + data[i].path
+ '<i class="position" data-line="' + '<i class="position" data-line="'
+ data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':' + data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':'
+ data[i].ch + ')</i></span></li>'; + data[i].ch + ')</i></span></li>';
} }
if (data.length === 0) {
searcHTML += '<li>' + config.label.search_no_match + '</li>';
}
searcHTML += '</ul>'; searcHTML += '</ul>';
var $search = $('.bottom-window-group .search'), var $search = $('.bottom-window-group .search'),
@ -369,7 +670,7 @@ var editors = {
title = config.label.search_text; title = config.label.search_text;
} }
if ($search.find("ul").length === 0) { if ($search.find("ul").length === 0) {
wide.searchTab = new Tabs({ bottomGroup.searchTab = new Tabs({
id: ".bottom-window-group .search", id: ".bottom-window-group .search",
removeAfter: function (id, prevId) { removeAfter: function (id, prevId) {
if ($search.find("ul").length === 1) { if ($search.find("ul").length === 1) {
@ -389,8 +690,17 @@ var editors = {
tree.openFile(tree.fileTree.getNodeByTId(tId)); tree.openFile(tree.fileTree.getNodeByTId(tId));
tree.fileTree.selectNode(wide.curNode); tree.fileTree.selectNode(wide.curNode);
var cursor = CodeMirror.Pos($it.find(".position").data("line") - 1, $it.find(".position").data("ch") - 1); var line = $it.find(".position").data("line") - 1;
wide.curEditor.setCursor(cursor); var cursor = CodeMirror.Pos(line, $it.find(".position").data("ch") - 1);
var editor = wide.curEditor;
editor.setCursor(cursor);
var half = Math.floor(editor.getScrollInfo().clientHeight / editor.defaultTextHeight() / 2);
var cursorCoords = editor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
editor.scrollTo(0, cursorCoords.top);
wide.curEditor.focus(); wide.curEditor.focus();
}); });
@ -399,7 +709,7 @@ var editors = {
$search.find(".tabs .first").text(title); $search.find(".tabs .first").text(title);
} else { } else {
$search.find(".tabs").show(); $search.find(".tabs").show();
wide.searchTab.add({ bottomGroup.searchTab.add({
"id": "search" + (new Date()).getTime(), "id": "search" + (new Date()).getTime(),
"title": title, "title": title,
"content": searcHTML "content": searcHTML
@ -407,31 +717,13 @@ var editors = {
} }
// focus // focus
wide.bottomWindowTab.setCurrent("search"); bottomGroup.tabs.setCurrent("search");
windows.flowBottom(); windows.flowBottom();
$(".bottom-window-group .search").focus(); $(".bottom-window-group .search").focus();
}, },
// 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab. // 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab.
newEditor: function (data) { newEditor: function (data, cursor) {
$(".toolbars").show(); var id = wide.curNode.id;
var id = wide.curNode.tId;
// 光标位置
var cursor = CodeMirror.Pos(0, 0);
if (data.cursorLine && data.cursorCh) {
cursor = CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1);
}
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) {
editors.tabs.setCurrent(id);
wide.curEditor = editors.data[i].editor;
wide.curEditor.setCursor(cursor);
wide.curEditor.focus();
return false;
}
}
editors.tabs.add({ editors.tabs.add({
id: id, id: id,
@ -440,25 +732,35 @@ var editors = {
content: '<textarea id="editor' + id + '"></textarea>' content: '<textarea id="editor' + id + '"></textarea>'
}); });
menu.undisabled(['save-all', 'close-all', 'run', 'go-get', 'go-install']); menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']);
var rulers = []; var textArea = document.getElementById("editor" + id);
rulers.push({color: "#ccc", column: 120, lineStyle: "dashed"}); textArea.value = data.content;
var editor = CodeMirror.fromTextArea(document.getElementById("editor" + id), { var editor = CodeMirror.fromTextArea(textArea, {
lineNumbers: true, lineNumbers: true,
autofocus: true, autofocus: true,
autoCloseBrackets: true, autoCloseBrackets: true,
matchBrackets: true, matchBrackets: true,
highlightSelectionMatches: {showToken: /\w/}, highlightSelectionMatches: {showToken: /\w/},
rulers: rulers, rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
styleActiveLine: true, styleActiveLine: true,
theme: 'wide', theme: config.editorTheme,
tabSize: config.editorTabSize,
indentUnit: 4, indentUnit: 4,
indentWithTabs: true,
foldGutter: true, foldGutter: true,
cursorHeight: 1,
path: data.path,
readOnly: wide.curNode.isGOAPI,
profile: 'xhtml', // define Emmet output profile
extraKeys: { extraKeys: {
"Ctrl-\\": "autocompleteAnyWord", "Ctrl-\\": "autocompleteAnyWord",
".": "autocompleteAfterDot", ".": "autocompleteAfterDot",
"Ctrl-/": 'toggleComment',
"Ctrl-I": "exprInfo", "Ctrl-I": "exprInfo",
"Ctrl-L": "gotoLine", "Ctrl-L": "gotoLine",
"Ctrl-E": "deleteLine", "Ctrl-E": "deleteLine",
@ -468,7 +770,7 @@ var editors = {
wide.saveFile(); wide.saveFile();
}, },
"Shift-Ctrl-S": function () { "Shift-Ctrl-S": function () {
wide.saveAllFiles(); menu.saveAllFiles();
}, },
"Shift-Alt-F": function () { "Shift-Alt-F": function () {
var currentPath = editors.getCurrentPath(); var currentPath = editors.getCurrentPath();
@ -485,30 +787,89 @@ var editors = {
windows.maxEditor(); windows.maxEditor();
} }
}, },
"Shift-Ctrl-Up": "copyLinesUp",
"Shift-Ctrl-Down": "copyLinesDown",
"Shift-Alt-Up": "moveLinesUp",
"Shift-Alt-Down": "moveLinesDown",
"Shift-Alt-J": "selectIdentifier"
} }
}); });
if ("text/html" === data.mode) {
emmetCodeMirror(editor);
}
editor.on('cursorActivity', function (cm) { editor.on('cursorActivity', function (cm) {
$(".edit-exprinfo").remove(); $(".edit-exprinfo").remove();
var cursor = cm.getCursor(); var cursor = cm.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |'); $(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
// TODO: 关闭 tab 的时候要重置
});
editor.on('focus', function (cm) {
windows.clearFloat();
}); });
editor.on('blur', function (cm) { editor.on('blur', function (cm) {
$(".edit-exprinfo").remove(); $(".edit-exprinfo").remove();
}); });
editor.on('changes', function (cm) {
if (cm.doc.isClean()) { // no modification
$(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) {
$span.removeClass("changed");
}
});
return;
}
// changed
$(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) {
$span.addClass("changed");
}
});
});
editor.on('keydown', function (cm, evt) {
if (evt.altKey || evt.ctrlKey || evt.shiftKey) {
return;
}
var k = evt.which;
if (k < 48) {
return;
}
// hit [0-9]
if (k > 57 && k < 65) {
return;
}
// hit [a-z]
if (k > 90) {
return;
}
if (config.autocomplete) {
if (0.5 <= Math.random()) {
CodeMirror.commands.autocompleteAfterDot(cm);
}
}
});
editor.setSize('100%', $(".edit-panel").height() - $(".edit-panel .tabs").height()); editor.setSize('100%', $(".edit-panel").height() - $(".edit-panel .tabs").height());
editor.setValue(data.content);
editor.setOption("mode", data.mode); editor.setOption("mode", data.mode);
editor.setOption("gutters", ["CodeMirror-lint-markers", "CodeMirror-foldgutter"]); editor.setOption("gutters", ["CodeMirror-lint-markers", "CodeMirror-foldgutter"]);
if ("wide" !== config.keymap) {
editor.setOption("keyMap", config.keymap);
}
if ("text/x-go" === data.mode || "application/json" === data.mode) { if ("text/x-go" === data.mode || "application/json" === data.mode) {
editor.setOption("lint", true); editor.setOption("lint", true);
} }
@ -517,12 +878,19 @@ var editors = {
editor.setOption("autoCloseTags", true); editor.setOption("autoCloseTags", true);
} }
editor.setCursor(cursor);
wide.curEditor = editor; wide.curEditor = editor;
editors.data.push({ editors.data.push({
"editor": editor, "editor": editor,
"id": id "id": id
}); });
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
var half = Math.floor(wide.curEditor.getScrollInfo().clientHeight / wide.curEditor.defaultTextHeight() / 2);
var cursorCoords = wide.curEditor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
wide.curEditor.scrollTo(0, cursorCoords.top);
editor.setCursor(cursor);
editor.focus();
} }
}; };

View File

@ -1,62 +1,154 @@
/*
* Copyright (c) 2014-present, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @file hotkeys.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.2, Dec 15, 2015
*/
var hotkeys = { var hotkeys = {
defaultKeyMap: { defaultKeyMap: {
// Ctrl+0 焦点切换到当前编辑器 // Ctrl-0
goEditor: { goEditor: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 48 which: 48,
fun: function () {
if (wide.curEditor) {
wide.curEditor.focus();
}
}
}, },
// Ctrl+1 焦点切换到文件树 // Ctrl-1
goFileTree: { goFileTree: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 49 which: 49,
fun: function () {
// 有些元素需设置 tabindex 为 -1 时才可以 focus
if (windows.outerLayout.west.state.isClosed) {
windows.outerLayout.slideOpen('west');
}
$("#files").focus();
}
}, },
// Ctrl+4 焦点切换到输出窗口 // Ctrl-2
goOutPut: { goOutline: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 52 which: 50,
fun: function () {
if (windows.innerLayout.east.state.isClosed) {
windows.innerLayout.slideOpen('east');
}
$("#outline").focus();
}
}, },
// Ctrl+5 焦点切换到搜索窗口 // Ctrl-4
goOutput: {
ctrlKey: true,
altKey: false,
shiftKey: false,
which: 52,
fun: function () {
bottomGroup.tabs.setCurrent("output");
windows.flowBottom();
$(".bottom-window-group .output").focus();
}
},
// Ctrl-5
goSearch: { goSearch: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 53 which: 53,
fun: function () {
bottomGroup.tabs.setCurrent("search");
windows.flowBottom();
$(".bottom-window-group .search").focus();
}
}, },
// Ctrl+6 焦点切换到通知窗口 // Ctrl-6
goNotification: { goNotification: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 54 which: 54,
fun: function () {
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
}
}, },
// Ctrl+D 窗口组切换 // Alt-C
clearWindow: {
ctrlKey: false,
altKey: true,
shiftKey: false,
which: 67
},
// Ctrl-D 窗口组切换
changeEditor: { changeEditor: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 68 which: 68
}, },
// Ctrl+F 搜索 // Ctrl-F search
search: { search: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 70 which: 70
}, },
// Ctrl+Q 关闭当前编辑器 // Ctrl-Q close current editor
closeCurEditor: { closeCurEditor: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
which: 81 which: 81
}, },
// F6 构建并运行 // Ctrl-R
rename: {
ctrlKey: true,
altKey: false,
shiftKey: false,
which: 82
},
// Shift-Alt-O 跳转到文件
goFile: {
ctrlKey: false,
altKey: true,
shiftKey: true,
which: 79
},
// F5 Build
build: {
ctrlKey: false,
altKey: false,
shiftKey: false,
which: 116
},
// F6 Build & Run
buildRun: { buildRun: {
ctrlKey: false, ctrlKey: false,
altKey: false, altKey: false,
@ -64,56 +156,132 @@ var hotkeys = {
which: 117 which: 117
} }
}, },
bindList: function ($source, $list, enterFun) {
$list.data("index", 0);
$source.keydown(function (event) {
var index = $list.data("index"),
count = $list.find("li").length;
if (count === 0) {
return true;
}
if (event.which === 38) { // up
index--;
if (index < 0) {
index = count - 1;
}
}
if (event.which === 40) { // down
index++;
if (index > count - 1) {
index = 0;
}
}
var $selected = $list.find("li:eq(" + index + ")");
if (event.which === 13) { // enter
enterFun($selected);
}
$list.find("li").removeClass("selected");
$list.data("index", index);
$selected.addClass("selected");
if (index === 0) {
$list.scrollTop(0);
} else {
if ($selected[0].offsetTop + $list.scrollTop() > $list.height()) {
if (event.which === 40) {
$list.scrollTop($list.scrollTop() + $selected.height());
} else {
$list.scrollTop($selected[0].offsetTop);
}
} else {
$list.scrollTop(0);
}
}
// 阻止上下键改变光标位置
if (event.which === 38 || event.which === 40 || event.which === 13) {
return false;
}
});
},
_bindOutput: function () {
$(".bottom-window-group .output").keydown(function (event) {
var hotKeys = hotkeys.defaultKeyMap;
if (event.altKey === hotKeys.clearWindow.altKey
&& event.which === hotKeys.clearWindow.which) { // Alt-C clear output
bottomGroup.clear('output');
event.preventDefault();
return;
}
});
},
_bindFileTree: function () { _bindFileTree: function () {
$("#files").keydown(function (event) { $("#files").keydown(function (event) {
event.preventDefault(); event.preventDefault();
var hotKeys = hotkeys.defaultKeyMap; var hotKeys = hotkeys.defaultKeyMap;
if (event.ctrlKey === hotKeys.search.ctrlKey if (event.ctrlKey === hotKeys.search.ctrlKey
&& event.which === hotKeys.search.which) { // Ctrl+F 搜索 && event.which === hotKeys.search.which) { // Ctrl-F 搜索
$("#dialogSearchForm").dialog("open"); $("#dialogSearchForm").dialog("open");
return; return;
} }
if (event.ctrlKey === hotKeys.rename.ctrlKey
&& event.which === hotKeys.rename.which) { // Ctrl-R 重命名
if (wide.curNode.removable) {
$("#dialogRenamePrompt").dialog("open");
}
return;
}
switch (event.which) { switch (event.which) {
case 46: // 删除 case 46: // delete
tree.removeIt(); tree.removeIt();
break; break;
case 13: // 回车 case 13: // enter
if (!wide.curNode) { if (!wide.curNode) {
return false; return false;
} }
if (wide.curNode.iconSkin === "ico-ztree-dir ") { // 选中节点是目录 if (tree.isDir()) {
// 不做任何处理 if (wide.curNode.open) {
return false; return false;
} }
// 模拟点击:打开文件 tree.fileTree.expandNode(wide.curNode, true, false, true);
$("#files").focus();
break;
}
tree.openFile(wide.curNode); tree.openFile(wide.curNode);
break; break;
case 38: // 上 case 38: // up
var node = {}; var node = {};
if (!wide.curNode) { // 没有选中节点时,默认选中第一个 if (!wide.curNode) { // select the first one if no node been selected
node = tree.fileTree.getNodeByTId("files_1"); node = tree.fileTree.getNodeByTId("files_1");
} else { } else {
if (wide.curNode && wide.curNode.isFirstNode && wide.curNode.level === 0) { if (wide.curNode && wide.curNode.isFirstNode && wide.curNode.level === 0) {
// 当前节点为顶部第一个节点
return false; return false;
} }
node = wide.curNode.getPreNode(); node = wide.curNode.getPreNode();
if (wide.curNode.isFirstNode && wide.curNode.getParentNode()) { if (wide.curNode.isFirstNode && wide.curNode.getParentNode()) {
// 当前节点为第一个节点且有父亲
node = wide.curNode.getParentNode(); node = wide.curNode.getParentNode();
} }
var preNode = wide.curNode.getPreNode(); var preNode = wide.curNode.getPreNode();
if (preNode && preNode.iconSkin === "ico-ztree-dir " if (preNode && tree.isDir() && preNode.open) {
&& preNode.open) {
// 当前节点的上一个节点是目录且打开时,获取打开节点中的最后一个节点
node = tree.getCurrentNodeLastNode(preNode); node = tree.getCurrentNodeLastNode(preNode);
} }
} }
@ -122,27 +290,23 @@ var hotkeys = {
tree.fileTree.selectNode(node); tree.fileTree.selectNode(node);
$("#files").focus(); $("#files").focus();
break; break;
case 40: // case 40: // down
var node = {}; var node = {};
if (!wide.curNode) { // 没有选中节点时,默认选中第一个 if (!wide.curNode) { // select the first one if no node been selected
node = tree.fileTree.getNodeByTId("files_1"); node = tree.fileTree.getNodeByTId("files_1");
} else { } else {
if (wide.curNode && tree.isBottomNode(wide.curNode)) { if (wide.curNode && tree.isBottomNode(wide.curNode)) {
// 当前节点为最底部的节点
return false; return false;
} }
node = wide.curNode.getNextNode(); node = wide.curNode.getNextNode();
if (wide.curNode.iconSkin === "ico-ztree-dir " && wide.curNode.open) { if (tree.isDir() && wide.curNode.open) {
// 当前节点是目录且打开时
node = wide.curNode.children[0]; node = wide.curNode.children[0];
} }
var nextShowNode = tree.getNextShowNode(wide.curNode); var nextShowNode = tree.getNextShowNode(wide.curNode);
if (wide.curNode.isLastNode && wide.curNode.level !== 0 && !wide.curNode.open if (wide.curNode.isLastNode && wide.curNode.level !== 0 && !wide.curNode.open && nextShowNode) {
&& nextShowNode) {
// 当前节点为最后一个叶子节点,但其父或祖先节点还有下一个节点
node = nextShowNode; node = nextShowNode;
} }
} }
@ -154,7 +318,7 @@ var hotkeys = {
$("#files").focus(); $("#files").focus();
break; break;
case 37: // case 37: // left
if (!wide.curNode) { if (!wide.curNode) {
wide.curNode = tree.fileTree.getNodeByTId("files_1"); wide.curNode = tree.fileTree.getNodeByTId("files_1");
tree.fileTree.selectNode(wide.curNode); tree.fileTree.selectNode(wide.curNode);
@ -162,14 +326,14 @@ var hotkeys = {
return false; return false;
} }
if (wide.curNode.iconSkin !== "ico-ztree-dir " || !wide.curNode.open) { if (!tree.isDir() || !wide.curNode.open) {
return false; return false;
} }
tree.fileTree.expandNode(wide.curNode, false, false, true); tree.fileTree.expandNode(wide.curNode, false, false, true);
$("#files").focus(); $("#files").focus();
break; break;
case 39: // case 39: // right
if (!wide.curNode) { if (!wide.curNode) {
wide.curNode = tree.fileTree.getNodeByTId("files_1"); wide.curNode = tree.fileTree.getNodeByTId("files_1");
tree.fileTree.selectNode(wide.curNode); tree.fileTree.selectNode(wide.curNode);
@ -177,114 +341,104 @@ var hotkeys = {
return false; return false;
} }
if (wide.curNode.iconSkin !== "ico-ztree-dir " || wide.curNode.open) { if (!tree.isDir() || wide.curNode.open) {
return false; return false;
} }
tree.fileTree.expandNode(wide.curNode, true, false, true); tree.fileTree.expandNode(wide.curNode, true, false, true);
$("#files").focus(); $("#files").focus();
break;
case 116: // F5
if (!wide.curNode || !tree.isDir()) {
return false;
}
tree.refresh(wide.curNode);
break; break;
} }
}); });
}, },
init: function () { _bindDocument: function () {
this._bindFileTree();
var hotKeys = this.defaultKeyMap; var hotKeys = this.defaultKeyMap;
$(document).keydown(function (event) { $(document).keydown(function (event) {
if (event.ctrlKey === hotKeys.goEditor.ctrlKey if (event.ctrlKey === hotKeys.goEditor.ctrlKey
&& event.which === hotKeys.goEditor.which) { // Ctrl+0 焦点切换到当前编辑器 && event.which === hotKeys.goEditor.which) { // Ctrl-0 焦点切换到当前编辑器
if (wide.curEditor) { hotKeys.goEditor.fun();
wide.curEditor.focus();
}
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.goFileTree.ctrlKey if (event.ctrlKey === hotKeys.goFileTree.ctrlKey
&& event.which === hotKeys.goFileTree.which) { // Ctrl+1 焦点切换到文件树 && event.which === hotKeys.goFileTree.which) { // Ctrl-1 焦点切换到文件树
// 有些元素需设置 tabindex 为 -1 时才可以 focus hotKeys.goFileTree.fun();
if ($(".footer .ico-restore:eq(0)").css("display") === "inline") {
// 当文件树最小化时
$(".side").css({
"left": "0"
});
if ($(".footer .ico-restore:eq(1)").css("display") === "inline") {
// 当底部最小化时
$(".bottom-window-group").css("top", "100%").hide();
}
}
$("#files").focus();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.goOutPut.ctrlKey if (event.ctrlKey === hotKeys.goOutline.ctrlKey
&& event.which === hotKeys.goOutPut.which) { // Ctrl+4 焦点切换到输出窗口 && event.which === hotKeys.goOutline.which) { // Ctrl-2 焦点切换到大纲
wide.bottomWindowTab.setCurrent("output"); hotKeys.goOutline.fun();
windows.flowBottom();
$(".bottom-window-group .output").focus();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.goOutput.ctrlKey
&& event.which === hotKeys.goOutput.which) { // Ctrl-4 焦点切换到输出窗口
hotKeys.goOutput.fun();
event.preventDefault();
return;
}
if (event.ctrlKey === hotKeys.goSearch.ctrlKey if (event.ctrlKey === hotKeys.goSearch.ctrlKey
&& event.which === hotKeys.goSearch.which) { // Ctrl+5 焦点切换到搜索窗口 && event.which === hotKeys.goSearch.which) { // Ctrl-5 焦点切换到搜索窗口
wide.bottomWindowTab.setCurrent("search"); hotKeys.goSearch.fun();
windows.flowBottom();
$(".bottom-window-group .search").focus();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.goNotification.ctrlKey if (event.ctrlKey === hotKeys.goNotification.ctrlKey
&& event.which === hotKeys.goNotification.which) { // Ctrl+6 焦点切换到通知窗口 && event.which === hotKeys.goNotification.which) { // Ctrl-6 焦点切换到通知窗口
wide.bottomWindowTab.setCurrent("notification"); hotKeys.goNotification.fun();
windows.flowBottom();
$(".bottom-window-group .notification").focus();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.closeCurEditor.ctrlKey if (event.ctrlKey === hotKeys.closeCurEditor.ctrlKey
&& event.which === hotKeys.closeCurEditor.which) { // Ctrl+Q 关闭当前编辑器 && event.which === hotKeys.closeCurEditor.which) { // Ctrl-Q 关闭当前编辑器
var currentId = editors.getCurrentId(); $(".edit-panel .tabs > div.current").find(".ico-close").click();
if (currentId) {
editors.tabs.del(currentId);
}
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.ctrlKey === hotKeys.changeEditor.ctrlKey if (event.ctrlKey === hotKeys.changeEditor.ctrlKey
&& event.which === hotKeys.changeEditor.which) { // Ctrl+D 窗口组切换 && event.which === hotKeys.changeEditor.which) { // Ctrl-D 窗口组切换
if (document.activeElement.className === "notification" if (document.activeElement.className === "notification"
|| document.activeElement.className === "output" || document.activeElement.className === "output"
|| document.activeElement.className === "search") { || document.activeElement.className === "search") {
// 焦点在底部窗口组时,对底部进行切换 // 焦点在底部窗口组时,对底部进行切换
var tabs = ["output", "search", "notification"], var tabs = ["output", "search", "notification"],
nextId = ""; nextPath = "";
for (var i = 0, ii = tabs.length; i < ii; i++) { for (var i = 0, ii = tabs.length; i < ii; i++) {
if (document.activeElement.className === tabs[i]) { if (bottomGroup.tabs.getCurrentId() === tabs[i]) {
if (i < ii - 1) { if (i < ii - 1) {
nextId = tabs[i + 1]; nextPath = tabs[i + 1];
} else { } else {
nextId = tabs[0]; nextPath = tabs[0];
} }
break; break;
} }
} }
wide.bottomWindowTab.setCurrent(nextId); bottomGroup.tabs.setCurrent(nextPath);
$(".bottom-window-group ." + nextId).focus(); $(".bottom-window-group ." + nextPath).focus();
event.preventDefault(); event.preventDefault();
@ -292,16 +446,16 @@ var hotkeys = {
} }
if (editors.data.length > 1) { if (editors.data.length > 1) {
var nextId = ""; var nextPath = "";
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
var currentId = editors.getCurrentId(); var currentId = editors.getCurrentId();
if (currentId) { if (currentId) {
if (currentId === editors.data[i].id) { if (currentId === editors.data[i].id) {
if (i < ii - 1) { if (i < ii - 1) {
nextId = editors.data[i + 1].id; nextPath = editors.data[i + 1].id;
wide.curEditor = editors.data[i + 1].editor; wide.curEditor = editors.data[i + 1].editor;
} else { } else {
nextId = editors.data[0].id; nextPath = editors.data[0].id;
wide.curEditor = editors.data[0].editor; wide.curEditor = editors.data[0].editor;
} }
break; break;
@ -309,10 +463,14 @@ var hotkeys = {
} }
} }
editors.tabs.setCurrent(nextId); editors.tabs.setCurrent(nextPath);
wide.curNode = tree.fileTree.getNodeByTId(nextId); var nextTId = tree.getTIdByPath(nextPath);
tree.fileTree.selectNode(wide.curNode); wide.curNode = tree.fileTree.getNodeByTId(nextTId);
tree.fileTree.selectNode(wide.curNode);
wide.refreshOutline();
var cursor = wide.curEditor.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
wide.curEditor.focus(); wide.curEditor.focus();
} }
@ -321,12 +479,31 @@ var hotkeys = {
return false; return false;
} }
if (event.which === hotKeys.buildRun.which) { // F6 构建并运行 if (event.which === hotKeys.build.which) { // F5 Build
wide.run(); menu.build();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.which === hotKeys.buildRun.which) { // F6 Build & Run
menu.run();
event.preventDefault();
return;
}
if (event.ctrlKey === hotKeys.goFile.ctrlKey
&& event.altKey === hotKeys.goFile.altKey
&& event.shiftKey === hotKeys.goFile.shiftKey
&& event.which === hotKeys.goFile.which) { // Shift-Alt-O 跳转到文件
$("#dialogGoFilePrompt").dialog("open");
}
}); });
},
init: function () {
this._bindFileTree();
this._bindOutput();
this._bindDocument();
} }
}; };

47
static/js/lib.min.js vendored Normal file

File diff suppressed because one or more lines are too long

10
static/js/lib/Autolinker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,158 +0,0 @@
// 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 DEFAULT_BRACKETS = "()[]{}''\"\"";
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
var SPACE_CHAR_REGEX = /\s/;
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseBrackets");
if (!val) return;
var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
if (typeof val == "string") pairs = val;
else if (typeof val == "object") {
if (val.pairs != null) pairs = val.pairs;
if (val.explode != null) explode = val.explode;
}
var map = buildKeymap(pairs);
if (explode) map.Enter = buildExplodeHandler(explode);
cm.addKeyMap(map);
});
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
// Project the token type that will exists after the given char is
// typed, and use it to determine whether it would cause the start
// of a string token.
function enteringString(cm, pos, ch) {
var line = cm.getLine(pos.line);
var token = cm.getTokenAt(pos);
if (/\bstring2?\b/.test(token.type)) return false;
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
stream.pos = stream.start = token.start;
for (;;) {
var type1 = cm.getMode().token(stream, token.state);
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
stream.start = stream.pos;
}
}
function buildKeymap(pairs) {
var map = {
name : "autoCloseBrackets",
Backspace: function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
}
}
};
var closingBrackets = "";
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
if (left != right) closingBrackets += right;
map["'" + left + "'"] = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), type, next;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (!range.empty())
curType = "surround";
else if (left == right && next == right) {
if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
curType = "skipThree";
else
curType = "skip";
} else if (left == right && cur.ch > 1 &&
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left))
curType = "addFour";
else if (left == '"' || left == "'") {
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
else return CodeMirror.Pass;
} else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
curType = "both";
else
return CodeMirror.Pass;
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
cm.operation(function() {
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "skipThree") {
for (var i = 0; i < 3; i++)
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.execCommand("goCharLeft");
} else if (type == "addFour") {
cm.replaceSelection(left + left + left + left, "before");
cm.execCommand("goCharRight");
}
});
};
if (left != right) map["'" + right + "'"] = function(cm) {
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (!range.empty() ||
cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
return CodeMirror.Pass;
}
cm.execCommand("goCharRight");
};
})(pairs.charAt(i), pairs.charAt(i + 1));
return map;
}
function buildExplodeHandler(pairs) {
return function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
cm.replaceSelection("\n\n", null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
};
}
});

View File

@ -1,38 +0,0 @@
// 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 listRE = /^(\s*)([*+-]|(\d+)\.)(\s+)/,
unorderedBullets = "*+-";
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head, match;
var inList = cm.getStateAfter(pos.line).list !== false;
if (!ranges[i].empty() || !inList || !(match = cm.getLine(pos.line).match(listRE))) {
cm.execCommand("newlineAndIndent");
return;
}
var indent = match[1], after = match[4];
var bullet = unorderedBullets.indexOf(match[2]) >= 0
? match[2]
: (parseInt(match[3], 10) + 1) + ".";
replacements[i] = "\n" + indent + bullet + after;
}
cm.replaceSelections(replacements);
};
});

View File

@ -1,102 +0,0 @@
// 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 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) {
// Find the token at the cursor
var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
className: token.string == ":" ? "python-type" : null};
}
if (!context) var context = [];
context.push(tprop);
var completionList = getCompletions(token, context);
completionList = completionList.sort();
return {list: completionList,
from: CodeMirror.Pos(cur.line, token.start),
to: CodeMirror.Pos(cur.line, token.end)};
}
function pythonHint(editor) {
return scriptHint(editor, pythonKeywordsU, function (e, cur) {return e.getTokenAt(cur);});
}
CodeMirror.registerHelper("hint", "python", pythonHint);
var pythonKeywords = "and del from not while as elif global or with assert else if pass yield"
+ "break except import print class exec in raise continue finally is return def for lambda try";
var pythonKeywordsL = pythonKeywords.split(" ");
var pythonKeywordsU = pythonKeywords.toUpperCase().split(" ");
var pythonBuiltins = "abs divmod input open staticmethod all enumerate int ord str "
+ "any eval isinstance pow sum basestring execfile issubclass print super"
+ "bin file iter property tuple bool filter len range type"
+ "bytearray float list raw_input unichr callable format locals reduce unicode"
+ "chr frozenset long reload vars classmethod getattr map repr xrange"
+ "cmp globals max reversed zip compile hasattr memoryview round __import__"
+ "complex hash min set apply delattr help next setattr buffer"
+ "dict hex object slice coerce dir id oct sorted intern ";
var pythonBuiltinsL = pythonBuiltins.split(" ").join("() ").split(" ");
var pythonBuiltinsU = pythonBuiltins.toUpperCase().split(" ").join("() ").split(" ");
function getCompletions(token, context) {
var found = [], start = token.string;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
function gatherCompletions(_obj) {
forEach(pythonBuiltinsL, maybeAdd);
forEach(pythonBuiltinsU, maybeAdd);
forEach(pythonKeywordsL, maybeAdd);
forEach(pythonKeywordsU, maybeAdd);
}
if (context) {
// 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 == "variable")
base = obj.string;
else if(obj.type == "variable-3")
base = ":" + obj.string;
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
}
return found;
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +0,0 @@
// 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("../htmlmixed/htmlmixed"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
//config settings
var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
//inner modes
var scriptingMode, htmlMixedMode;
//tokenizer when in html mode
function htmlDispatch(stream, state) {
if (stream.match(scriptStartRegex, false)) {
state.token=scriptingDispatch;
return scriptingMode.token(stream, state.scriptState);
}
else
return htmlMixedMode.token(stream, state.htmlState);
}
//tokenizer when in scripting mode
function scriptingDispatch(stream, state) {
if (stream.match(scriptEndRegex, false)) {
state.token=htmlDispatch;
return htmlMixedMode.token(stream, state.htmlState);
}
else
return scriptingMode.token(stream, state.scriptState);
}
return {
startState: function() {
scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
return {
token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
htmlState : CodeMirror.startState(htmlMixedMode),
scriptState : CodeMirror.startState(scriptingMode)
};
},
token: function(stream, state) {
return state.token(stream, state);
},
indent: function(state, textAfter) {
if (state.token == htmlDispatch)
return htmlMixedMode.indent(state.htmlState, textAfter);
else if (scriptingMode.indent)
return scriptingMode.indent(state.scriptState, textAfter);
},
copyState: function(state) {
return {
token : state.token,
htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
};
},
innerMode: function(state) {
if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
else return {state: state.htmlState, mode: htmlMixedMode};
}
};
}, "htmlmixed");
CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"});
});

View File

@ -1,115 +0,0 @@
// 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.modeInfo = [
{name: "APL", mime: "text/apl", mode: "apl"},
{name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"},
{name: "C", mime: "text/x-csrc", mode: "clike"},
{name: "C++", mime: "text/x-c++src", mode: "clike"},
{name: "Cobol", mime: "text/x-cobol", mode: "cobol"},
{name: "Java", mime: "text/x-java", mode: "clike"},
{name: "C#", mime: "text/x-csharp", mode: "clike"},
{name: "Scala", mime: "text/x-scala", mode: "clike"},
{name: "Clojure", mime: "text/x-clojure", mode: "clojure"},
{name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript"},
{name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp"},
{name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"},
{name: "CSS", mime: "text/css", mode: "css"},
{name: "D", mime: "text/x-d", mode: "d"},
{name: "diff", mime: "text/x-diff", mode: "diff"},
{name: "DTD", mime: "application/xml-dtd", mode: "dtd"},
{name: "Dylan", mime: "text/x-dylan", mode: "dylan"},
{name: "ECL", mime: "text/x-ecl", mode: "ecl"},
{name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel"},
{name: "Erlang", mime: "text/x-erlang", mode: "erlang"},
{name: "Fortran", mime: "text/x-fortran", mode: "fortran"},
{name: "F#", mime: "text/x-fsharp", mode: "mllike"},
{name: "Gas", mime: "text/x-gas", mode: "gas"},
{name: "Gherkin", mime: "text/x-feature", mode: "gherkin"},
{name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"},
{name: "Go", mime: "text/x-go", mode: "go"},
{name: "Groovy", mime: "text/x-groovy", mode: "groovy"},
{name: "HAML", mime: "text/x-haml", mode: "haml"},
{name: "Haskell", mime: "text/x-haskell", mode: "haskell"},
{name: "Haxe", mime: "text/x-haxe", mode: "haxe"},
{name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded"},
{name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded"},
{name: "JavaServer Pages", mime: "application/x-jsp", mode: "htmlembedded"},
{name: "HTML", mime: "text/html", mode: "htmlmixed"},
{name: "HTTP", mime: "message/http", mode: "http"},
{name: "Jade", mime: "text/x-jade", mode: "jade"},
{name: "JavaScript", mime: "text/javascript", mode: "javascript"},
{name: "JavaScript", mime: "application/javascript", mode: "javascript"},
{name: "JSON", mime: "application/x-json", mode: "javascript"},
{name: "JSON", mime: "application/json", mode: "javascript"},
{name: "JSON-LD", mime: "application/ld+json", mode: "javascript"},
{name: "TypeScript", mime: "application/typescript", mode: "javascript"},
{name: "Jinja2", mime: null, mode: "jinja2"},
{name: "Julia", mime: "text/x-julia", mode: "julia"},
{name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin"},
{name: "LESS", mime: "text/x-less", mode: "css"},
{name: "LiveScript", mime: "text/x-livescript", mode: "livescript"},
{name: "Lua", mime: "text/x-lua", mode: "lua"},
{name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown"},
{name: "mIRC", mime: "text/mirc", mode: "mirc"},
{name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"},
{name: "NTriples", mime: "text/n-triples", mode: "ntriples"},
{name: "OCaml", mime: "text/x-ocaml", mode: "mllike"},
{name: "Octave", mime: "text/x-octave", mode: "octave"},
{name: "Pascal", mime: "text/x-pascal", mode: "pascal"},
{name: "PEG.js", mime: null, mode: "pegjs"},
{name: "Perl", mime: "text/x-perl", mode: "perl"},
{name: "PHP", mime: "text/x-php", mode: "php"},
{name: "PHP(HTML)", mime: "application/x-httpd-php", mode: "php"},
{name: "Pig", mime: "text/x-pig", mode: "pig"},
{name: "Plain Text", mime: "text/plain", mode: "null"},
{name: "Properties files", mime: "text/x-properties", mode: "properties"},
{name: "Python", mime: "text/x-python", mode: "python"},
{name: "Puppet", mime: "text/x-puppet", mode: "puppet"},
{name: "Cython", mime: "text/x-cython", mode: "python"},
{name: "R", mime: "text/x-rsrc", mode: "r"},
{name: "reStructuredText", mime: "text/x-rst", mode: "rst"},
{name: "Ruby", mime: "text/x-ruby", mode: "ruby"},
{name: "Rust", mime: "text/x-rustsrc", mode: "rust"},
{name: "Sass", mime: "text/x-sass", mode: "sass"},
{name: "Scheme", mime: "text/x-scheme", mode: "scheme"},
{name: "SCSS", mime: "text/x-scss", mode: "css"},
{name: "Shell", mime: "text/x-sh", mode: "shell"},
{name: "Sieve", mime: "application/sieve", mode: "sieve"},
{name: "Slim", mime: "text/x-slim", mode: "slim"},
{name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk"},
{name: "Smarty", mime: "text/x-smarty", mode: "smarty"},
{name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
{name: "Solr", mime: "text/x-solr", mode: "solr"},
{name: "SPARQL", mime: "application/x-sparql-query", mode: "sparql"},
{name: "SQL", mime: "text/x-sql", mode: "sql"},
{name: "MariaDB", mime: "text/x-mariadb", mode: "sql"},
{name: "sTeX", mime: "text/x-stex", mode: "stex"},
{name: "LaTeX", mime: "text/x-latex", mode: "stex"},
{name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog"},
{name: "Tcl", mime: "text/x-tcl", mode: "tcl"},
{name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"},
{name: "Tiki wiki", mime: "text/tiki", mode: "tiki"},
{name: "TOML", mime: "text/x-toml", mode: "toml"},
{name: "Turtle", mime: "text/turtle", mode: "turtle"},
{name: "VB.NET", mime: "text/x-vb", mode: "vb"},
{name: "VBScript", mime: "text/vbscript", mode: "vbscript"},
{name: "Velocity", mime: "text/velocity", mode: "velocity"},
{name: "Verilog", mime: "text/x-verilog", mode: "verilog"},
{name: "XML", mime: "application/xml", mode: "xml"},
{name: "XQuery", mime: "application/xquery", mode: "xquery"},
{name: "YAML", mime: "text/x-yaml", mode: "yaml"},
{name: "Z80", mime: "text/x-z80", mode: "z80"}
];
});

View File

@ -1,221 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/**
* Smarty 2 and 3 mode.
*/
(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("smarty", function(config) {
"use strict";
// our default settings; check to see if they're overridden
var settings = {
rightDelimiter: '}',
leftDelimiter: '{',
smartyVersion: 2 // for backward compatibility
};
if (config.hasOwnProperty("leftDelimiter")) {
settings.leftDelimiter = config.leftDelimiter;
}
if (config.hasOwnProperty("rightDelimiter")) {
settings.rightDelimiter = config.rightDelimiter;
}
if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) {
settings.smartyVersion = 3;
}
var keyFunctions = ["debug", "extends", "function", "include", "literal"];
var last;
var regs = {
operatorChars: /[+\-*&%=<>!?]/,
validIdentifier: /[a-zA-Z0-9_]/,
stringChar: /['"]/
};
var helpers = {
cont: function(style, lastType) {
last = lastType;
return style;
},
chain: function(stream, state, parser) {
state.tokenize = parser;
return parser(stream, state);
}
};
// our various parsers
var parsers = {
// the main tokenizer
tokenizer: function(stream, state) {
if (stream.match(settings.leftDelimiter, true)) {
if (stream.eat("*")) {
return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
} else {
// Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
state.depth++;
var isEol = stream.eol();
var isFollowedByWhitespace = /\s/.test(stream.peek());
if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) {
state.depth--;
return null;
} else {
state.tokenize = parsers.smarty;
last = "startTag";
return "tag";
}
}
} else {
stream.next();
return null;
}
},
// parsing Smarty content
smarty: function(stream, state) {
if (stream.match(settings.rightDelimiter, true)) {
if (settings.smartyVersion === 3) {
state.depth--;
if (state.depth <= 0) {
state.tokenize = parsers.tokenizer;
}
} else {
state.tokenize = parsers.tokenizer;
}
return helpers.cont("tag", null);
}
if (stream.match(settings.leftDelimiter, true)) {
state.depth++;
return helpers.cont("tag", "startTag");
}
var ch = stream.next();
if (ch == "$") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("variable-2", "variable");
} else if (ch == "|") {
return helpers.cont("operator", "pipe");
} else if (ch == ".") {
return helpers.cont("operator", "property");
} else if (regs.stringChar.test(ch)) {
state.tokenize = parsers.inAttribute(ch);
return helpers.cont("string", "string");
} else if (regs.operatorChars.test(ch)) {
stream.eatWhile(regs.operatorChars);
return helpers.cont("operator", "operator");
} else if (ch == "[" || ch == "]") {
return helpers.cont("bracket", "bracket");
} else if (ch == "(" || ch == ")") {
return helpers.cont("bracket", "operator");
} else if (/\d/.test(ch)) {
stream.eatWhile(/\d/);
return helpers.cont("number", "number");
} else {
if (state.last == "variable") {
if (ch == "@") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("property", "property");
} else if (ch == "|") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("qualifier", "modifier");
}
} else if (state.last == "pipe") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("qualifier", "modifier");
} else if (state.last == "whitespace") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("attribute", "modifier");
} if (state.last == "property") {
stream.eatWhile(regs.validIdentifier);
return helpers.cont("property", null);
} else if (/\s/.test(ch)) {
last = "whitespace";
return null;
}
var str = "";
if (ch != "/") {
str += ch;
}
var c = null;
while (c = stream.eat(regs.validIdentifier)) {
str += c;
}
for (var i=0, j=keyFunctions.length; i<j; i++) {
if (keyFunctions[i] == str) {
return helpers.cont("keyword", "keyword");
}
}
if (/\s/.test(ch)) {
return null;
}
return helpers.cont("tag", "tag");
}
},
inAttribute: function(quote) {
return function(stream, state) {
var prevChar = null;
var currChar = null;
while (!stream.eol()) {
currChar = stream.peek();
if (stream.next() == quote && prevChar !== '\\') {
state.tokenize = parsers.smarty;
break;
}
prevChar = currChar;
}
return "string";
};
},
inBlock: function(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = parsers.tokenizer;
break;
}
stream.next();
}
return style;
};
}
};
// the public API for CodeMirror
return {
startState: function() {
return {
tokenize: parsers.tokenizer,
mode: "smarty",
last: null,
depth: 0
};
},
token: function(stream, state) {
var style = state.tokenize(stream, state);
state.last = last;
return style;
},
electricChars: ""
};
});
CodeMirror.defineMIME("text/x-smarty", "smarty");
});

View File

@ -1,114 +0,0 @@
<!doctype html>
<title>CodeMirror: Smarty mixed mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../mode/xml/xml.js"></script>
<script src="../../mode/javascript/javascript.js"></script>
<script src="../../mode/css/css.js"></script>
<script src="../../mode/htmlmixed/htmlmixed.js"></script>
<script src="../../mode/smarty/smarty.js"></script>
<script src="../../mode/smartymixed/smartymixed.js"></script>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/marijnh/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Smarty mixed</a>
</ul>
</div>
<article>
<h2>Smarty mixed mode</h2>
<form><textarea id="code" name="code">
{**
* @brief Smarty mixed mode
* @author Ruslan Osmanov
* @date 29.06.2013
*}
<html>
<head>
<title>{$title|htmlspecialchars|truncate:30}</title>
</head>
<body class="{$bodyclass}">
{* Multiline smarty
* comment, no {$variables} here
*}
{literal}
{literal} is just an HTML text.
<script type="text/javascript">//<![CDATA[
var a = {$just_a_normal_js_object : "value"};
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("code"), {
mode : "smartymixed",
tabSize : 2,
indentUnit : 2,
indentWithTabs : false,
lineNumbers : true,
smartyVersion : 3
});
// ]]>
</script>
<style>
/* CSS content
{$no_smarty} */
.some-class { font-weight: bolder; color: "orange"; }
</style>
{/literal}
{extends file="parent.tpl"}
{include file="template.tpl"}
{* some example Smarty content *}
{if isset($name) && $name == 'Blog'}
This is a {$var}.
{$integer = 4511}, {$array[] = "a"}, {$stringvar = "string"}
{$integer = 4512} {$array[] = "a"} {$stringvar = "string"}
{assign var='bob' value=$var.prop}
{elseif $name == $foo}
{function name=menu level=0}
{foreach $data as $entry}
{if is_array($entry)}
- {$entry@key}
{menu data=$entry level=$level+1}
{else}
{$entry}
{* One
* Two
* Three
*}
{/if}
{/foreach}
{/function}
{/if}
</body>
<!-- R.O. -->
</html>
</textarea></form>
<script type="text/javascript">
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("code"), {
mode : "smartymixed",
tabSize : 2,
indentUnit : 2,
indentWithTabs : false,
lineNumbers : true,
smartyVersion : 3,
matchBrackets : true,
});
</script>
<p>The Smarty mixed mode depends on the Smarty and HTML mixed modes. HTML
mixed mode itself depends on XML, JavaScript, and CSS modes.</p>
<p>It takes the same options, as Smarty and HTML mixed modes.</p>
<p><strong>MIME types defined:</strong> <code>text/x-smarty</code>.</p>
</article>

View File

@ -1,193 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/**
* @file smartymixed.js
* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML)
* @author Ruslan Osmanov <rrosmanov at gmail dot com>
* @version 3.0
* @date 05.07.2013
*/
// Warning: Don't base other modes on this one. This here is a
// terrible way to write a mixed mode.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../smarty/smarty"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("smartymixed", function(config) {
var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
var smartyMode = CodeMirror.getMode(config, "smarty");
var settings = {
rightDelimiter: '}',
leftDelimiter: '{'
};
if (config.hasOwnProperty("leftDelimiter")) {
settings.leftDelimiter = config.leftDelimiter;
}
if (config.hasOwnProperty("rightDelimiter")) {
settings.rightDelimiter = config.rightDelimiter;
}
function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }
var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter);
var regs = {
smartyComment: new RegExp("^" + reRight + "\\*"),
literalOpen: new RegExp(reLeft + "literal" + reRight),
literalClose: new RegExp(reLeft + "\/literal" + reRight),
hasLeftDelimeter: new RegExp(".*" + reLeft),
htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft)
};
var helpers = {
chain: function(stream, state, parser) {
state.tokenize = parser;
return parser(stream, state);
},
cleanChain: function(stream, state, parser) {
state.tokenize = null;
state.localState = null;
state.localMode = null;
return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
},
maybeBackup: function(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat),
m;
if (close > - 1) stream.backUp(cur.length - close);
else if (m = cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur[0]);
}
return style;
}
};
var parsers = {
html: function(stream, state) {
if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && state.htmlMixedState.htmlState.tagName === null) {
state.tokenize = parsers.smarty;
state.localMode = smartyMode;
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
} else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
state.tokenize = parsers.smarty;
state.localMode = smartyMode;
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
}
return htmlMixedMode.token(stream, state.htmlMixedState);
},
smarty: function(stream, state) {
if (stream.match(settings.leftDelimiter, false)) {
if (stream.match(regs.smartyComment, false)) {
return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
}
} else if (stream.match(settings.rightDelimiter, false)) {
stream.eat(settings.rightDelimiter);
state.tokenize = parsers.html;
state.localMode = htmlMixedMode;
state.localState = state.htmlMixedState;
return "tag";
}
return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
},
inBlock: function(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
helpers.cleanChain(stream, state, "");
break;
}
stream.next();
}
return style;
};
}
};
return {
startState: function() {
var state = htmlMixedMode.startState();
return {
token: parsers.html,
localMode: null,
localState: null,
htmlMixedState: state,
tokenize: null,
inLiteral: false
};
},
copyState: function(state) {
var local = null, tok = (state.tokenize || state.token);
if (state.localState) {
local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
}
return {
token: state.token,
tokenize: state.tokenize,
localMode: state.localMode,
localState: local,
htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
inLiteral: state.inLiteral
};
},
token: function(stream, state) {
if (stream.match(settings.leftDelimiter, false)) {
if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
state.inLiteral = true;
return "keyword";
} else if (state.inLiteral && stream.match(regs.literalClose, true)) {
state.inLiteral = false;
return "keyword";
}
}
if (state.inLiteral && state.localState != state.htmlMixedState) {
state.tokenize = parsers.html;
state.localMode = htmlMixedMode;
state.localState = state.htmlMixedState;
}
var style = (state.tokenize || state.token)(stream, state);
return style;
},
indent: function(state, textAfter) {
if (state.localMode == smartyMode
|| (state.inLiteral && !state.localMode)
|| regs.hasLeftDelimeter.test(textAfter)) {
return CodeMirror.Pass;
}
return htmlMixedMode.indent(state.htmlMixedState, textAfter);
},
innerMode: function(state) {
return {
state: state.localState || state.htmlMixedState,
mode: state.localMode || htmlMixedMode
};
}
};
}, "htmlmixed", "smarty");
CodeMirror.defineMIME("text/x-smarty", "smartymixed");
// vim: et ts=2 sts=2 sw=2
});

View File

@ -1,261 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/*
* Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de)
* Licence: MIT
*/
(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("stex", function() {
"use strict";
function pushCommand(state, command) {
state.cmdState.push(command);
}
function peekCommand(state) {
if (state.cmdState.length > 0) {
return state.cmdState[state.cmdState.length - 1];
} else {
return null;
}
}
function popCommand(state) {
var plug = state.cmdState.pop();
if (plug) {
plug.closeBracket();
}
}
// returns the non-default plugin closest to the end of the list
function getMostPowerful(state) {
var context = state.cmdState;
for (var i = context.length - 1; i >= 0; i--) {
var plug = context[i];
if (plug.name == "DEFAULT") {
continue;
}
return plug;
}
return { styleIdentifier: function() { return null; } };
}
function addPluginPattern(pluginName, cmdStyle, styles) {
return function () {
this.name = pluginName;
this.bracketNo = 0;
this.style = cmdStyle;
this.styles = styles;
this.argument = null; // \begin and \end have arguments that follow. These are stored in the plugin
this.styleIdentifier = function() {
return this.styles[this.bracketNo - 1] || null;
};
this.openBracket = function() {
this.bracketNo++;
return "bracket";
};
this.closeBracket = function() {};
};
}
var plugins = {};
plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]);
plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]);
plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]);
plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]);
plugins["end"] = addPluginPattern("end", "tag", ["atom"]);
plugins["DEFAULT"] = function () {
this.name = "DEFAULT";
this.style = "tag";
this.styleIdentifier = this.openBracket = this.closeBracket = function() {};
};
function setState(state, f) {
state.f = f;
}
// called when in a normal (no environment) context
function normal(source, state) {
var plug;
// Do we look like '\command' ? If so, attempt to apply the plugin 'command'
if (source.match(/^\\[a-zA-Z@]+/)) {
var cmdName = source.current().slice(1);
plug = plugins[cmdName] || plugins["DEFAULT"];
plug = new plug();
pushCommand(state, plug);
setState(state, beginParams);
return plug.style;
}
// escape characters
if (source.match(/^\\[$&%#{}_]/)) {
return "tag";
}
// white space control characters
if (source.match(/^\\[,;!\/\\]/)) {
return "tag";
}
// find if we're starting various math modes
if (source.match("\\[")) {
setState(state, function(source, state){ return inMathMode(source, state, "\\]"); });
return "keyword";
}
if (source.match("$$")) {
setState(state, function(source, state){ return inMathMode(source, state, "$$"); });
return "keyword";
}
if (source.match("$")) {
setState(state, function(source, state){ return inMathMode(source, state, "$"); });
return "keyword";
}
var ch = source.next();
if (ch == "%") {
// special case: % at end of its own line; stay in same state
if (!source.eol()) {
setState(state, inCComment);
}
return "comment";
}
else if (ch == '}' || ch == ']') {
plug = peekCommand(state);
if (plug) {
plug.closeBracket(ch);
setState(state, beginParams);
} else {
return "error";
}
return "bracket";
} else if (ch == '{' || ch == '[') {
plug = plugins["DEFAULT"];
plug = new plug();
pushCommand(state, plug);
return "bracket";
}
else if (/\d/.test(ch)) {
source.eatWhile(/[\w.%]/);
return "atom";
}
else {
source.eatWhile(/[\w\-_]/);
plug = getMostPowerful(state);
if (plug.name == 'begin') {
plug.argument = source.current();
}
return plug.styleIdentifier();
}
}
function inCComment(source, state) {
source.skipToEnd();
setState(state, normal);
return "comment";
}
function inMathMode(source, state, endModeSeq) {
if (source.eatSpace()) {
return null;
}
if (source.match(endModeSeq)) {
setState(state, normal);
return "keyword";
}
if (source.match(/^\\[a-zA-Z@]+/)) {
return "tag";
}
if (source.match(/^[a-zA-Z]+/)) {
return "variable-2";
}
// escape characters
if (source.match(/^\\[$&%#{}_]/)) {
return "tag";
}
// white space control characters
if (source.match(/^\\[,;!\/]/)) {
return "tag";
}
// special math-mode characters
if (source.match(/^[\^_&]/)) {
return "tag";
}
// non-special characters
if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
return null;
}
if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
return "number";
}
var ch = source.next();
if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") {
return "bracket";
}
// eat comments here, because inCComment returns us to normal state!
if (ch == "%") {
if (!source.eol()) {
source.skipToEnd();
}
return "comment";
}
return "error";
}
function beginParams(source, state) {
var ch = source.peek(), lastPlug;
if (ch == '{' || ch == '[') {
lastPlug = peekCommand(state);
lastPlug.openBracket(ch);
source.eat(ch);
setState(state, normal);
return "bracket";
}
if (/[ \t\r]/.test(ch)) {
source.eat(ch);
return null;
}
setState(state, normal);
popCommand(state);
return normal(source, state);
}
return {
startState: function() {
return {
cmdState: [],
f: normal
};
},
copyState: function(s) {
return {
cmdState: s.cmdState.slice(),
f: s.f
};
},
token: function(stream, state) {
return state.f(stream, state);
}
};
});
CodeMirror.defineMIME("text/x-stex", "stex");
CodeMirror.defineMIME("text/x-latex", "stex");
});

View File

@ -109,7 +109,7 @@
CodeMirror.defineExtension("uncomment", function(from, to, options) { CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions; if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from); var self = this, mode = self.getModeAt(from);
var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end); var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments // Try finding line comments
var lineString = options.lineComment || mode.lineComment, lines = []; var lineString = options.lineComment || mode.lineComment, lines = [];

View File

@ -56,7 +56,12 @@
var inp = dialog.getElementsByTagName("input")[0], button; var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) { if (inp) {
if (options.value) inp.value = options.value; if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
inp.select();
}
}
if (options.onInput) if (options.onInput)
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
@ -70,7 +75,7 @@
CodeMirror.e_stop(e); CodeMirror.e_stop(e);
close(); close();
} }
if (e.keyCode == 13) callback(inp.value); if (e.keyCode == 13) callback(inp.value, e);
}); });
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
@ -129,8 +134,8 @@
CodeMirror.defineExtension("openNotification", function(template, options) { CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close); closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom); var dialog = dialogDiv(this, template, options && options.bottom);
var duration = options && (options.duration === undefined ? 5000 : options.duration);
var closed = false, doneTimer; var closed = false, doneTimer;
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() { function close() {
if (closed) return; if (closed) return;
@ -143,7 +148,10 @@
CodeMirror.e_preventDefault(e); CodeMirror.e_preventDefault(e);
close(); close();
}); });
if (duration) if (duration)
doneTimer = setTimeout(close, options.duration); doneTimer = setTimeout(close, duration);
return close;
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More