Compare commits

...

548 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
484 changed files with 62274 additions and 15864 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

9
.gitignore vendored
View File

@ -1,7 +1,12 @@
/wide.exe /wide.exe
/wide /wide
/static/user/admin/style.css
/header /header
/header.exe /header.exe
/**/.DS_Store
/node_modules
/nbproject
/workspaces
.idea

View File

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

View File

@ -10,11 +10,12 @@
], ],
"Excludes": [ "Excludes": [
"static/js/lib/*", "static/js/lib/*",
"static/user/*" "static/user/*",
"vendor/**"
], ],
"UseDefaultExcludes": true, "UseDefaultExcludes": true,
"Properties": { "Properties": {
"Year": "2014-2015", "Year": "2014-present",
"Owner": "b3log.org" "Owner": "b3log.org"
} }
} }

View File

@ -4,7 +4,7 @@ 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,

View File

@ -1,11 +1,4 @@
language: go language: go
go: go:
- 1.4 - 1.12
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- ./coverage.sh
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci -repotoken W04Y4p452CDeI9gcsC8t5y217uHnUDFCb

View File

@ -1,16 +0,0 @@
FROM golang:latest
MAINTAINER Liang Ding <dl88250@gmail.com>
ADD . /wide/gogogo/src/github.com/b3log/wide
RUN useradd wide && useradd runner
ENV GOROOT /usr/src/go
ENV GOPATH /wide/gogogo
RUN go get -v golang.org/x/tools/cmd/vet github.com/88250/ide_stub github.com/nsf/gocode github.com/bradfitz/goimports
WORKDIR /wide/gogogo/src/github.com/b3log/wide
RUN go get -v && go build -v
EXPOSE 7070

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,

168
README.md
View File

@ -1,156 +1,18 @@
# Wide [![Build Status](https://img.shields.io/travis/b3log/wide.svg?style=flat)](https://travis-ci.org/b3log/wide) [![Coverage Status](https://img.shields.io/coveralls/b3log/wide.svg?style=flat)](https://coveralls.io/r/b3log/wide) [![Apache License](http://img.shields.io/badge/license-apache2-orange.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![API Documentation](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](http://godoc.org/github.com/b3log/wide) [![Download](http://img.shields.io/badge/download-~1K-red.svg?style=flat)](http://pan.baidu.com/s/1dD3XwOT) * [Wide 用户指南](https://ld246.com/article/1538873544275)
* [Wide 开发指南](https://ld246.com/article/1538876422995)
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
_Have a [**try**](http://wide.b3log.org/signup) first, then [download](http://pan.baidu.com/s/1dD3XwOT) and setup it on your local area network, enjoy yourself!_ ![Theme](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
先试试我们搭建好的[**在线服务**](http://wide.b3log.org/signup),好用的话你可以在这里[下载](http://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请你的小伙伴们加入吧! ![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
## Intro ![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
A <b>W</b>eb-based <b>IDE</b> for Teams using Golang.
![Hello, 世界](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png)
## Motivation
* **Team** IDE:
* _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 to extend
* Easy to integrate 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
* [X] Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc.
* [X] Autocomplete: Go/HTML etc.
* [X] Format: Go/HTML/JSON etc.
* [X] Build & Run
* [X] Multiplayer: a real team development experience
* [X] Navigation, Jump to declaration, Find usages, File search etc.
* [X] Shell: run command on the server
* [X] Web development: Frontend devlopment (HTML/JS/CSS) all in one
* [X] Go tool: go get/install/fmt etc.
* [X] File Import & Export
* [X] Themes
* [ ] Debug
* [ ] Git integration: git command on the web
## Screenshots
* **Overview**
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
* **Goto File**
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
* **Autocomplete**
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
* **Theme**
![4](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
* **Show Expression Info**
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
* **Build Error Info**
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
* **About**
![About](https://cloud.githubusercontent.com/assets/873584/5521709/87ce85a8-89e3-11e4-87b2-4870a426dfcd.png)
## Architecture
### 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/Jump To Declaration/etc.
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)
## Setup
### Download Binary
We have provided OS-specific executable binary as follows:
* linux-amd64/386
* windows-amd64/386
* darwin-amd64/386
Download [HERE](http://pan.baidu.com/s/1dD3XwOT)!
### Build Wide for yourself
1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone`
2. Get dependencies with
* `go get`
* `go get github.com/88250/ide_stub`
* `go get github.com/nsf/gocode`
3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -p 127.0.0.1:7070:7070 88250/wide:latest ./wide -docker=true -channel=ws://127.0.0.1:7070`
3. Open browser: http://127.0.0.1:7070
## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
## Terms
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://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 support@liuyun.io for request a commercial license
* Copyright (c) b3log.org, all rights reserved
## 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)
* [GoBuild](http://gobuild.io)
* [Docker](https://docker.com)
----
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />

View File

@ -1,4 +1,4 @@
* This software is open sourced under the Apache License 2.0 * This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any pages, even the pages are developed by you * 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 support@liuyun.io for request a commercial license * 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 * Copyright (c) b3log.org, all rights reserved

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -15,54 +15,56 @@
package conf package conf
import ( import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
"github.com/b3log/wide/util"
) )
// Layout represents the layot of a window. // 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 { type Layout struct {
State string // min/max/normal 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. // LatestSessionContent represents the latest session content.
type LatestSessionContent struct { type LatestSessionContent struct {
FileTree []string // paths of expanding nodes of file tree FileTree []string `json:"fileTree"` // paths of expanding nodes of file tree
Files []string // paths of files of opening editor tabs Files []string `json:"files"` // paths of files of opening editor tabs
CurrentFile string // path of file of the current focused editor tab CurrentFile string `json:"currentFile"` // path of file of the current focused editor tab
Layout *Layout `json:"layout"` // UI Layout
FileTreeLayout *Layout
EditorLayout *Layout
OutlineLayout *Layout
BottomLayout *Layout
} }
// User configuration. // User configuration.
type User struct { type User struct {
Name string Id string
Password string Name string
Salt string Avatar string
Email string Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Gravatar string // see http://gravatar.com Locale string
Workspace string // the GOPATH of this user GoFormat string
Locale string GoBuildArgsForLinux string
GoFormat string GoBuildArgsForWindows string
FontFamily string GoBuildArgsForDarwin string
FontSize string FontFamily string
Theme string FontSize string
Created int64 // user create time in unix nano Theme string
Updated int64 // preference update time in unix nano Keymap string // wide/vim
Lived int64 // the latest session activity in unix nano Created int64 // user create time in unix nano
Editor *editor Updated int64 // preference update time in unix nano
LatestSessionContent *LatestSessionContent Lived int64 // the latest session activity in unix nano
Editor *editor
LatestSessionContent *LatestSessionContent
} }
// Editor configuration of a user. // Editor configuration of a user.
@ -74,25 +76,7 @@ type editor struct {
TabSize string TabSize string
} }
// NewUser creates a user with the specified username, password, email and workspace. // Save saves the user's configurations in conf/users/{userId}.json.
func NewUser(username, password, email, workspace string) *User {
md5hash := md5.New()
md5hash.Write([]byte(email))
gravatar := hex.EncodeToString(md5hash.Sum(nil))
salt := util.Rand.String(16)
password = Salt(password, salt)
now := time.Now().UnixNano()
return &User{Name: username, Password: password, Salt: salt, Email: email, Gravatar: gravatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// Save saves the user's configurations in conf/users/{username}.json.
func (u *User) Save() bool { func (u *User) Save() bool {
bytes, err := json.MarshalIndent(u, "", " ") bytes, err := json.MarshalIndent(u, "", " ")
@ -102,7 +86,13 @@ func (u *User) Save() bool {
return false return false
} }
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err { 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) logger.Error(err)
return false return false
@ -111,34 +101,62 @@ func (u *User) Save() bool {
return true return true
} }
// GetWorkspace gets workspace path of the user. // 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: // Compared to the use of Wide.Workspace, this function will be processed as follows:
// 1. Replace {WD} variable with the actual directory path // 1. Replace {WD} variable with the actual directory path
// 2. Replace ${GOPATH} with enviorment variable GOPATH // 2. Replace ${GOPATH} with enviorment variable GOPATH
// 3. Replace "/" with "\\" (Windows) // 3. Replace "/" with "\\" (Windows)
func (u *User) GetWorkspace() string { func (u *User) WorkspacePath() string {
w := strings.Replace(u.Workspace, "{WD}", Wide.WD, 1) w := u.Workspace
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1) w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
return filepath.FromSlash(w) 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. // GetOwner gets the user the specified path belongs to. Returns "" if not found.
func GetOwner(path string) string { func GetOwner(path string) string {
for _, user := range Users { for _, user := range Users {
if strings.HasPrefix(path, user.GetWorkspace()) { if strings.HasPrefix(path, user.WorkspacePath()) {
return user.Name return user.Id
} }
} }
return "" return ""
} }
// Salt salts the specified password with the specified salt.
func Salt(password, salt string) string {
sha1hash := sha1.New()
sha1hash.Write([]byte(password + salt))
return hex.EncodeToString(sha1hash.Sum(nil))
}

View File

@ -1,32 +0,0 @@
{
"Name": "admin",
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
"Salt": "dnoyeb",
"Email": "",
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Theme": "default",
"Created": 1414080000000000000,
"Updated": 1414080000000000000,
"Lived": 1414080000000000000,
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "13px",
"LineHeight": "17px",
"Theme": "wide",
"TabSize": "4"
},
"LatestSessionContent": {
"FileTree": [],
"Files": [],
"CurrentFile": "",
"FileTreeLayout": null,
"EditorLayout": null,
"OutlineLayout": null,
"BottomLayout": null
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -17,19 +17,17 @@ package conf
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "html/template"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"text/template"
"time" "time"
"github.com/b3log/wide/event" "github.com/88250/gulu"
"github.com/b3log/wide/log" "github.com/88250/wide/event"
"github.com/b3log/wide/util"
) )
const ( const (
@ -38,41 +36,44 @@ const (
// PathListSeparator holds the OS-specific path list separator. // PathListSeparator holds the OS-specific path list separator.
PathListSeparator = string(os.PathListSeparator) PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current wide version. // WideVersion holds the current Wide's version.
WideVersion = "1.2.0" WideVersion = "1.6.0"
// CodeMirrorVer holds the current editor version. // CodeMirrorVer holds the current editor version.
CodeMirrorVer = "4.10" CodeMirrorVer = "5.1"
// UserAgent represents HTTP client user agent.
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
HelloWorld = `package main HelloWorld = `package main
import "fmt" import "fmt"
func main() { func main() {
fmt.Println("Hello, 世界") fmt.Println("欢迎通过《边看边练 Go 系列》来学习 Go 语言 https://ld246.com/article/1437497122181")
} }
` `
) )
// Configuration. // Configuration.
type conf struct { type conf struct {
IP string // server ip, ${ip} Server string // server
Port string // server port LogLevel string // logging level: trace/debug/info/warn/error
Context string // server context Data string // data directory
Server string // server host and port ({IP}:{Port}) RuntimeMode string // runtime mode (dev/prod)
StaticServer string // static resources server scheme, host and port (http://{IP}:{Port}) HTTPSessionMaxAge int // HTTP session max age (in seciond)
LogLevel string // logging level: trace/debug/info/warn/error StaticResourceVersion string // version of static resources
Channel string // channel (ws://{IP}:{Port}) Locale string // default locale
HTTPSessionMaxAge int // HTTP session max age (in seciond) Autocomplete bool // default autocomplete
StaticResourceVersion string // version of static resources SiteStatCode template.HTML // site statistic code
MaxProcs int // Go max procs ReadOnly bool // read-only mode
RuntimeMode string // runtime mode (dev/prod) OAuthLoginURL string
WD string // current working direcitory, ${pwd} OAuthAccessTokenURL string
Locale string // default locale OAuthUserInfoURL string
Playground string // playground directory OAuthClientID string
OAuthClientSecret string
} }
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Wide configurations. // Wide configurations.
var Wide *conf var Wide *conf
@ -80,21 +81,28 @@ var Wide *conf
// configurations of users. // configurations of users.
var Users []*User var Users []*User
// Indicates whether runs via Docker. // Indicates whether Docker is available.
var Docker bool var Docker bool
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json. // Docker image to run user's program
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, const DockerImageGo = "golang"
confPlayground string, confDocker bool) {
// XXX: ugly args list....
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, // Load loads the Wide configurations from wide.json and users' configurations from users/{userId}.json.
confPlayground, confDocker) func Load(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
initWide(confPath, confData, confServer, confLogLevel, confReadOnly, confSiteStatCode)
initUsers() initUsers()
cmd := exec.Command("docker", "version")
_, err := cmd.CombinedOutput()
if nil != err {
logger.Warnf("Not found 'docker' installed, running user's code will cause security problem")
} else {
Docker = true
}
} }
func initUsers() { func initUsers() {
f, err := os.Open("conf/users") f, err := os.Open(Wide.Data + PathSeparator + "users")
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
@ -114,17 +122,36 @@ func initUsers() {
continue continue
} }
if ".json" != filepath.Ext(name) { // such as backup (*.json~) not be created by Wide
continue
}
user := &User{} user := &User{}
bytes, _ := ioutil.ReadFile("conf/users/" + name) bytes, _ := os.ReadFile(filepath.Join(Wide.Data, "users", name))
err := json.Unmarshal(bytes, user) err := json.Unmarshal(bytes, user)
if err != nil { if err != nil {
logger.Errorf("Parses [%s] error: %v", name, err) logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
os.Exit(-1) 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) Users = append(Users, user)
} }
@ -132,9 +159,8 @@ func initUsers() {
initCustomizedConfs() initCustomizedConfs()
} }
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
confPlayground string, confDocker bool) { bytes, err := os.ReadFile(confPath)
bytes, err := ioutil.ReadFile(confPath)
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
@ -150,92 +176,65 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
os.Exit(-1) os.Exit(-1)
} }
Wide.Autocomplete = true // default to true
// Logging Level // Logging Level
log.SetLevel(Wide.LogLevel) gulu.Log.SetLevel(Wide.LogLevel)
if "" != confLogLevel { if "" != confLogLevel {
Wide.LogLevel = confLogLevel Wide.LogLevel = confLogLevel
log.SetLevel(confLogLevel) gulu.Log.SetLevel(confLogLevel)
} }
logger.Debug("Conf: \n" + string(bytes)) logger.Debug("Conf: \n" + string(bytes))
// Working Driectory
Wide.WD = util.OS.Pwd()
logger.Debugf("${pwd} [%s]", Wide.WD)
// User Home // User Home
home, err := util.OS.Home() home, err := gulu.OS.Home()
if nil != err { if nil != err {
logger.Error("Can't get user's home, please report this issue to developer", err) logger.Error("Can't get user's home, please report this issue to developer", err)
os.Exit(-1) os.Exit(-1)
} }
logger.Debugf("${user.home} [%s]", home) logger.Debugf("${user.home} [%s]", home)
// Playground Directory // Data directory
Wide.Playground = strings.Replace(Wide.Playground, "${home}", home, 1) if "" != confData {
if "" != confPlayground { Wide.Data = confData
Wide.Playground = confPlayground
} }
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)
if !util.File.IsExist(Wide.Playground) { os.Exit(-1)
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
logger.Errorf("Create Playground [%s] error", err)
os.Exit(-1)
}
} }
if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
// IP os.Exit(-1)
ip, err := util.Net.LocalIP() }
if err != nil { if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
logger.Error(err) logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1) os.Exit(-1)
} }
logger.Debugf("${ip} [%s]", ip)
Docker = confDocker
if "" != confIP {
ip = confIP
}
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
if "" != confPort {
Wide.Port = confPort
}
// Server // Server
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
Wide.Server = strings.Replace(Wide.Server, "{Port}", Wide.Port, 1)
if "" != confServer { if "" != confServer {
Wide.Server = confServer Wide.Server = confServer
} }
// Static Server if "" != confReadOnly {
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1) Wide.ReadOnly, _ = strconv.ParseBool(confReadOnly)
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{Port}", Wide.Port, 1)
if "" != confStaticServer {
Wide.StaticServer = confStaticServer
} }
// Context // SiteStatCode
if "" != confContext { if "" != confSiteStatCode {
Wide.Context = confContext Wide.SiteStatCode = confSiteStatCode
} }
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", strconv.FormatInt(time.Now().UnixNano(), 10), 1) time := strconv.FormatInt(time.Now().UnixNano(), 10)
logger.Debugf("${time} [%s]", time)
// Channel Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
Wide.Channel = strings.Replace(Wide.Channel, "{IP}", Wide.IP, 1)
Wide.Channel = strings.Replace(Wide.Channel, "{Port}", Wide.Port, 1)
if "" != confChannel {
Wide.Channel = confChannel
}
} }
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes). // FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
@ -253,6 +252,8 @@ func FixedTimeCheckEnv() {
} }
func checkEnv() { func checkEnv() {
defer gulu.Panic.Recover(nil)
cmd := exec.Command("go", "version") cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
if nil != err { if nil != err {
@ -268,30 +269,30 @@ func checkEnv() {
os.Exit(-1) os.Exit(-1)
} }
gocode := util.Go.GetExecutableInGOBIN("gocode") gocode := gulu.Go.GetExecutableInGOBIN("gocode")
cmd = exec.Command(gocode) cmd = exec.Command(gocode)
_, err = cmd.Output() _, err = cmd.Output()
if nil != err { if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound} event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
logger.Warnf("Not found gocode [%s]", gocode) logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/stamblerre/gocode", gocode)
} }
ideStub := util.Go.GetExecutableInGOBIN("ide_stub") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
cmd = exec.Command(ideStub, "version") cmd = exec.Command(ideStub, "version")
_, err = cmd.Output() _, err = cmd.Output()
if nil != err { if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound} event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound}
logger.Warnf("Not found ide_stub [%s]", ideStub) logger.Warnf("Not found gotools [%s], please install it with this command: go get github.com/visualfc/gotools", ideStub)
} }
} }
// GetUserWorkspace gets workspace path with the specified username, returns "" if not found. // GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
func GetUserWorkspace(username string) string { func GetUserWorkspace(userId string) string {
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == userId {
return user.GetWorkspace() return user.WorkspacePath()
} }
} }
@ -299,14 +300,14 @@ func GetUserWorkspace(username string) string {
} }
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports". // GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
func GetGoFmt(username string) string { func GetGoFmt(userId string) string {
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == userId {
switch user.GoFormat { switch user.GoFormat {
case "gofmt": case "gofmt":
return "gofmt" return "gofmt"
case "goimports": case "goimports":
return util.Go.GetExecutableInGOBIN("goimports") return gulu.Go.GetExecutableInGOBIN("goimports")
default: default:
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat) logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
return "gofmt" return "gofmt"
@ -317,15 +318,14 @@ func GetGoFmt(username string) string {
return "gofmt" return "gofmt"
} }
// GetUser gets configuration of the user specified by the given username, returns nil if not found. // GetUser gets configuration of the user specified by the given user id, returns nil if not found.
func GetUser(username string) *User { func GetUser(id string) *User {
if "playground" == username { // reserved user for Playground if "playground" == id { // reserved user for Playground
// mock it return NewUser("playground", "playground", "", "")
return NewUser("playground", "", "", "")
} }
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == id {
return user return user
} }
} }
@ -336,17 +336,17 @@ func GetUser(username string) *User {
// initCustomizedConfs initializes the user customized configurations. // initCustomizedConfs initializes the user customized configurations.
func initCustomizedConfs() { func initCustomizedConfs() {
for _, user := range Users { for _, user := range Users {
UpdateCustomizedConf(user.Name) UpdateCustomizedConf(user.Id)
} }
} }
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files. // UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
// //
// 1. /static/user/{username}/style.css // 1. /static/users/{userId}/style.css
func UpdateCustomizedConf(username string) { func UpdateCustomizedConf(userId string) {
var u *User var u *User
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
if user.Name == username { if user.Id == userId {
u = user u = user
} }
} }
@ -364,8 +364,7 @@ func UpdateCustomizedConf(username string) {
os.Exit(-1) os.Exit(-1)
} }
wd := util.OS.Pwd() dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
dir := filepath.Clean(wd + "/static/user/" + u.Name)
if err := os.MkdirAll(dir, 0755); nil != err { if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err) logger.Error(err)
@ -395,7 +394,7 @@ func initWorkspaceDirs() {
paths := []string{} paths := []string{}
for _, user := range Users { for _, user := range Users {
paths = append(paths, filepath.SplitList(user.GetWorkspace())...) paths = append(paths, filepath.SplitList(user.WorkspacePath())...)
} }
for _, path := range paths { for _, path := range paths {
@ -418,7 +417,7 @@ func CreateWorkspaceDir(path string) {
// createDir creates a directory on the path if it not exists. // createDir creates a directory on the path if it not exists.
func createDir(path string) { func createDir(path string) {
if !util.File.IsExist(path) { if !gulu.File.IsExist(path) {
if err := os.MkdirAll(path, 0775); nil != err { if err := os.MkdirAll(path, 0775); nil != err {
logger.Error(err) logger.Error(err)

View File

@ -1,16 +1,16 @@
{ {
"IP": "${ip}", "Server": "http://127.0.0.1:7070",
"Port": "7070", "LogLevel": "debug",
"Context": "", "Data": "${home}/wide",
"Server": "{IP}:{Port}", "RuntimeMode": "prod",
"StaticServer": "", "HTTPSessionMaxAge": 86400,
"LogLevel": "info", "StaticResourceVersion": "${time}",
"Channel": "ws://{IP}:{Port}", "Locale": "zh_CN",
"HTTPSessionMaxAge": 86400, "SiteStatCode": "",
"StaticResourceVersion": "${time}", "ReadOnly": false,
"MaxProcs": 4, "OAuthLoginURL": "",
"RuntimeMode": "dev", "OAuthAccessTokenURL": "",
"WD": "${pwd}", "OAuthUserInfoURL": "",
"Locale": "en_US", "OAuthClientID": "",
"Playground": "${home}/playground" "OAuthClientSecret": ""
} }

View File

@ -1,24 +0,0 @@
#!/bin/bash
# see https://gist.github.com/hailiang/0f22736320abe6be71ce for more details
set -e
# Run test coverage on each subdirectories and merge the coverage profile.
echo "mode: count" > profile.cov
# Standard go tooling behavior is to ignore dirs with leading underscors
for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d);
do
if ls $dir/*.go &> /dev/null; then
go test -covermode=count -coverprofile=$dir/profile.tmp $dir
if [ -f $dir/profile.tmp ]
then
cat $dir/profile.tmp | tail -n +2 >> profile.cov
rm $dir/profile.tmp
fi
fi
done
go tool cover -func profile.cov

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,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -25,129 +25,63 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/file" "github.com/88250/wide/conf"
"github.com/b3log/wide/log" "github.com/88250/wide/file"
"github.com/b3log/wide/session" "github.com/88250/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// WSHandler handles request of creating editor channel.
// XXX: NOT used at present
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := httpSession.Values["id"].(string)
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
err := editorChan.WriteJSON(&ret)
if nil != err {
return
}
session.EditorWS[sid] = &editorChan
logger.Tracef("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS))
args := map[string]interface{}{}
for {
if err := session.EditorWS[sid].ReadJSON(&args); err != nil {
return
}
code := args["code"].(string)
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch)
logger.Tracef("offset: %d", offset)
gocode := util.Go.GetExecutableInGOBIN("gocode")
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 := session.EditorWS[sid].WriteJSON(&ret); err != nil {
logger.Error("Editor WS ERROR: " + err.Error())
return
}
}
}
// AutocompleteHandler handles request of code autocompletion. // AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) { func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{} 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 { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) 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 {
logger.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 {
logger.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) logger.Tracef("offset: %d", offset)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
libPath := "" libPath := ""
for _, workspace := range workspaces { for _, workspace := range workspaces {
@ -158,22 +92,17 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("gocode set lib-path [%s]", libPath) logger.Tracef("gocode set lib-path [%s]", libPath)
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
// FIXME: using gocode set lib-path has some issues while accrossing workspaces // FIXME: using gocode set lib-path has some issues while accrossing workspaces
gocode := util.Go.GetExecutableInGOBIN("gocode") exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
argv := []string{"set", "lib-path", libPath}
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 {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -184,29 +113,29 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
// GetExprInfoHandler handles request of getting expression infomation. // 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)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -216,7 +145,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -228,61 +157,61 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
ideStub := util.Go.GetExecutableInGOBIN("ide_stub") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."} argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, 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 {
logger.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. // 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)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -292,7 +221,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -304,24 +233,24 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
ideStub := util.Go.GetExecutableInGOBIN("ide_stub") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."} argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, 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 {
logger.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
} }
@ -329,45 +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, _ := 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:])
data["path"] = path 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. // 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)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -377,7 +310,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -388,36 +321,36 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
logger.Tracef("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
ideStub := util.Go.GetExecutableInGOBIN("ide_stub") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."} argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, 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 {
logger.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:])
@ -425,7 +358,7 @@ 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. // getCursorOffset calculates the cursor offset.
@ -454,8 +387,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
return offset return offset
} }
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, userId string) {
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(userId)
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace, "GOPATH="+userWorkspace,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -20,9 +20,9 @@ import (
"os" "os"
"os/exec" "os/exec"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/session" "github.com/88250/wide/conf"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// GoFmtHandler handles request of formatting Go source code. // GoFmtHandler handles request of formatting Go source code.
@ -31,30 +31,37 @@ import (
// 1. gofmt // 1. gofmt
// 2. goimports // 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)
session, _ := session.HTTPSession.Get(r, "wide-session") if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
filePath := args["file"].(string) filePath := args["file"].(string)
if util.Go.IsAPI(filePath) { if gulu.Go.IsAPI(filePath) {
// ignore it result.Code = -1
return return
} }
@ -62,7 +69,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -72,12 +79,19 @@ 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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
fmt := conf.GetGoFmt(username) 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(fmt, argv...) cmd := exec.Command(fmt, argv...)
@ -86,8 +100,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
output := string(bytes) output := string(bytes)
if "" == output { if "" == output {
// format error, returns the original content // format error, returns the original content
data["succ"] = true result.Code = 0
data["code"] = code
return return
} }
@ -99,7 +112,7 @@ 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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -18,7 +18,7 @@ package event
import ( import (
"os" "os"
"github.com/b3log/wide/log" "github.com/88250/gulu"
) )
const ( const (
@ -28,7 +28,7 @@ const (
EvtCodeGOROOTNotFound EvtCodeGOROOTNotFound
// EvtCodeGocodeNotFound indicates an event: not found gocode // EvtCodeGocodeNotFound indicates an event: not found gocode
EvtCodeGocodeNotFound EvtCodeGocodeNotFound
// EvtCodeIDEStubNotFound indicates an event: not found ide_stub // EvtCodeIDEStubNotFound indicates an event: not found gotools
EvtCodeIDEStubNotFound EvtCodeIDEStubNotFound
// EvtCodeServerInternalError indicates an event: server internal error // EvtCodeServerInternalError indicates an event: server internal error
EvtCodeServerInternalError EvtCodeServerInternalError
@ -38,7 +38,7 @@ const (
const maxQueueLength = 10 const maxQueueLength = 10
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Event represents an event. // Event represents an event.
type Event struct { type Event struct {
@ -69,6 +69,8 @@ var UserEventQueues = queues{}
// Load initializes the event handling. // Load initializes the event handling.
func Load() { func Load() {
go func() { go func() {
defer gulu.Panic.Recover(nil)
for event := range EventQueue { for event := range EventQueue {
logger.Debugf("Received a global event [code=%d]", event.Code) logger.Debugf("Received a global event [code=%d]", event.Code)
@ -84,21 +86,19 @@ func Load() {
// AddHandler adds the specified handlers to user event queues. // 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. // 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 {
logger.Warnf("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 *Event, maxQueueLength), Queue: make(chan *Event, maxQueueLength),
} }
@ -106,6 +106,8 @@ func (ueqs queues) New(sid string) *UserEventQueue {
ueqs[sid] = q ueqs[sid] = q
go func() { // start listening go func() { // start listening
defer gulu.Panic.Recover(nil)
for evt := range q.Queue { for evt := range q.Queue {
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code) logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
@ -121,12 +123,11 @@ func (ueqs queues) New(sid string) *UserEventQueue {
// Close closes a user event queue with the specified wide session id. // 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
}
delete(ueqs, sid) if q, ok := ueqs[sid]; ok {
close(q.Queue)
delete(ueqs, sid)
}
} }
// Handler represents an event handler. // Handler represents an event handler.

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -20,11 +20,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/b3log/wide/util" "github.com/88250/gulu"
) )
// GetZip handles request of retrieving zip file. // GetZipHandler handles request of retrieving zip file.
func GetZip(w http.ResponseWriter, r *http.Request) { func GetZipHandler(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query() q := r.URL.Query()
path := q["path"][0] path := q["path"][0]
@ -34,7 +34,7 @@ func GetZip(w http.ResponseWriter, r *http.Request) {
return return
} }
if !util.File.IsExist(path) { if !gulu.File.IsExist(path) {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
return return
@ -49,41 +49,54 @@ func GetZip(w http.ResponseWriter, r *http.Request) {
os.Remove(path) os.Remove(path)
} }
// CreateZip handles request of creating zip. // CreateZipHandler handles request of creating zip.
func CreateZip(w http.ResponseWriter, r *http.Request) { func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := gulu.Ret.NewResult()
defer util.RetJSON(w, r, data) defer gulu.Ret.RetResult(w, r, data)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false data.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
var name string
base := filepath.Base(path) base := filepath.Base(path)
if !util.File.IsExist(path) { if nil != args["name"] {
data["succ"] = false name = args["name"].(string)
data["msg"] = "Can't find file [" + path + "]" } else {
name = base
}
dir := filepath.Dir(path)
if !gulu.File.IsExist(path) {
data.Code = -1
data.Msg = "Can't find file [" + path + "]"
return return
} }
zipFile, err := util.Zip.Create(path + ".zip") zipPath := filepath.Join(dir, name)
zipFile, err := gulu.Zip.Create(zipPath + ".zip")
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false data.Code = -1
return return
} }
defer zipFile.Close() defer zipFile.Close()
if util.File.IsDir(path) { if gulu.File.IsDir(path) {
zipFile.AddDirectory(base, path) zipFile.AddDirectory(base, path)
} else { } else {
zipFile.AddEntry(base, path) zipFile.AddEntry(base, path)
} }
data.Data = zipPath
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -24,21 +24,22 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/event" "github.com/88250/wide/conf"
"github.com/b3log/wide/log" "github.com/88250/wide/event"
"github.com/b3log/wide/session" "github.com/88250/wide/session"
"github.com/b3log/wide/util"
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Node represents a file node in file tree. // Node represents a file node in file tree.
type Node struct { type Node struct {
Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
IconSkin string `json:"iconSkin"` // Value should be end with a space IconSkin string `json:"iconSkin"` // Value should be end with a space
IsParent bool `json:"isParent"`
Type string `json:"type"` // "f": file, "d": directory Type string `json:"type"` // "f": file, "d": directory
Creatable bool `json:"creatable"` // whether can create file in this file node Creatable bool `json:"creatable"` // whether can create file in this file node
Removable bool `json:"removable"` // whether can remove this file node Removable bool `json:"removable"` // whether can remove this file node
@ -59,7 +60,7 @@ var apiNode *Node
// initAPINode builds the Go API file node. // initAPINode builds the Go API file node.
func initAPINode() { func initAPINode() {
apiPath := util.Go.GetAPIPath() apiPath := gulu.Go.GetAPIPath()
apiNode = &Node{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d", apiNode = &Node{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
Creatable: false, Removable: false, IsGoAPI: true, Children: []*Node{}} Creatable: false, Removable: false, IsGoAPI: true, Children: []*Node{}}
@ -67,26 +68,26 @@ func initAPINode() {
walk(apiPath, apiNode, false, false, true) walk(apiPath, apiNode, false, false, true)
} }
// GetFiles handles request of constructing user workspace file tree. // GetFilesHandler handles request of constructing user workspace file tree.
// //
// The Go API source code package also as a child node, // The Go API source code package also as a child node,
// so that users can easily view the Go API source code in file tree. // so that users can easily view the Go API source code in file tree.
func GetFiles(w http.ResponseWriter, r *http.Request) { func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetGzJSON(w, r, data) if httpSession.IsNew {
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := httpSession.Values["uid"].(string)
userWorkspace := conf.GetUserWorkspace(username) result := gulu.Ret.NewResult()
defer gulu.Ret.RetGzResult(w, r, result)
userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}} root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}}
if nil == apiNode { // lazy init if nil == apiNode { // lazy init
initAPINode() initAPINode()
@ -96,9 +97,16 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
for _, workspace := range workspaces { for _, workspace := range workspaces {
workspacePath := workspace + conf.PathSeparator + "src" workspacePath := workspace + conf.PathSeparator + "src"
workspaceNode := Node{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:], workspaceNode := Node{
Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d", Id: filepath.ToSlash(workspacePath), // jQuery API can't accept "\", so we convert it to "/"
Creatable: true, Removable: false, IsGoAPI: false, Children: []*Node{}} Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
Path: filepath.ToSlash(workspacePath),
IconSkin: "ico-ztree-dir-workspace ",
Type: "d",
Creatable: true,
Removable: false,
IsGoAPI: false,
Children: []*Node{}}
walk(workspacePath, &workspaceNode, true, true, false) walk(workspacePath, &workspaceNode, true, true, false)
@ -109,14 +117,28 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
// add Go API node // add Go API node
root.Children = append(root.Children, apiNode) root.Children = append(root.Children, apiNode)
data["root"] = root result.Data = root
} }
// RefreshDirectory handles request of refresh a directory of file tree. // RefreshDirectoryHandler handles request of refresh a directory of file tree.
func RefreshDirectory(w http.ResponseWriter, r *http.Request) { func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
r.ParseForm() r.ParseForm()
path := r.FormValue("path") path := r.FormValue("path")
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
node := Node{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}} node := Node{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
walk(path, &node, true, true, false) walk(path, &node, true, true, false)
@ -131,76 +153,106 @@ func RefreshDirectory(w http.ResponseWriter, r *http.Request) {
w.Write(data) w.Write(data)
} }
// GetFile handles request of opening file by editor. // GetFileHandler handles request of opening file by editor.
func GetFile(w http.ResponseWriter, r *http.Request) { func GetFileHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
size := util.File.GetFileSize(path) if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
if size > 5242880 { // 5M http.Error(w, "Forbidden", http.StatusForbidden)
data["succ"] = false
data["msg"] = "This file is too large to open :("
return return
} }
size := gulu.File.GetFileSize(path)
if size > 5242880 { // 5M
result.Code = -1
result.Msg = "This file is too large to open :("
return
}
data := map[string]interface{}{}
result.Data = &data
buf, _ := ioutil.ReadFile(path) buf, _ := ioutil.ReadFile(path)
extension := filepath.Ext(path) extension := filepath.Ext(path)
if util.File.IsImg(extension) { if gulu.File.IsImg(extension) {
// image file will be open in a browser tab // image file will be open in a browser tab
data["mode"] = "img" data["mode"] = "img"
username := conf.GetOwner(path) userId := conf.GetOwner(path)
if "" == username { if "" == userId {
logger.Warnf("The path [%s] has no owner") logger.Warnf("The path [%s] has no owner", path)
data["path"] = "" data["path"] = ""
return return
} }
user := conf.GetUser(username) user := conf.GetUser(uid)
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1) data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.WorkspacePath(), "", 1)
return return
} }
content := string(buf) content := string(buf)
if util.File.IsBinary(content) { if gulu.File.IsBinary(content) {
data["succ"] = false result.Code = -1
data["msg"] = "Can't open a binary file :(" result.Msg = "Can't open a binary file :("
} else { } else {
data["content"] = content data["content"] = content
data["mode"] = getEditorMode(extension)
data["path"] = path data["path"] = path
} }
} }
// SaveFile handles request of saving file. // SaveFileHandler handles request of saving file.
func SaveFile(w http.ResponseWriter, r *http.Request) { func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
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{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -208,11 +260,17 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
sid := args["sid"].(string) sid := args["sid"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fout, err := os.Create(filePath) fout, err := os.Create(filePath)
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -223,7 +281,7 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
@ -233,28 +291,43 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
} }
} }
// NewFile handles request of creating file or directory. // NewFileHandler handles request of creating file or directory.
func NewFile(w http.ResponseWriter, r *http.Request) { func NewFileHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fileType := args["fileType"].(string) fileType := args["fileType"].(string)
sid := args["sid"].(string) sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !createFile(path, fileType) { if !createFile(path, fileType) {
data["succ"] = false result.Code = -1
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't create file " + path} Data: "can't create file " + path}
@ -263,64 +336,110 @@ func NewFile(w http.ResponseWriter, r *http.Request) {
} }
if "f" == fileType { if "f" == fileType {
extension := filepath.Ext(path) logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
data["mode"] = getEditorMode(extension) } else {
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
} }
} }
// RemoveFile handles request of removing file or directory. // RemoveFileHandler handles request of removing file or directory.
func RemoveFile(w http.ResponseWriter, r *http.Request) { func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := args["sid"].(string) sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !removeFile(path) { if !removeFile(path) {
data["succ"] = false result.Code = -1
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't remove file " + path} Data: "can't remove file " + path}
return
} }
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
} }
// RenameFile handles request of renaming file or directory. // RenameFileHandler handles request of renaming file or directory.
func RenameFile(w http.ResponseWriter, r *http.Request) { func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
oldPath := args["oldPath"].(string) oldPath := args["oldPath"].(string)
if gulu.Go.IsAPI(oldPath) ||
!session.CanAccess(uid, oldPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
newPath := args["newPath"].(string) newPath := args["newPath"].(string)
if gulu.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := args["sid"].(string) sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !renameFile(oldPath, newPath) { if !renameFile(oldPath, newPath) {
data["succ"] = false result.Code = -1
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't rename file " + oldPath} Data: "can't rename file " + oldPath}
return
} }
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.UserId)
} }
// Use to find results sorting. // Use to find results sorting.
@ -335,34 +454,40 @@ func (f foundPaths) Len() int { return len(f) }
func (f foundPaths) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f foundPaths) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score } func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
// Find handles request of find files under the specified directory with the specified filename pattern. // FindHandler handles request of find files under the specified directory with the specified filename pattern.
func Find(w http.ResponseWriter, r *http.Request) { func FindHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
path := args["path"].(string) // path of selected file in file tree path := args["path"].(string) // path of selected file in file tree
name := args["name"].(string) if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string)
userWorkspace := conf.GetUserWorkspace(username) name := args["name"].(string)
userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
if "" != path && !util.File.IsDir(path) { if "" != path && !gulu.File.IsDir(path) {
path = filepath.Dir(path) path = filepath.Dir(path)
} }
@ -372,27 +497,34 @@ func Find(w http.ResponseWriter, r *http.Request) {
rs := find(workspace+conf.PathSeparator+"src", name, []*string{}) rs := find(workspace+conf.PathSeparator+"src", name, []*string{})
for _, r := range rs { for _, r := range rs {
substr := util.Str.LCS(path, *r) substr := gulu.Str.LCS(path, *r)
founds = append(founds, &foundPath{Path: *r, score: len(substr)}) founds = append(founds, &foundPath{Path: filepath.ToSlash(*r), score: len(substr)})
} }
} }
sort.Sort(founds) sort.Sort(founds)
data["founds"] = founds result.Data = founds
} }
// SearchText handles request of searching files under the specified directory with the specified keyword. // SearchTextHandler handles request of searching files under the specified directory with the specified keyword.
func SearchText(w http.ResponseWriter, r *http.Request) { func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
defer util.RetJSON(w, r, data) if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -400,7 +532,7 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string) sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if nil == wSession { if nil == wSession {
data["succ"] = false result.Code = -1
return return
} }
@ -409,7 +541,7 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
dir := args["dir"].(string) dir := args["dir"].(string)
if "" == dir { if "" == dir {
userWorkspace := conf.GetUserWorkspace(wSession.Username) userWorkspace := conf.GetUserWorkspace(wSession.UserId)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
dir = workspaces[0] dir = workspaces[0]
} }
@ -418,13 +550,13 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
text := args["text"].(string) text := args["text"].(string)
founds := []*Snippet{} founds := []*Snippet{}
if util.File.IsDir(dir) { if gulu.File.IsDir(dir) {
founds = search(dir, extension, text, []*Snippet{}) founds = search(dir, extension, text, []*Snippet{})
} else { } else {
founds = searchInFile(dir, text) founds = searchInFile(dir, text)
} }
data["founds"] = founds result.Data = founds
} }
// walk traverses the specified path to build a file tree. // walk traverses the specified path to build a file tree.
@ -436,7 +568,13 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
fio, _ := os.Lstat(fpath) fio, _ := os.Lstat(fpath)
child := Node{Name: filename, Path: fpath, Removable: removable, IsGoAPI: isGOAPI, Children: []*Node{}} child := Node{
Id: filepath.ToSlash(fpath), // jQuery API can't accept "\", so we convert it to "/"
Name: filename,
Path: filepath.ToSlash(fpath),
Removable: removable,
IsGoAPI: isGOAPI,
Children: []*Node{}}
node.Children = append(node.Children, &child) node.Children = append(node.Children, &child)
if nil == fio { if nil == fio {
@ -449,6 +587,7 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
child.Type = "d" child.Type = "d"
child.Creatable = creatable child.Creatable = creatable
child.IconSkin = "ico-ztree-dir " child.IconSkin = "ico-ztree-dir "
child.IsParent = true
walk(fpath, &child, creatable, removable, isGOAPI) walk(fpath, &child, creatable, removable, isGOAPI)
} else { } else {
@ -457,7 +596,6 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
ext := filepath.Ext(fpath) ext := filepath.Ext(fpath)
child.IconSkin = getIconSkin(ext) child.IconSkin = getIconSkin(ext)
child.Mode = getEditorMode(ext)
} }
} }
@ -488,13 +626,18 @@ func listFiles(dirname string) []string {
} }
if fio.IsDir() { if fio.IsDir() {
// exclude the .git direcitory // exclude the .git, .svn, .hg direcitory
if ".git" == fio.Name() { if ".git" == fio.Name() || ".svn" == fio.Name() || ".hg" == fio.Name() {
continue continue
} }
dirs = append(dirs, name) dirs = append(dirs, name)
} else { } else {
// exclude the .DS_Store directory on Mac OS X
if ".DS_Store" == fio.Name() {
continue
}
files = append(files, name) files = append(files, name)
} }
} }
@ -506,7 +649,7 @@ func listFiles(dirname string) []string {
// //
// Refers to the zTree document for CSS class names. // Refers to the zTree document for CSS class names.
func getIconSkin(filenameExtension string) string { func getIconSkin(filenameExtension string) string {
if util.File.IsImg(filenameExtension) { if gulu.File.IsImg(filenameExtension) {
return "ico-ztree-img " return "ico-ztree-img "
} }
@ -534,34 +677,6 @@ func getIconSkin(filenameExtension string) string {
} }
} }
// getEditorMode gets editor mode with the specified filename extension.
//
// Refers to the CodeMirror document for modes.
func getEditorMode(filenameExtension string) string {
switch filenameExtension {
case ".go":
return "text/x-go"
case ".html":
return "text/html"
case ".md":
return "text/x-markdown"
case ".js":
return "text/javascript"
case ".json":
return "application/json"
case ".css":
return "text/css"
case ".xml":
return "application/xml"
case ".sh":
return "text/x-sh"
case ".sql":
return "text/x-sql"
default:
return "text/plain"
}
}
// createFile creates file on the specified path. // createFile creates file on the specified path.
// //
// fileType: // fileType:
@ -580,7 +695,7 @@ func createFile(path, fileType string) bool {
defer file.Close() defer file.Close()
logger.Debugf("Created file [%s]", path) logger.Tracef("Created file [%s]", path)
return true return true
case "d": case "d":
@ -592,7 +707,7 @@ func createFile(path, fileType string) bool {
return false return false
} }
logger.Debugf("Created directory [%s]", path) logger.Tracef("Created directory [%s]", path)
return true return true
default: default:
@ -610,7 +725,7 @@ func removeFile(path string) bool {
return false return false
} }
logger.Debugf("Removed [%s]", path) logger.Tracef("Removed [%s]", path)
return true return true
} }
@ -623,7 +738,7 @@ func renameFile(oldPath, newPath string) bool {
return false return false
} }
logger.Debugf("Renamed [%s] to [%s]", oldPath, newPath) logger.Tracef("Renamed [%s] to [%s]", oldPath, newPath)
return true return true
} }
@ -653,7 +768,7 @@ func find(dir, name string, results []*string) []*string {
path := dir + fname path := dir + fname
if fileInfo.IsDir() { if fileInfo.IsDir() {
if util.Str.Contains(fname, defaultExcludesFind) { if gulu.Str.Contains(fname, defaultExcludesFind) {
continue continue
} }
@ -726,7 +841,7 @@ func searchInFile(path string, text string) []*Snippet {
} }
content := string(bytes) content := string(bytes)
if util.File.IsBinary(content) { if gulu.File.IsBinary(content) {
return ret return ret
} }
@ -736,7 +851,8 @@ func searchInFile(path string, text string) []*Snippet {
ch := strings.Index(strings.ToLower(line), strings.ToLower(text)) ch := strings.Index(strings.ToLower(line), strings.ToLower(text))
if -1 != ch { if -1 != ch {
snippet := &Snippet{Path: path, Line: idx + 1, Ch: ch + 1, Contents: []string{line}} snippet := &Snippet{Path: filepath.ToSlash(path),
Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
ret = append(ret, snippet) ret = append(ret, snippet)
} }

View File

@ -1,77 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 (
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"github.com/b3log/wide/util"
)
type fileInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
}
func handleUpload(p *multipart.Part, dir string) (fi *fileInfo) {
fi = &fileInfo{
Name: p.FileName(),
Type: p.Header.Get("Content-Type"),
}
path := filepath.Clean(dir + "/" + fi.Name)
f, _ := os.Create(path)
io.Copy(f, p)
f.Close()
return
}
func handleUploads(r *http.Request, dir string) (fileInfos []*fileInfo) {
fileInfos = make([]*fileInfo, 0)
mr, err := r.MultipartReader()
part, err := mr.NextPart()
for err == nil {
if name := part.FormName(); name != "" {
if part.FileName() != "" {
fileInfos = append(fileInfos, handleUpload(part, dir))
}
}
part, err = mr.NextPart()
}
return
}
// Upload handles request of file upload.
func Upload(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
q := r.URL.Query()
dir := q["path"][0]
data["files"] = handleUploads(r, dir)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -23,7 +23,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/b3log/wide/util" "github.com/88250/gulu"
) )
type element struct { type element struct {
@ -32,16 +32,16 @@ type element struct {
Ch int Ch int
} }
// GetOutline gets outfile of a go file. // GetOutlineHandler gets outfile of a go file.
func GetOutline(w http.ResponseWriter, r *http.Request) { func GetOutlineHandler(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)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -51,11 +51,14 @@ func GetOutline(w http.ResponseWriter, r *http.Request) {
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", code, 0) f, err := parser.ParseFile(fset, "", code, 0)
if err != nil { if err != nil {
data["succ"] = false result.Code = -1
return return
} }
data := map[string]interface{}{}
result.Data = &data
// ast.Print(fset, f) // ast.Print(fset, f)
line, ch := getCursor(code, int(f.Name.Pos())) line, ch := getCursor(code, int(f.Name.Pos()))

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,7 +1,7 @@
{ {
"colon": ": ", "colon": ": ",
"wide": "Wide", "wide": "Wide",
"wide_title": "Team development, anytime, anywhere", "wide_title": "Playing golang, anytime, anywhere",
"cancel": "Cancel", "cancel": "Cancel",
"file": "File", "file": "File",
"login": "Login", "login": "Login",
@ -28,7 +28,6 @@
"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",
@ -102,9 +101,6 @@
"start-install": "START [go install]", "start-install": "START [go install]",
"install-succ": "[go install] SUCCESS", "install-succ": "[go install] SUCCESS",
"install-error": "[go install] ERROR", "install-error": "[go install] ERROR",
"start-get": "START [go get]",
"get-succ": "[go get] SUCCESS",
"get-error": "[go get] ERROR",
"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",
@ -118,13 +114,12 @@
"team": "Team", "team": "Team",
"sing_up_error": "Sign Up Error", "sing_up_error": "Sign Up Error",
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16", "user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
"invalid_email": "Invalid Email",
"password_no_match": "Password doesn't match the confirmation", "password_no_match": "Password doesn't match the confirmation",
"discard": "Discard", "discard": "Discard",
"close": "Close", "close": "Close",
"close_other": "Close Other", "close_other": "Close Other",
"clear": "Clear", "clear": "Clear",
"perference": "Perference", "preference": "Preference",
"appearence": "Appearence", "appearence": "Appearence",
"gotool": "Go Tool", "gotool": "Go Tool",
"user": "User", "user": "User",
@ -137,7 +132,6 @@
"clearOutput": "Clear Output", "clearOutput": "Clear Output",
"export": "Export", "export": "Export",
"refresh": "Refresh", "refresh": "Refresh",
"import": "Import",
"theme": "Theme", "theme": "Theme",
"tab_size": "Tab Size", "tab_size": "Tab Size",
"copy_file_path": "Copy File Path", "copy_file_path": "Copy File Path",
@ -156,11 +150,8 @@
"source": "Source", "source": "Source",
"toggle_comment": "Toggle Comment", "toggle_comment": "Toggle Comment",
"find_in_files": "Find in Files", "find_in_files": "Find in Files",
"email": "Email",
"no_empty": "Can not Empty!", "no_empty": "Can not Empty!",
"change_avatar": "Avatar modify go",
"open": "Open", "open": "Open",
"pricing": "Pricing",
"search_no_match": "No matching files were found.", "search_no_match": "No matching files were found.",
"outline": "Outline", "outline": "Outline",
"govet": "go vet", "govet": "go vet",
@ -170,6 +161,11 @@
"restore_outline": "Restore Outline", "restore_outline": "Restore Outline",
"share": "Share", "share": "Share",
"url": "URL", "url": "URL",
"short_url": "Short URL", "embeded": "Embeded",
"embeded": "Embeded" "terms": "Terms",
"download": "Download",
"decompress": "Decompress",
"keymap": "Keymap",
"resize": "Resize",
"sponsor": "Sponsor"
} }

View File

@ -1,7 +1,7 @@
{ {
"colon": "", "colon": "",
"wide": "Wide", "wide": "Wide",
"wide_title": "チーム開発、いつでも、どこでも", "wide_title": "いつでも、どこでもゴランをプレイする",
"cancel": "取消", "cancel": "取消",
"file": "ファイル", "file": "ファイル",
"login": "ログイン", "login": "ログイン",
@ -28,7 +28,6 @@
"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": "ビルド実行",
@ -102,9 +101,6 @@
"start-install": "[go install] 開始", "start-install": "[go install] 開始",
"install-succ": "[go install] 成功", "install-succ": "[go install] 成功",
"install-error": "[go install] 失敗", "install-error": "[go install] 失敗",
"start-get": "[go get] 開始",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失敗",
"check_version": "更新をチェック中", "check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります", "new_version_available": "新しいバージョンがあります",
"go_env": "Go", "go_env": "Go",
@ -118,18 +114,17 @@
"team": "チーム", "team": "チーム",
"sing_up_error": "登録に失敗しました", "sing_up_error": "登録に失敗しました",
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ", "user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
"invalid_email": "無効な電子メール",
"password_no_match": "一貫性のないパスワード入力", "password_no_match": "一貫性のないパスワード入力",
"discard": "あきらめる", "discard": "あきらめる",
"close": "クローズ", "close": "クローズ",
"close_other": "閉じるその他", "close_other": "閉じるその他",
"clear": "空の", "clear": "空の",
"perference": "偏好设定", "preference": "環境設定",
"appearence": "エクステリア", "appearence": "エクステリア",
"gotool": "Go ツール", "gotool": "Go ツール",
"user": "ユーザー", "user": "ユーザー",
"font": "フォント", "font": "フォント",
"font_size": "フォント·サイズ", "font_size": "フォントサイズ",
"line_height": "行の高さ", "line_height": "行の高さ",
"go_format": "Go フォーマット", "go_format": "Go フォーマット",
"locale": "ロケール", "locale": "ロケール",
@ -137,9 +132,8 @@
"clearOutput": "空の出力", "clearOutput": "空の出力",
"export": "輸出", "export": "輸出",
"refresh": "リフレッシュ", "refresh": "リフレッシュ",
"import": "インポート",
"theme": "テーマ", "theme": "テーマ",
"tab_size": "Tab·サイズ", "tab_size": "Tab サイズ",
"copy_file_path": "ファイルパスをコピー", "copy_file_path": "ファイルパスをコピー",
"file_tree": "ファイルツリー", "file_tree": "ファイルツリー",
"select": "選択する", "select": "選択する",
@ -148,7 +142,7 @@
"edit": "編集", "edit": "編集",
"undo": "元に戻す", "undo": "元に戻す",
"redo": "やり直し", "redo": "やり直し",
"cut": "Cut", "cut": "切り取り",
"copy": "コピー", "copy": "コピー",
"paste": "貼り付け", "paste": "貼り付け",
"select_all": "すべて選択", "select_all": "すべて選択",
@ -156,20 +150,22 @@
"source": "ソース", "source": "ソース",
"toggle_comment": "トグルコメント", "toggle_comment": "トグルコメント",
"find_in_files": "ファイルから検索", "find_in_files": "ファイルから検索",
"email": "Eメール",
"no_empty": "空ではありません", "no_empty": "空ではありません",
"change_avatar": "アバターの変更は行く",
"open": "オープン", "open": "オープン",
"pricing": "价格",
"search_no_match": "一致するファイルが見つかりませんでした。", "search_no_match": "一致するファイルが見つかりませんでした。",
"outline": "アウトライン", "outline": "アウトライン",
"govet": "go vet", "govet": "go vet",
"start-vet": "START [go vet]", "start-vet": "[go vet] 開始",
"vet-succ": "[go vet] SUCCESS", "vet-succ": "[go vet] 成功",
"vet-error": "[go vet] ERROR", "vet-error": "[go vet] 失敗",
"restore_outline": "アウトラインを復元", "restore_outline": "アウトラインを復元",
"share": "シェア", "share": "シェア",
"url": "リンク", "url": "リンク",
"short_url": "ショートリンク", "embeded": "埋め込む",
"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,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -17,16 +17,15 @@ package i18n
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"os" "os"
"sort" "sort"
"strings" "strings"
"github.com/b3log/wide/log" "github.com/88250/gulu"
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Locale. // Locale.
type locale struct { type locale struct {
@ -44,6 +43,10 @@ func Load() {
names, _ := f.Readdirnames(-1) names, _ := f.Readdirnames(-1)
f.Close() f.Close()
if len(Locales) == len(names)-1 {
return
}
for _, name := range names { for _, name := range names {
if !strings.HasSuffix(name, ".json") { if !strings.HasSuffix(name, ".json") {
continue continue
@ -55,7 +58,7 @@ func Load() {
} }
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 {
logger.Error(err) logger.Error(err)

View File

@ -1,7 +1,7 @@
{ {
"colon": "", "colon": "",
"wide": "Wide", "wide": "Wide",
"wide_title": "团队开发,随时随地", "wide_title": "随时随地玩 golang",
"cancel": "取消", "cancel": "取消",
"file": "文件", "file": "文件",
"login": "登录", "login": "登录",
@ -28,7 +28,6 @@
"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": "构建并运行",
@ -102,9 +101,6 @@
"start-install": "开始 [go install]", "start-install": "开始 [go install]",
"install-succ": "[go install] 成功", "install-succ": "[go install] 成功",
"install-error": "[go install] 失败", "install-error": "[go install] 失败",
"start-get": "开始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失败",
"check_version": "正在检查更新", "check_version": "正在检查更新",
"new_version_available": "新版本可用", "new_version_available": "新版本可用",
"go_env": "Go 环境", "go_env": "Go 环境",
@ -118,13 +114,12 @@
"team": "团队", "team": "团队",
"sing_up_error": "注册失败", "sing_up_error": "注册失败",
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成长度为16", "user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成长度为16",
"invalid_email": "无效的电子邮件",
"password_no_match": "密码输入不一致", "password_no_match": "密码输入不一致",
"discard": "放弃", "discard": "放弃",
"close": "关闭", "close": "关闭",
"close_other": "关闭其它", "close_other": "关闭其它",
"clear": "清空", "clear": "清空",
"perference": "偏好设定", "preference": "偏好设定",
"appearence": "外观", "appearence": "外观",
"gotool": "Go 工具", "gotool": "Go 工具",
"user": "用户", "user": "用户",
@ -137,7 +132,6 @@
"clearOutput": "清空输出", "clearOutput": "清空输出",
"export": "导出", "export": "导出",
"refresh": "刷新", "refresh": "刷新",
"import": "导入",
"theme": "主题", "theme": "主题",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"copy_file_path": "复制文件路径", "copy_file_path": "复制文件路径",
@ -156,20 +150,22 @@
"source": "源码", "source": "源码",
"toggle_comment": "注释", "toggle_comment": "注释",
"find_in_files": "在文件中查找", "find_in_files": "在文件中查找",
"email": "电子邮件",
"no_empty": "不能为空", "no_empty": "不能为空",
"change_avatar": "头像修改请到",
"open": "打开", "open": "打开",
"pricing": "价格",
"search_no_match": "没有发现匹配的文件。", "search_no_match": "没有发现匹配的文件。",
"outline": "大纲", "outline": "大纲",
"govet": "go vet", "govet": "go vet",
"start-vet": "START [go vet]", "start-vet": "开始 [go vet]",
"vet-succ": "[go vet] SUCCESS", "vet-succ": "[go vet] 成功",
"vet-error": "[go vet] ERROR", "vet-error": "[go vet] 失败",
"restore_outline": "恢复大纲", "restore_outline": "恢复大纲",
"share": "分享", "share": "分享",
"url": "链接", "url": "链接",
"short_url": "短链接", "embeded": "嵌入",
"embeded": "嵌入" "terms": "使用条款",
"download": "下载",
"decompress": "解压缩",
"keymap": "快捷键",
"resize": "调整大小",
"sponsor": "赞助"
} }

View File

@ -1,110 +1,106 @@
{ {
"colon": "", "colon": "",
"wide": "Wide", "wide": "Wide",
"wide_title": "團隊開發,隨時隨地", "wide_title": "隨時隨地玩 golang",
"cancel": "取消", "cancel": "取消",
"file": "文件檔", "file": "",
"login": "登", "login": "登",
"username": "用户名字", "username": "使用者",
"current_user": "當前用户名", "current_user": "當前使用者",
"current_session": "當前會話", "current_session": "當前會話",
"password": "密碼", "password": "密碼",
"login_error": "登錄失败", "login_error": "登入失敗",
"run": "執行", "run": "執行",
"debug": "除蟲", "debug": "Debug",
"help": "說明書", "help": "說明書",
"check_update": "程式更新?", "check_update": "檢查更新?",
"issues": "問題", "issues": "問題",
"wide_doc": "Wide 說明文件", "wide_doc": "Wide 說明",
"about": "關於", "about": "關於",
"start_page": "起始頁", "start_page": "開始頁面",
"create_file": "建立文件", "create_file": "開新檔案",
"create": "", "create": "建",
"create_dir": "建立目錄", "create_dir": "新增資料夾",
"delete": "删除", "delete": "删除",
"rename": "重命名", "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": "編譯並執行",
"editor": "編輯器", "editor": "編輯器",
"max_editor": "編輯器窗口最大化", "max_editor": "編輯器最大化",
"restore_editor": "編輯器窗口還原", "restore_editor": "編輯器還原",
"unread_notification": "未讀通知", "unread_notification": "未讀通知",
"notification_2": "没有檢查到 gocode這將會導致 [自動完成] 失效", "notification_2": "没有檢查到 gocode這將會導致「自動完成」失效",
"notification_3": "没有檢查到 ide_stub這將會導致 [跳轉到聲明]、[查找使用] 失效", "notification_3": "没有檢查到 ide_stub這將會導致「跳轉到聲明」、「查找使用」失效",
"notification_4": "器內部錯誤", "notification_4": "服器內部錯誤",
"goto_line": "跳轉到行", "goto_line": "跳轉到行",
"goto_file": "打開文件", "goto_file": "開啟舊檔",
"go": "跳到", "go": "跳到",
"tip": "提示", "tip": "提示",
"confirm": "確定", "confirm": "確定",
"stop": "停止", "stop": "停止",
"output": "出", "output": "出",
"search": "搜", "search": "搜",
"notification": "通知", "notification": "通知",
"min": "最小", "min": "縮到最小",
"restore_side": "左側還原", "restore_side": "左側窗還原",
"search_text": "尋找文本", "search_text": "尋找",
"find": "尋找", "find": "尋找",
"find_next": "尋找下一個", "find_next": "尋找下一個",
"find_previous": "尋找上一個", "find_previous": "尋找上一個",
"replace": "替換", "replace": "取代",
"replace_all": "替換全部", "replace_all": "取代全部",
"restore_bottom": "底部還原", "restore_bottom": "底部窗還原",
"file_format": "文件格式", "file_format": "文件格式",
"keyword": "關鍵字", "keyword": "關鍵字",
"user_guide": "用户指南", "user_guide": "使用者說明文件",
"dev_guide": "開發指南", "dev_guide": "開發說明文件",
"keyboard_shortcuts": "鍵盤快捷鍵", "keyboard_shortcuts": "鍵盤快捷鍵",
"ver": "版本", "ver": "版本",
"current_ver": "當前版本", "current_ver": "當前版本",
"dev_team": "開發團隊", "dev_team": "開發團隊",
"donate_us": "愛心捐", "donate_us": "愛心捐",
"confirm_save": "請確認所有文件已保存", "confirm_save": "請確認所有檔案都已儲存",
"workspace": "工作空間", "workspace": "工作空間",
"project_address": "項目地址", "project_address": "項目地址",
"community": "社區", "community": "社區",
"autocomplete": "自動補全", "autocomplete": "自動完成",
"jump_to_decl": "跳轉到聲明", "jump_to_decl": "跳轉到聲明",
"show_expr_info": "查看表達式信息", "show_expr_info": "查看表達式信息",
"find_usages": "尋找使用", "find_usages": "尋找使用",
"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_editor_file": "儲存當前編輯檔案",
"save_all_editors_files": "保存所有編輯器文件", "save_all_editors_files": "儲存所有檔案",
"close_editor": "關閉當前編輯器", "close_editor": "關閉當前編輯器",
"full_screen": "編輯器全螢幕", "full_screen": "全螢幕",
"auto_indent": "自動縮進", "auto_indent": "自動縮進",
"indent": "縮進", "indent": "縮進",
"unindent": "縮進還原", "unindent": "縮進還原",
"focus": "焦點", "focus": "焦點",
"switch_tab": "切換編輯器/窗口组 tab", "switch_tab": "切換編輯器/視窗组 tab",
"focus_editor": "焦點切換到編輯器", "focus_editor": "切換至編輯器",
"focus_file_tree": "焦點切換到文件樹", "focus_file_tree": "切換至檔案樹",
"focus_output": "焦點切換到输出窗口", "focus_output": "切換至输出視窗",
"focus_search": "焦點切換到搜索窗口", "focus_search": "切換至搜索視窗",
"focus_notification": "焦點切換到通知窗口", "focus_notification": "切換至通知視窗",
"start-build": "開始 [go build]", "start-build": "開始 [go build]",
"build-succ": "[go build] 成功", "build-succ": "[go build] 成功",
"build-error": "[go build] 失", "build-error": "[go build] 失",
"start-test": "開始 [go test]", "start-test": "開始 [go test]",
"test-succ": "[go test] 成功", "test-succ": "[go test] 成功",
"test-error": "[go test] 失", "test-error": "[go test] 失",
"start-install": "開始 [go install]", "start-install": "開始 [go install]",
"install-succ": "[go install] 成功", "install-succ": "[go install] 成功",
"install-error": "[go install] 失败", "install-error": "[go install] 失敗",
"start-get": "開始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失败",
"check_version": "正在檢查更新", "check_version": "正在檢查更新",
"new_version_available": "可用新版本", "new_version_available": "可用新版本",
"go_env": "Go 環境", "go_env": "Go 環境",
@ -117,18 +113,17 @@
"sign_up": "註冊", "sign_up": "註冊",
"team": "團隊", "team": "團隊",
"sing_up_error": "註冊失敗", "sing_up_error": "註冊失敗",
"user_name_ruler": "用戶名只能由az, AZ, 0-9, _ 組成長度為16", "user_name_ruler": "帳號只能由 az, AZ, 0-9, _ 組成長度為16",
"invalid_email": "無效的電子郵件",
"password_no_match": "密碼輸入不一致", "password_no_match": "密碼輸入不一致",
"discard": "棄", "discard": "棄",
"close": "關閉", "close": "關閉",
"close_other": "關閉其它", "close_other": "關閉其它",
"clear": "清空", "clear": "清空",
"perference": "偏好設定", "preference": "偏好設定",
"appearence": "外觀", "appearence": "外觀",
"gotool": "Go 工具", "gotool": "Go 工具",
"user": "用戶", "user": "使用者",
"font": "字", "font": "字",
"font_size": "字體大小", "font_size": "字體大小",
"line_height": "行高", "line_height": "行高",
"go_format": "Go 格式化", "go_format": "Go 格式化",
@ -137,39 +132,40 @@
"clearOutput": "清空輸出", "clearOutput": "清空輸出",
"export": "導出", "export": "導出",
"refresh": "刷新", "refresh": "刷新",
"import": "導入",
"theme": "主題", "theme": "主題",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"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": "在文件中尋找",
"email": "電子郵件",
"no_empty": "不能為空", "no_empty": "不能為空",
"change_avatar": "頭像修改請到", "open": "開啟",
"open": "打開",
"pricing": "價格",
"search_no_match": "沒有發現匹配的文件。", "search_no_match": "沒有發現匹配的文件。",
"outline": "大綱", "outline": "大綱",
"govet": "go vet", "govet": "go vet",
"start-vet": "START [go vet]", "start-vet": "開始 [go vet]",
"vet-succ": "[go vet] SUCCESS", "vet-succ": "[go vet] 成功",
"vet-error": "[go vet] ERROR", "vet-error": "[go vet] 失敗",
"restore_outline": "恢復大綱", "restore_outline": "恢復大綱",
"share": "分享", "share": "分享",
"url": "鏈接", "url": "連結",
"short_url": "短鏈接", "embeded": "嵌入",
"embeded": "嵌入" "terms": "使用條款",
"download": "下載",
"decompress": "解壓縮",
"keymap": "快速鍵",
"resize": "調整大小",
"sponsor": "贊助"
} }

View File

@ -1,217 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 log includes logging related manipulations.
//
// log.SetLevel("debug")
// logger := log.NewLogger(os.Stdout)
//
// logger.Trace("trace message)
// logger.Debug("debug message")
// logger.Info("info message")
// logger.Warn("warning message")
// logger.Error("error message")
//
// logger.Errorf("formatted %s message", "error")
package log
import (
"fmt"
"io"
stdlog "log"
"strings"
)
// Logging level.
const (
Off = iota
Trace
Debug
Info
Warn
Error
)
// all loggers.
var loggers []*Logger
// the global default logging level, it will be used for creating logger.
var logLevel = Debug
// Logger represents a simple logger with level.
// The underlying logger is the standard Go logging "log".
type Logger struct {
level int
logger *stdlog.Logger
}
// NewLogger creates a logger.
func NewLogger(out io.Writer) *Logger {
ret := &Logger{level: logLevel, logger: stdlog.New(out, "", stdlog.Ldate|stdlog.Ltime|stdlog.Lshortfile)}
loggers = append(loggers, ret)
return ret
}
// SetLevel sets the logging level of all loggers.
func SetLevel(level string) {
logLevel = getLevel(level)
for _, l := range loggers {
l.SetLevel(level)
}
}
// getLevel gets logging level int value corresponding to the specified level.
func getLevel(level string) int {
level = strings.ToLower(level)
switch level {
case "off":
return Off
case "trace":
return Trace
case "debug":
return Debug
case "info":
return Info
case "warn":
return Warn
case "error":
return Error
default:
return Info
}
}
// SetLevel sets the logging level of a logger.
func (l *Logger) SetLevel(level string) {
l.level = getLevel(level)
}
// IsTraceEnabled determines whether the trace level is enabled.
func (l *Logger) IsTraceEnabled() bool {
return l.level <= Trace
}
// IsDebugEnabled determines whether the debug level is enabled.
func (l *Logger) IsDebugEnabled() bool {
return l.level <= Debug
}
// IsWarnEnabled determines whether the debug level is enabled.
func (l *Logger) IsWarnEnabled() bool {
return l.level <= Warn
}
// Trace prints trace level message.
func (l *Logger) Trace(v ...interface{}) {
if Trace < l.level {
return
}
l.logger.SetPrefix("T ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Tracef prints trace level message with format.
func (l *Logger) Tracef(format string, v ...interface{}) {
if Trace < l.level {
return
}
l.logger.SetPrefix("T ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Debug prints debug level message.
func (l *Logger) Debug(v ...interface{}) {
if Debug < l.level {
return
}
l.logger.SetPrefix("D ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Debugf prints debug level message with format.
func (l *Logger) Debugf(format string, v ...interface{}) {
if Debug < l.level {
return
}
l.logger.SetPrefix("D ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Info prints info level message.
func (l *Logger) Info(v ...interface{}) {
if Info < l.level {
return
}
l.logger.SetPrefix("I ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Infof prints info level message with format.
func (l *Logger) Infof(format string, v ...interface{}) {
if Info < l.level {
return
}
l.logger.SetPrefix("I ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Warn prints warning level message.
func (l *Logger) Warn(v ...interface{}) {
if Warn < l.level {
return
}
l.logger.SetPrefix("W ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Warn prints warning level message with format.
func (l *Logger) Warnf(format string, v ...interface{}) {
if Warn < l.level {
return
}
l.logger.SetPrefix("W ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Error prints error level message.
func (l *Logger) Error(v ...interface{}) {
if Error < l.level {
return
}
l.logger.SetPrefix("E ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Errorf prints error level message with format.
func (l *Logger) Errorf(format string, v ...interface{}) {
if Error < l.level {
return
}
l.logger.SetPrefix("E ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}

View File

@ -1,169 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 log
import (
"os"
"testing"
)
// Logger.
var logger = NewLogger(os.Stdout)
func TestSetLevel(t *testing.T) {
SetLevel("trace")
}
func TestTrace(t *testing.T) {
logger.SetLevel("trace")
logger.Trace("trace")
logger.SetLevel("off")
logger.Trace("trace")
}
func TestTracef(t *testing.T) {
logger.SetLevel("trace")
logger.Tracef("tracef")
logger.SetLevel("off")
logger.Tracef("tracef")
}
func TestDebug(t *testing.T) {
logger.SetLevel("debug")
logger.Debug("debug")
logger.SetLevel("off")
logger.Debug("debug")
}
func TestDebugf(t *testing.T) {
logger.SetLevel("debug")
logger.Debugf("debugf")
logger.SetLevel("off")
logger.Debug("debug")
}
func TestInfo(t *testing.T) {
logger.SetLevel("info")
logger.Info("info")
logger.SetLevel("off")
logger.Info("info")
}
func TestInfof(t *testing.T) {
logger.SetLevel("info")
logger.Infof("infof")
logger.SetLevel("off")
logger.Infof("infof")
}
func TestWarn(t *testing.T) {
logger.SetLevel("warn")
logger.Warn("warn")
logger.SetLevel("off")
logger.Warn("warn")
}
func TestWarnf(t *testing.T) {
logger.SetLevel("warn")
logger.Warnf("warnf")
logger.SetLevel("off")
logger.Warnf("warnf")
}
func TestError(t *testing.T) {
logger.SetLevel("error")
logger.Error("error")
logger.SetLevel("off")
logger.Error("error")
}
func TestErrorf(t *testing.T) {
logger.SetLevel("error")
logger.Errorf("errorf")
logger.SetLevel("off")
logger.Errorf("errorf")
}
func TestGetLevel(t *testing.T) {
if getLevel("trace") != Trace {
t.FailNow()
return
}
if getLevel("debug") != Debug {
t.FailNow()
return
}
if getLevel("info") != Info {
t.FailNow()
return
}
if getLevel("warn") != Warn {
t.FailNow()
return
}
if getLevel("error") != Error {
t.FailNow()
return
}
}
func TestLoggerSetLevel(t *testing.T) {
logger.SetLevel("trace")
if logger.level != Trace {
t.FailNow()
return
}
}
func TestIsTraceEnabled(t *testing.T) {
logger.SetLevel("trace")
if !logger.IsTraceEnabled() {
t.FailNow()
return
}
}
func TestIsDebugEnabled(t *testing.T) {
logger.SetLevel("debug")
if !logger.IsDebugEnabled() {
t.FailNow()
return
}
}
func TestIsWarnEnabled(t *testing.T) {
logger.SetLevel("warn")
if !logger.IsWarnEnabled() {
t.FailNow()
return
}
}

288
main.go
View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -19,165 +19,146 @@ import (
"flag" "flag"
"html/template" "html/template"
"io" "io"
"math/rand"
"mime" "mime"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal"
"runtime" "runtime"
"strconv"
"strings" "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/log" "github.com/88250/wide/i18n"
"github.com/b3log/wide/notification" "github.com/88250/wide/notification"
"github.com/b3log/wide/output" "github.com/88250/wide/output"
"github.com/b3log/wide/playground" "github.com/88250/wide/playground"
"github.com/b3log/wide/session" "github.com/88250/wide/session"
"github.com/b3log/wide/util"
) )
// Logger // Logger
var logger *log.Logger var logger *gulu.Logger
// The only one init function in Wide. // The only one init function in Wide.
func init() { func init() {
confPath := flag.String("conf", "conf/wide.json", "path of wide.json") confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
confIP := flag.String("ip", "", "this will overwrite Wide.IP if specified") confData := flag.String("data", "", "path of data dir")
confPort := flag.String("port", "", "this will overwrite Wide.Port if specified")
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified") confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
confLogLevel := flag.String("log_level", "debug", "this will overwrite Wide.LogLevel if specified") confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified")
confStaticServer := flag.String("static_server", "", "this will overwrite Wide.StaticServer if specified") confReadOnly := flag.String("readonly", "", "this will overrite Wide.ReadOnly if specified")
confContext := flag.String("context", "", "this will overwrite Wide.Context if specified") confSiteStatCode := flag.String("site_stat_code", "", "this will overrite Wide.SiteStatCode if specified")
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel if specified")
confStat := flag.Bool("stat", false, "whether report statistics periodically")
confDocker := flag.Bool("docker", false, "whether run in a docker container")
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
flag.Parse() flag.Parse()
log.SetLevel("warn") gulu.Log.SetLevel("warn")
logger = log.NewLogger(os.Stdout) logger = gulu.Log.NewLogger(os.Stdout)
wd := util.OS.Pwd() //wd := gulu.OS.Pwd()
if strings.HasPrefix(wd, os.TempDir()) { //if strings.HasPrefix(wd, os.TempDir()) {
logger.Error("Don't run wide in OS' temp directory or with `go run`") // logger.Error("Don't run Wide in OS' temp directory or with `go run`")
//
os.Exit(-1) // os.Exit(-1)
} //}
i18n.Load() i18n.Load()
event.Load() event.Load()
conf.Load(*confPath, *confData, *confServer, *confLogLevel, *confReadOnly, template.HTML(*confSiteStatCode))
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
*confPlayground, *confDocker)
conf.FixedTimeCheckEnv() conf.FixedTimeCheckEnv()
session.FixedTimeSave() session.FixedTimeSave()
session.FixedTimeRelease() session.FixedTimeRelease()
session.FixedTimeReport()
if *confStat { logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
session.FixedTimeReport()
}
} }
// Main. // Main.
func main() { func main() {
runtime.GOMAXPROCS(conf.Wide.MaxProcs)
initMime() initMime()
handleSignal()
// IDE // IDE
http.HandleFunc(conf.Wide.Context+"/", handlerGzWrapper(indexHandler)) http.HandleFunc("/", handlerGzWrapper(indexHandler))
http.HandleFunc(conf.Wide.Context+"/start", handlerWrapper(startHandler)) http.HandleFunc("/start", handlerWrapper(startHandler))
http.HandleFunc(conf.Wide.Context+"/about", handlerWrapper(aboutHandler)) http.HandleFunc("/about", handlerWrapper(aboutHandler))
http.HandleFunc(conf.Wide.Context+"/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler)) http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
// static resources // static resources
http.Handle(conf.Wide.Context+"/static/", http.StripPrefix(conf.Wide.Context+"/static/", http.FileServer(http.Dir("static")))) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
serveSingle("/favicon.ico", "./static/favicon.ico") http.Handle("/static/users/", http.StripPrefix("/static/", http.FileServer(http.Dir(conf.Wide.Data+"/static"))))
serveSingle("/favicon.ico", "./static/images/favicon.png")
// workspaces // oauth
for _, user := range conf.Users { http.HandleFunc("/login/redirect", session.LoginRedirectHandler)
http.Handle(conf.Wide.Context+"/workspace/"+user.Name+"/", http.HandleFunc("/login/callback", session.LoginCallbackHandler)
http.StripPrefix(conf.Wide.Context+"/workspace/"+user.Name+"/", http.FileServer(http.Dir(user.GetWorkspace()))))
}
// session // session
http.HandleFunc(conf.Wide.Context+"/session/ws", handlerWrapper(session.WSHandler)) http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
http.HandleFunc(conf.Wide.Context+"/session/save", handlerWrapper(session.SaveContent)) http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
// run // run
http.HandleFunc(conf.Wide.Context+"/build", handlerWrapper(output.BuildHandler)) http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/run", handlerWrapper(output.RunHandler)) http.HandleFunc("/run", handlerWrapper(output.RunHandler))
http.HandleFunc(conf.Wide.Context+"/stop", handlerWrapper(output.StopHandler)) http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
http.HandleFunc(conf.Wide.Context+"/go/test", handlerWrapper(output.GoTestHandler)) http.HandleFunc("/go/test", handlerWrapper(output.GoTestHandler))
http.HandleFunc(conf.Wide.Context+"/go/vet", handlerWrapper(output.GoVetHandler)) http.HandleFunc("/go/vet", handlerWrapper(output.GoVetHandler))
http.HandleFunc(conf.Wide.Context+"/go/get", handlerWrapper(output.GoGetHandler)) http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc(conf.Wide.Context+"/go/install", handlerWrapper(output.GoInstallHandler)) http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
http.HandleFunc(conf.Wide.Context+"/output/ws", handlerWrapper(output.WSHandler))
// cross-compilation
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
// file tree // file tree
http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFiles)) http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectory)) http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFile)) http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFile)) http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFile)) http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFile)) http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFile)) http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchText)) http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.Find)) http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
// outline // outline
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutline)) http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
// file export/import // file export
http.HandleFunc(conf.Wide.Context+"/file/zip/new", handlerWrapper(file.CreateZip)) http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZip)) http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.Upload))
// editor // editor
http.HandleFunc(conf.Wide.Context+"/editor/ws", handlerWrapper(editor.WSHandler)) http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc(conf.Wide.Context+"/go/fmt", handlerWrapper(editor.GoFmtHandler)) http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc(conf.Wide.Context+"/autocomplete", handlerWrapper(editor.AutocompleteHandler)) http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc(conf.Wide.Context+"/exprinfo", handlerWrapper(editor.GetExprInfoHandler)) http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc(conf.Wide.Context+"/find/decl", handlerWrapper(editor.FindDeclarationHandler)) http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
// shell
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
// notification // notification
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler)) http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
// user // user
http.HandleFunc(conf.Wide.Context+"/login", handlerWrapper(session.LoginHandler)) http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
http.HandleFunc(conf.Wide.Context+"/logout", handlerWrapper(session.LogoutHandler)) http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUser)) http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
// playground // playground
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler)) http.HandleFunc("/playground", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/", handlerWrapper(playground.IndexHandler)) http.HandleFunc("/playground/", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler)) http.HandleFunc("/playground/ws", handlerWrapper(playground.WSHandler))
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler)) http.HandleFunc("/playground/save", handlerWrapper(playground.SaveHandler))
http.HandleFunc(conf.Wide.Context+"/playground/short-url", handlerWrapper(playground.ShortURLHandler)) http.HandleFunc("/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler)) http.HandleFunc("/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler)) http.HandleFunc("/playground/stop", handlerWrapper(playground.StopHandler))
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler)) http.HandleFunc("/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
http.HandleFunc(conf.Wide.Context+"/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context) logger.Infof("Wide is running [%s]", conf.Wide.Server)
err := http.ListenAndServe(conf.Wide.Server, nil) err := http.ListenAndServe("0.0.0.0:7070", nil)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
@ -186,61 +167,50 @@ func main() {
// indexHandler handles request of Wide index. // indexHandler handles request of Wide index.
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
if "/" != r.RequestURI { if "/" != r.RequestURI {
http.NotFound(w, r) http.Redirect(w, r, "/", http.StatusFound)
return return
} }
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
if "playground" == username { // reserved user for Playground if "playground" == uid { // reserved user for Playground
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
// create a Wide session user := conf.GetUser(uid)
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
user := conf.GetUser(username)
if nil == user { if nil == user {
logger.Warnf("Not found user [%s]", username) http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
return return
} }
locale := user.Locale locale := user.Locale
wideSessions := session.WideSessions.GetByUsername(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": user.LatestSessionContent, "uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"user": user, "editorThemes": conf.GetEditorThemes()} "user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": []string{"darwin_amd64", "linux_amd64", "windows_amd64"}}
logger.Debugf("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 {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -248,6 +218,22 @@ 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. // 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) {
@ -257,22 +243,20 @@ func serveSingle(pattern string, filename string) {
// startHandler handles request of start page. // startHandler handles request of start page.
func startHandler(w http.ResponseWriter, r *http.Request) { func startHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/s", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale user := conf.GetUser(uid)
userWorkspace := conf.GetUserWorkspace(username) locale := user.Locale
userWorkspace := conf.GetUserWorkspace(uid)
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
@ -281,13 +265,13 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
} }
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": conf.WideVersion, "session": wSession} "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 {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -297,21 +281,18 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
// keyboardShortcutsHandler handles request of keyboard shortcuts page. // keyboardShortcutsHandler handles request of keyboard shortcuts page.
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) { func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.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}
@ -319,7 +300,7 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -329,21 +310,18 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
// aboutHandle handles request of about page. // aboutHandle handles request of about page.
func aboutHandler(w http.ResponseWriter, r *http.Request) { func aboutHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.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,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()} "ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
@ -352,7 +330,7 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -431,7 +409,7 @@ func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http
// panicRecover wraps the panic recover process. // 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(w, r) handler(w, r)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -21,12 +21,12 @@ import (
"strconv" "strconv"
"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/i18n" "github.com/88250/wide/event"
"github.com/b3log/wide/log" "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/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -40,7 +40,7 @@ const (
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Notification represents a notification. // Notification represents a notification.
type Notification struct { type Notification struct {
@ -62,9 +62,9 @@ func event2Notification(e *event.Event) {
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.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var notification *Notification var notification *Notification

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -17,87 +17,132 @@ package output
import ( import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"io/ioutil"
"math/rand"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"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/i18n"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// BuildHandler handles request of building. // BuildHandler handles request of building.
func BuildHandler(w http.ResponseWriter, r *http.Request) { func BuildHandler(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)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale user := conf.GetUser(uid)
locale := user.Locale
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
sid := args["sid"].(string) sid := args["sid"].(string)
filePath := args["file"].(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) curDir := filepath.Dir(filePath)
fout, err := os.Create(filePath) fout, err := os.Create(filePath)
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
code := args["code"].(string) code := args["code"].(string)
if _, err := fout.WriteString(code); nil != err {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false 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 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 := "" suffix := ""
if util.OS.IsWindows() { if gulu.OS.IsWindows() {
suffix = ".exe" suffix = ".exe"
} }
cmd := exec.Command("go", "build")
cmd.Dir = curDir
setCmdEnv(cmd, username)
executable := filepath.Base(curDir) + suffix executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable) executable = filepath.Join(curDir, executable)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -105,149 +150,166 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
if !data["succ"].(bool) { if 0 != result.Code {
return return
} }
channelRet := map[string]interface{}{} if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
if nil != session.OutputWS[sid] { return
// display "START [go build]" in front-end browser }
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n" channelRet["cmd"] = "build"
channelRet["cmd"] = "start-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] wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := errReader.ReadString('\n')
if io.EOF == err {
break
}
lines = append(lines, line)
err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
return
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() wsChannel.Refresh()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) 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"
if err := cmd.Start(); nil != err { // lint process
logger.Error(err) if lines[0][0] == '#' {
data["succ"] = false lines = lines[1:] // skip the first line
}
return 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
} }
go func(runningId int) { wsChannel := session.OutputWS[sid]
defer util.Recover() if nil == wsChannel {
defer cmd.Wait() return
}
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir) wsChannel.Refresh()
// read all
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "build"
channelRet["executable"] = executable
if 0 == len(buf) { // build success
channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
go func() { // go install, for subsequent gocode lib-path
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, username)
out, _ := cmd.CombinedOutput()
if len(out) > 0 {
logger.Warn(string(out))
}
}()
} 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] {
// logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
}
wsChannel.Refresh()
}
}(rand.Int())
} }

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())
}

View File

@ -1,148 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoGetHandler handles request of 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")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command("go", "get")
cmd.Dir = curDir
setCmdEnv(cmd, username)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
return
}
if !data["succ"].(bool) {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go get]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
channelRet["cmd"] = "start-get"
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)
data["succ"] = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go get] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get"
// read all
buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) {
logger.Debugf("User [%s, %s] 's [go get] [runningId=%d] has done (with error)", username, sid, runningId)
channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go get] [runningId=%d] has done", username, 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.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -26,31 +26,37 @@ import (
"strconv" "strconv"
"strings" "strings"
"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/i18n"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// GoInstallHandler handles request of go install. // GoInstallHandler handles request of go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) { func GoInstallHandler(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)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -63,14 +69,14 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "install") cmd := exec.Command("go", "install")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
logger.Debugf("go install %s", curDir) logger.Debugf("go install %s", curDir)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -78,12 +84,12 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
if !data["succ"].(bool) { if 0 != result.Code {
return return
} }
@ -110,16 +116,16 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err { if err := cmd.Start(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer gulu.Panic.Recover(nil)
defer cmd.Wait() defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir) logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -183,12 +189,12 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir) logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
} }
wsChannel.Refresh() wsChannel.Refresh()

View File

@ -1,25 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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.
// +build !linux
package output
import (
"os/exec"
)
func SetNamespace(cmd *exec.Cmd) {
// do nothing
}

View File

@ -1,35 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 (
"os/exec"
"syscall"
)
func SetNamespace(cmd *exec.Cmd) {
// XXX: keep move with Go 1.4 and later's
cmd.SysProcAttr = &syscall.SysProcAttr{}
//cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER /*| syscall.CLONE_NEWNS*/ | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC /*| syscall.CLONE_NEWNET*/
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: 0,
Gid: 0,
}
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -25,10 +25,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/log" "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/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -38,7 +38,7 @@ const (
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// Lint represents a code lint. // Lint represents a code lint.
type Lint struct { type Lint struct {
@ -93,7 +93,7 @@ func parsePath(curDir, outputLine string) string {
column = parts[2] column = parts[2]
} }
tagStart := `<span class="path" data-path="` + filepath.Join(curDir, file) + `" data-line="` + line + tagStart := `<span class="path" data-path="` + filepath.ToSlash(filepath.Join(curDir, file)) + `" data-line="` + line +
`" data-column="` + column + `">` `" data-column="` + column + `">`
text := file + ":" + line text := file + ":" + line
if hasColumn { if hasColumn {
@ -104,18 +104,29 @@ func parsePath(curDir, outputLine string) string {
return tagStart + text + tagEnd + msgPart return tagStart + text + tagEnd + msgPart
} }
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, uid string) {
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
cache, err := os.UserCacheDir()
if nil != err {
logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
cache = os.TempDir()
}
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPROXY=https://goproxy.cn",
"GO111MODULE=on",
"GOPATH="+userWorkspace, "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 util.OS.IsWindows() { if gulu.OS.IsWindows() {
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized. // 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()...) 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,105 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 (
"os"
"sync"
"github.com/b3log/wide/session"
)
// Type of process set.
type procs map[string][]*os.Process
// Processse of all users.
//
// <sid, []*os.Process>
var Processes = procs{}
// Exclusive lock.
var mutex sync.Mutex
// add adds the specified process to the user process set.
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
// 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 *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:]...) // 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 *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 {
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.Username, 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.Username, sid)
}
return
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -15,243 +15,16 @@
package output package output
import ( import (
"bufio" "github.com/88250/wide/session"
"encoding/json"
"math/rand"
"net/http" "net/http"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
) )
const (
outputBufMax = 128 // 128 string(rune)
outputTimeout = 100 // 100ms
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file. // RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) { func RunHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} session.RunHandler(w, r, session.OutputWS)
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
}
filePath := args["executable"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command(filePath)
cmd.Dir = curDir
if conf.Docker {
SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
data["succ"] = false
}
wsChannel := session.OutputWS[sid]
channelRet := map[string]interface{}{}
if !data["succ"].(bool) {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
go func() {
buf := outputBuf{}
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
if nil != err {
// remove the exited process from user process set
Processes.Remove(wSession, cmd.Process)
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
break
}
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.OutputWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
} }
// StopHandler handles request of stoping a running process. // StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) { func StopHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} session.StopHandler(w, r)
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.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)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -24,31 +24,31 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"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/i18n"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// GoTestHandler handles request of go test. // GoTestHandler handles request of go test.
func GoTestHandler(w http.ResponseWriter, r *http.Request) { func GoTestHandler(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)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -61,12 +61,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "test", "-v") cmd := exec.Command("go", "test", "-v")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -74,12 +74,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
if !data["succ"].(bool) { if 0 != result.Code {
return return
} }
@ -95,7 +95,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
return return
} }
@ -106,15 +106,15 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err { if err := cmd.Start(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", username, sid, runningId) logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test" channelRet["cmd"] = "go test"
@ -126,11 +126,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", username, sid, runningId) 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) channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
} else { } else {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", username, sid, runningId) 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) channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
} }
@ -140,7 +140,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
} }
wsChannel.Refresh() wsChannel.Refresh()

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -24,31 +24,31 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"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/i18n"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// GoVetHandler handles request of go vet. // GoVetHandler handles request of go vet.
func GoVetHandler(w http.ResponseWriter, r *http.Request) { func GoVetHandler(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)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -61,12 +61,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "vet", ".") cmd := exec.Command("go", "vet", ".")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -74,12 +74,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
if !data["succ"].(bool) { if 0 != result.Code {
return return
} }
@ -95,7 +95,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
return return
} }
@ -106,15 +106,15 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err { if err := cmd.Start(); nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId) logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go vet" channelRet["cmd"] = "go vet"
@ -126,11 +126,11 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId) 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) channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
} else { } else {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId) 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) channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
} }
@ -140,7 +140,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Error(err) logger.Warn(err)
} }
wsChannel.Refresh() wsChannel.Refresh()

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"
}
}

60
pkg.sh
View File

@ -1,15 +1,15 @@
#!/bin/bash #!/bin/bash
# Wide package tool. # Wide package tool.
# #
# Command: # Command:
# ./pkg.sh ${version} ${target} # ./pkg.sh ${version} ${target}
# Example: # Example:
# ./pkg.sh 1.0.1 /home/daniel/1.0.1/ # ./pkg.sh 1.0.0 /home/daniel/1.0.0/
ver=$1 ver=$1
target=$2 target=$2
list="conf doc i18n static views README.md LICENSE" list="conf doc i18n static views README.md TERMS.md LICENSE"
mkdir -p ${target} mkdir -p ${target}
@ -20,43 +20,61 @@ echo target=${target}
os=darwin os=darwin
export GOOS=${os} export GOOS=${os}
export GOARCH=386 export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go go build github.com/visualfc/gotools
rm -f wide 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 GOOS=${os}
export GOARCH=amd64 export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go go build github.com/visualfc/gotools
rm -f wide 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 ## linux
os=linux os=linux
export GOOS=${os} export GOOS=${os}
export GOARCH=386 export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go go build github.com/visualfc/gotools
rm -f wide 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 GOOS=${os}
export GOARCH=amd64 export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go go build github.com/visualfc/gotools
rm -f wide 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 ## windows
os=windows os=windows
export GOOS=${os} export GOOS=${os}
export GOARCH=386 export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build go build
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go go build github.com/visualfc/gotools
rm -f wide.exe 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 GOOS=${os}
export GOARCH=amd64 export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build go build
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go go build github.com/visualfc/gotools
rm -f wide.exe 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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -18,26 +18,32 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"github.com/b3log/wide/session" "github.com/88250/gulu"
"github.com/b3log/wide/util" "github.com/88250/wide/conf"
"github.com/88250/wide/session"
) )
// AutocompleteHandler handles request of code autocompletion. // AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) { func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{} 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 { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -48,20 +54,28 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
line := int(args["cursorLine"].(float64)) line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(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) offset := getCursorOffset(code, line, ch)
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)} gocode := gulu.Go.GetExecutableInGOBIN("gocode")
gocode := util.Go.GetExecutableInGOBIN("gocode")
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 {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -22,17 +22,23 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/session" "github.com/88250/wide/conf"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// BuildHandler handles request of Playground building. // BuildHandler handles request of Playground building.
func BuildHandler(w http.ResponseWriter, r *http.Request) { func BuildHandler(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)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -42,28 +48,31 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
fileName := args["fileName"].(string) fileName := args["fileName"].(string)
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
suffix := "" suffix := ""
if util.OS.IsWindows() { if gulu.OS.IsWindows() {
suffix = ".exe" suffix = ".exe"
} }
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1)) 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) cmd := exec.Command("go", "build", "-o", executable, filePath)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
data["output"] = template.HTML(string(out)) data["output"] = template.HTML(string(out))
if nil != err { if nil != err {
data["succ"] = false result.Code = -1
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -23,19 +23,24 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
"github.com/b3log/wide/conf" "github.com/88250/gulu"
"github.com/b3log/wide/session" "github.com/88250/wide/conf"
"github.com/b3log/wide/util" "github.com/88250/wide/session"
) )
// SaveHandler handles request of Playground code save. // SaveHandler handles request of Playground code save.
func SaveHandler(w http.ResponseWriter, r *http.Request) { func SaveHandler(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") if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -45,7 +50,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -58,7 +63,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
@ -72,6 +77,9 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
code = string(output) code = string(output)
} }
data := map[string]interface{}{}
result.Data = &data
data["code"] = code data["code"] = code
// Step2. generate file name // Step2. generate file name
@ -82,54 +90,13 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
data["fileName"] = fileName data["fileName"] = fileName
// Step3. write file // Step3. write file
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
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 {
logger.Error(err) logger.Error(err)
data["succ"] = false result.Code = -1
return return
} }
} }
// ShortURLHandler handles request of short URL.
func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
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)
data["succ"] = false
return
}
url := args["url"].(string)
resp, _ := http.Post("http://dwz.cn/create.php", "application/x-www-form-urlencoded",
strings.NewReader("url="+url))
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
logger.Error(err)
data["succ"] = false
return
}
shortURL := url
if 0 == response["status"].(float64) {
shortURL = response["tinyurl"].(string)
}
data["shortURL"] = shortURL
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -26,48 +26,40 @@ import (
"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/log" "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/gorilla/websocket" "github.com/gorilla/websocket"
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
// IndexHandler handles request of Playground index. // IndexHandler handles request of Playground index.
func IndexHandler(w http.ResponseWriter, r *http.Request) { func IndexHandler(w http.ResponseWriter, r *http.Request) {
// create a HTTP session // create a HTTP session
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
httpSession.Values["id"] = strconv.Itoa(rand.Int()) httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Values["username"] = "playground" httpSession.Values["uid"] = "playground"
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
// create a wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
locale := conf.Wide.Locale locale := conf.Wide.Locale
// try to load file // try to load file
code := conf.HelloWorld code := conf.HelloWorld
fileName := "8b7cc38b4c12e6fde5c4d15a4f2f32e5.go" // MD5 of HelloWorld.go fileName := "6c5595ec6fbadf4cfce3edbfcfd8c6d0.go" // MD5 of HelloWorld.go
if strings.HasSuffix(r.URL.Path, ".go") { if strings.HasSuffix(r.URL.Path, ".go") {
fileNameArg := r.URL.Path[len("/playground/"):] fileNameArg := r.URL.Path[len("/playground/"):]
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileNameArg) filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileNameArg)
bytes, err := ioutil.ReadFile(filePath) bytes, err := ioutil.ReadFile(filePath)
if nil != err { if nil != err {
@ -92,19 +84,19 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
} }
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, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, "sid": session.WideSessions.GenId(), "pathSeparator": conf.PathSeparator,
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(), "codeMirrorVer": conf.CodeMirrorVer,
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"embed": embed, "disqus": disqus, "fileName": fileName} "embed": embed, "disqus": disqus, "fileName": fileName}
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUserId(uid)
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions)) logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
t, err := template.ParseFiles("views/playground/index.html") t, err := template.ParseFiles("views/playground/index.html")
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -15,239 +15,16 @@
package playground package playground
import ( import (
"bufio" "github.com/88250/wide/session"
"encoding/json"
"math/rand"
"net/http" "net/http"
"os/exec"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/output"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
) )
const (
outputBufMax = 128 // 128 string(rune)
outputTimeout = 100 // 100ms
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file. // RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) { func RunHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} session.RunHandler(w, r, session.PlaygroundWS)
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
}
filePath := args["executable"].(string)
cmd := exec.Command(filePath)
if conf.Docker {
output.SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
data["succ"] = false
}
wsChannel := session.PlaygroundWS[sid]
channelRet := map[string]interface{}{}
if !data["succ"].(bool) {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
output.Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
go func() {
buf := outputBuf{}
for {
wsChannel := session.PlaygroundWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
if nil != err {
// remove the exited process from user process set
output.Processes.Remove(wSession, cmd.Process)
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
break
}
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.PlaygroundWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
} }
// StopHandler handles request of stoping a running process. // StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) { func StopHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} session.StopHandler(w, r)
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.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
}
output.Processes.Kill(wSession, pid)
} }

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,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -25,17 +25,21 @@ package session
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath"
"sort" "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/log" "github.com/88250/wide/event"
"github.com/b3log/wide/util" "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"
) )
@ -43,10 +47,12 @@ import (
const ( const (
sessionStateActive = iota sessionStateActive = iota
sessionStateClosed // (not used so far) sessionStateClosed // (not used so far)
CookieName = "wide-sess"
) )
// Logger. // Logger.
var logger = log.NewLogger(os.Stdout) var logger = gulu.Log.NewLogger(os.Stdout)
var ( var (
// SessionWS holds all session channels. <sid, *util.WSChannel> // SessionWS holds all session channels. <sid, *util.WSChannel>
@ -71,12 +77,13 @@ var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// WideSession represents a session associated with a browser tab. // WideSession represents a session associated with a browser tab.
type WideSession struct { type WideSession struct {
ID string // id ID string // id
Username string // username UserId string // user id
HTTPSession *sessions.Session // HTTP session related HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // process set Processes []*os.Process // process set
EventQueue *event.UserEventQueue // event queue EventQueue *event.UserEventQueue // event queue
State int // state State int // state
Content *conf.LatestSessionContent // the latest session content Content *conf.LatestSessionContent // the latest session content
FileWatcher *fsnotify.Watcher // files change watcher
Created time.Time // create time Created time.Time // create time
Updated time.Time // the latest use time Updated time.Time // the latest use time
} }
@ -98,13 +105,15 @@ var mutex sync.Mutex
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field. // Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
func FixedTimeRelease() { func FixedTimeRelease() {
go func() { go func() {
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) {
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username) logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.UserId)
WideSessions.Remove(s.ID) WideSessions.Remove(s.ID)
} }
@ -115,7 +124,7 @@ func FixedTimeRelease() {
// Online user statistic report. // Online user statistic report.
type userReport struct { type userReport struct {
username string userId string
sessionCnt int sessionCnt int
processCnt int processCnt int
updated time.Time updated time.Time
@ -123,13 +132,15 @@ type userReport struct {
// report returns a online user statistics in pretty format. // report returns a online user statistics in pretty format.
func (u *userReport) report() string { func (u *userReport) report() string {
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) + 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") + "]" "] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
} }
// FixedTimeReport reports the Wide sessions status periodically (10 minutes). // FixedTimeReport reports the Wide sessions status periodically (10 minutes).
func FixedTimeReport() { func FixedTimeReport() {
go func() { go func() {
defer gulu.Panic.Recover(nil)
for _ = range time.Tick(10 * time.Minute) { for _ = range time.Tick(10 * time.Minute) {
users := userReports{} users := userReports{}
processSum := 0 processSum := 0
@ -138,7 +149,7 @@ func FixedTimeReport() {
processCnt := len(s.Processes) processCnt := len(s.Processes)
processSum += processCnt processSum += processCnt
if report, exists := contains(users, s.Username); exists { if report, exists := contains(users, s.UserId); exists {
if s.Updated.After(report.updated) { if s.Updated.After(report.updated) {
report.updated = s.Updated report.updated = s.Updated
} }
@ -146,7 +157,7 @@ func FixedTimeReport() {
report.sessionCnt++ report.sessionCnt++
report.processCnt += processCnt report.processCnt += processCnt
} else { } else {
users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated}) users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
} }
} }
@ -165,9 +176,9 @@ func FixedTimeReport() {
}() }()
} }
func contains(reports []*userReport, username string) (*userReport, bool) { func contains(reports []*userReport, userId string) (*userReport, bool) {
for _, ur := range reports { for _, ur := range reports {
if username == ur.username { if userId == ur.userId {
return ur, true return ur, true
} }
} }
@ -181,26 +192,22 @@ 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) 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 } 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. // WSHandler handles request of creating session channel.
// //
// When a channel closed, releases all resources associated with it. // 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 {
httpSession, _ := HTTPSession.Get(r, "wide-session")
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.Username)
}
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()}
@ -213,15 +220,52 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
SessionWS[sid] = &wsChan SessionWS[sid] = &wsChan
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, CookieName)
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)) logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
input := map[string]interface{}{} input := map[string]interface{}{}
wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait))
wsChan.Conn.SetPongHandler(func(string) error { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
ticker := time.NewTicker(pingPeriod)
defer func() {
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 { for {
if err := wsChan.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.Username) logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.UserId)
WideSessions.Remove(sid)
return return
} }
@ -238,10 +282,10 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// SaveContent handles request of session content string. // 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
@ -250,14 +294,14 @@ 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 {
logger.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
} }
@ -265,7 +309,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
wSession.Content = args.LatestSessionContent wSession.Content = args.LatestSessionContent
for _, user := range conf.Users { for _, user := range conf.Users {
if user.Name == wSession.Username { if user.Id == wSession.UserId {
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically // update the variable in-memory, session.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content user.LatestSessionContent = wSession.Content
@ -290,30 +334,11 @@ func (s *WideSession) Refresh() {
s.Updated = time.Now() s.Updated = time.Now()
} }
// New creates a wide session. // GenId generates a wide session id.
func (sessions *wSessions) New(httpSession *sessions.Session, sid string) *WideSession { func (sessions *wSessions) GenId() string {
mutex.Lock() rand.Seed(time.Now().UnixNano())
defer mutex.Unlock()
now := time.Now() return strconv.Itoa(rand.Int())
// create user event queue
userEventQueue := event.UserEventQueues.New(sid)
ret := &WideSession{
ID: sid,
Username: httpSession.Values["username"].(string),
HTTPSession: httpSession,
EventQueue: userEventQueue,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
return ret
} }
// Get gets a wide session with the specified session id. // Get gets a wide session with the specified session id.
@ -337,6 +362,7 @@ func (sessions *wSessions) Get(sid string) *WideSession {
// 1. user event queue // 1. user event queue
// 2. process set // 2. process set
// 3. websocket channels // 3. websocket channels
// 4. file watcher
func (sessions *wSessions) Remove(sid string) { func (sessions *wSessions) Remove(sid string) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -352,9 +378,9 @@ func (sessions *wSessions) Remove(sid string) {
// kill processes // kill processes
for _, p := range s.Processes { for _, p := range s.Processes {
if err := p.Kill(); nil != err { if err := p.Kill(); nil != err {
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username) logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} else { } else {
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username) logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} }
} }
@ -379,14 +405,19 @@ func (sessions *wSessions) Remove(sid string) {
delete(PlaygroundWS, sid) delete(PlaygroundWS, sid)
} }
// file watcher
if nil != s.FileWatcher {
s.FileWatcher.Close()
}
cnt := 0 // count wide sessions associated with HTTP session cnt := 0 // count wide sessions associated with HTTP session
for _, ses := range *sessions { for _, ses := range *sessions {
if ses.Username == s.Username { if ses.UserId == s.UserId {
cnt++ cnt++
} }
} }
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt) logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
return return
} }
@ -394,17 +425,130 @@ func (sessions *wSessions) Remove(sid string) {
} }
// GetByUsername gets wide sessions. // GetByUsername gets wide sessions.
func (sessions *wSessions) 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,10 +1,10 @@
// Copyright (c) 2014-2015, b3log.org // 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,
@ -15,30 +15,24 @@
package session package session
import ( import (
"crypto/md5"
"encoding/hex"
"encoding/json" "encoding/json"
"math/rand"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
"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/util" "github.com/88250/wide/i18n"
) )
const ( const (
// TODO: i18n // TODO: i18n
userExists = "user exists" userExists = "user exists"
emailExists = "email exists"
userCreated = "user created" userCreated = "user created"
userCreateError = "user create error" userCreateError = "user create error"
) )
@ -48,69 +42,82 @@ var addUserMutex sync.Mutex
// PreferenceHandler handles request of preference page. // PreferenceHandler handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) { func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession, _ := HTTPSession.Get(r, CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
user := conf.GetUser(username) user := conf.GetUser(uid)
if "GET" == r.Method { 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, 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(), "ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
"locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats(), "locales": i18n.GetLocalesNames(), "gofmts": gulu.Go.GetGoFormats(),
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()} "themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
t, err := template.ParseFiles("views/preference.html") t, err := template.ParseFiles("views/preference.html")
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), http.StatusInternalServerError)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return return
} }
t.Execute(w, model) t.Execute(w, model)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return return
} }
// non-GET request as save request // non-GET request as save request
succ := true result := gulu.Ret.NewResult()
data := map[string]interface{}{"succ": &succ} defer gulu.Ret.RetResult(w, r, result)
defer util.RetJSON(w, r, data)
args := struct { args := struct {
FontFamily string FontFamily string
FontSize string FontSize string
GoFmt string GoFmt string
Workspace string GoBuildArgsForLinux string
Username string GoBuildArgsForWindows string
Password string GoBuildArgsForDarwin string
Email string Keymap string
Locale string Workspace string
Theme string Username string
EditorFontFamily string Locale string
EditorFontSize string Theme string
EditorLineHeight string EditorFontFamily string
EditorTheme string EditorFontSize string
EditorTabSize string EditorLineHeight string
EditorTheme string
EditorTabSize string
}{} }{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err) logger.Error(err)
succ = false result.Code = -1
return return
} }
@ -118,16 +125,12 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.FontFamily = args.FontFamily user.FontFamily = args.FontFamily
user.FontSize = args.FontSize user.FontSize = args.FontSize
user.GoFormat = args.GoFmt 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 // XXX: disallow change workspace at present
// user.Workspace = args.Workspace // user.Workspace = args.Workspace
if user.Password != args.Password {
user.Password = conf.Salt(args.Password, user.Salt)
}
user.Email = args.Email
hash := md5.New()
hash.Write([]byte(user.Email))
user.Gravatar = hex.EncodeToString(hash.Sum(nil))
user.Locale = args.Locale user.Locale = args.Locale
user.Theme = args.Theme user.Theme = args.Theme
@ -137,154 +140,17 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.Editor.Theme = args.EditorTheme user.Editor.Theme = args.EditorTheme
user.Editor.TabSize = args.EditorTabSize user.Editor.TabSize = args.EditorTabSize
conf.UpdateCustomizedConf(username) conf.UpdateCustomizedConf(uid)
now := time.Now().UnixNano() now := time.Now().UnixNano()
user.Lived = now user.Lived = now
user.Updated = now user.Updated = now
succ = user.Save() if user.Save() {
} result.Code = 0
} else {
// LoginHandler handles request of user login. result.Code = -1
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the login page
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(), 500)
return
}
t.Execute(w, model)
return
} }
// non-GET request as login request
succ := true
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 {
logger.Error("login error: ", err)
succ = false
return
}
succ = false
for _, user := range conf.Users {
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
succ = true
break
}
}
if !succ {
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// SignUpUser handles request of registering user.
func SignUpUser(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
dir := filepath.Dir(firstUserWorkspace)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": dir,
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
t, err := template.ParseFiles("views/sign_up.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as add user request
succ := true
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
succ = false
return
}
username := args["username"].(string)
password := args["password"].(string)
email := args["email"].(string)
msg := addUser(username, password, email)
if userCreated != msg {
succ = false
data["msg"] = msg
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
} }
// FixedTimeSave saves online users' configurations periodically (1 minute). // FixedTimeSave saves online users' configurations periodically (1 minute).
@ -292,35 +158,57 @@ func SignUpUser(w http.ResponseWriter, r *http.Request) {
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time. // Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
func FixedTimeSave() { func FixedTimeSave() {
go func() { go func() {
for _ = range time.Tick(time.Minute) { defer gulu.Panic.Recover(nil)
users := getOnlineUsers()
for _, u := range users { for _ = range time.Tick(time.Minute) {
if u.Save() { SaveOnlineUsers()
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
} }
}() }()
} }
// CanAccess determines whether the user specified by the given user id can access the specified path.
func CanAccess(userId, path string) bool {
path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(userId)
workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces {
if strings.HasPrefix(path, workspace) {
return true
}
}
return false
}
// SaveOnlineUsers saves online users' configurations at once.
func SaveOnlineUsers() {
users := getOnlineUsers()
for _, u := range users {
if u.Save() {
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
}
func getOnlineUsers() []*conf.User { func getOnlineUsers() []*conf.User {
ret := []*conf.User{} ret := []*conf.User{}
usernames := map[string]string{} // distinct username uids := map[string]string{} // distinct uid
for _, s := range WideSessions { for _, s := range WideSessions {
usernames[s.Username] = s.Username uids[s.UserId] = s.UserId
} }
for _, username := range usernames { for _, uid := range uids {
u := conf.GetUser(username) u := conf.GetUser(uid)
if "playground" == username { // user [playground] is a reserved mock user if "playground" == uid { // user [playground] is a reserved mock user
continue continue
} }
if nil == u { if nil == u {
logger.Warnf("Not found user [%s]", username) logger.Warnf("Not found user [%s]", uid)
continue continue
} }
@ -330,133 +218,3 @@ func getOnlineUsers() []*conf.User {
return ret return ret
} }
// addUser add a user with the specified username, password and email.
//
// 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(username, password, email string) string {
if "playground" == username {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Name) == strings.ToLower(username) {
return userExists
}
if strings.ToLower(user.Email) == strings.ToLower(email) {
return emailExists
}
}
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
dir := filepath.Dir(firstUserWorkspace)
workspace := filepath.Join(dir, username)
newUser := conf.NewUser(username, password, email, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
logger.Infof("Created a user [%s]", username)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(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()
}
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
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
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}

View File

@ -1,196 +0,0 @@
// Copyright (c) 2014-2015, 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
//
// http://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 shell related mainipulations.
package shell
import (
"html/template"
"math/rand"
"net/http"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
// Shell channel.
//
// <sid, *util.WSChannel>>
var ShellWS = map[string]*util.WSChannel{}
// Logger.
var logger = log.NewLogger(os.Stdout)
// IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
// create a wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession}
wideSessions := session.WideSessions.GetByUsername(username)
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/shell.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
}
// WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
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()}
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
ShellWS[sid] = &wsChan
logger.Debugf("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
input := map[string]interface{}{}
for {
if err := wsChan.ReadJSON(&input); err != nil {
logger.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.WriteJSON(&ret); err != nil {
logger.Error("Shell WS ERROR: " + err.Error())
return
}
wsChan.Refresh()
}
}
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()
// release resources
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.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,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -58,7 +58,7 @@
} }
#dialogAbout .thx a { #dialogAbout .thx a {
width: 100px; width: 80px;
display: inline-block; display: inline-block;
} }

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,6 +14,12 @@
* limitations under the License. * 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 { ::-webkit-scrollbar {
background: none; background: none;
@ -134,17 +140,24 @@ button {
/* start icon */ /* start icon */
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src:url('fonts/icomoon.eot?35cb2z'); src: url('fonts/icomoon.eot?lqk80d');
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'), src: url('fonts/icomoon.eot?lqk80d#iefix') format('embedded-opentype'),
url('fonts/icomoon.woff?35cb2z') format('woff'), url('fonts/icomoon.ttf?lqk80d') format('truetype'),
url('fonts/icomoon.ttf?35cb2z') format('truetype'), url('fonts/icomoon.woff?lqk80d') format('woff'),
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg'); url('fonts/icomoon.svg?lqk80d#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
.font-ico { [class^="ico-"], [class*=" ico-"] {
font-family: 'icomoon'; /* 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 =========== */ /* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -153,173 +166,140 @@ button {
line-height: 20px; line-height: 20px;
} }
.ico-book:before { .ico-qqz:before {
content: "\e623"; content: "\e900";
} }
.ico-price:before {
content: "\e616";
}
.ico-start:before {
content: "\e9d7";
}
.ico-share:before {
content: "\e61f";
}
.ico-github:before {
content: "\f00a";
}
.ico-tencent:before {
content: "\e622";
}
.ico-weibo:before {
content: "\e621";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-email:before {
content: "\e619";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-find:before { .ico-find:before {
content: "\e602"; content: "\e602";
} }
.ico-findfiles:before {
content: "\e603";
}
.ico-editor:before { .ico-editor:before {
content: "\e604"; content: "\e604";
} }
.ico-tree:before {
content: "\e600";
}
.ico-build:before {
content: "\e601";
}
.ico-notification:before { .ico-notification:before {
content: "\e607"; content: "\e607";
} }
.ico-price:before {
content: "\e616";
}
.ico-report:before { .ico-report:before {
content: "\e605"; content: "\e605";
} }
.ico-git:before {
.ico-comment:before { content: "\e624";
content: "\e620";
} }
.ico-book:before {
.ico-goline:before { content: "\e623";
content: "\e61e";
} }
.ico-start:before {
.ico-info:before { content: "\e9d7";
content: "\e61d"; text-shadow: 0 0 rgba(0, 0, 0, 0.4);
} }
.ico-tree:before {
.ico-signup:before { content: "\e600";
content: "\e606";
} }
.ico-build:before {
.ico-signout:before { content: "\e601";
content: "\e618";
} }
.ico-redo:before {
content: "\e615";
}
.ico-undo:before {
content: "\e60e";
}
.ico-about:before {
content: "\e60d";
}
.ico-import:before {
content: "\f0ee";
}
.ico-export:before { .ico-export:before {
content: "\f0ed"; content: "\f0ed";
} }
.ico-import:before {
.ico-refresh:before { content: "\f0ee";
content: "\f021";
} }
.ico-keyboard:before {
.ico-remove:before { content: "\f11c";
content: "\e60b";
} }
.ico-moveup:before {
.ico-save:before { content: "\f148";
content: "\f0c7"; }
.ico-movedown:before {
content: "\f149";
}
.ico-weibo:before {
content: "\e621";
}
.ico-uniE608:before {
content: "\e608";
} }
.ico-max:before { .ico-max:before {
content: "\e609"; content: "\e609";
} }
.ico-remove:before {
.ico-format:before { content: "\e60b";
content: "\e612";
} }
.ico-buildrun:before { .ico-buildrun:before {
content: "\e60c"; content: "\e60c";
} }
.ico-about:before {
content: "\e60d";
}
.ico-undo:before {
content: "\e60e";
}
.ico-stop:before { .ico-stop:before {
content: "\e60f"; 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 { .ico-restore:before {
content: "\e613"; content: "\e613";
} }
.toolbars .ico-restore:before { .toolbars .ico-restore:before {
content: "\e60a"; content: "\e60a";
} }
.ico-min:before { .ico-min:before {
content: "\e614"; content: "\e614";
position: absolute; position: absolute;
right: 5px; right: 5px;
} }
.ico-redo:before {
.ico-close:before { content: "\e615";
content: "\e611"; }
.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 */ /* end ico */

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -17,7 +17,7 @@
/** /**
* 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
*/ */
@ -52,7 +52,7 @@
.dialog-header-bg { .dialog-header-bg {
height: 23px; height: 23px;
background-color: #CCD5E5; background-color: #bbb;
cursor: move; cursor: move;
width: 100%; width: 100%;
} }

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

Binary file not shown.

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

@ -6,51 +6,50 @@
<font id="icomoon" horiz-adv-x="1024"> <font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" /> <font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" /> <missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" /> <glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe600;" 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" horiz-adv-x="1170" /> <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="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="&#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="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="&#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="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="&#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="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="&#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="M622.858 778.475l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" /> <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="M697.83 473.125c76.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 473.125c76.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 380.209c-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 380.209c-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="&#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="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="&#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="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" horiz-adv-x="952" /> <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="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="&#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="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="&#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="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="&#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="M192 832l640-384-640-384z" /> <glyph unicode="&#xe60c;" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe60d;" 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="&#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="&#xe60e;" 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="&#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="&#xe60f;" d="M128 832h768v-768h-768z" /> <glyph unicode="&#xe60f;" glyph-name="stop" d="M128 832h768v-768h-768z" />
<glyph unicode="&#xe610;" d="M363.722 237.948l41.298 57.816-45.254 45.256-57.818-41.296c-10.722 5.994-22.204 10.774-34.266 14.192l-11.682 70.084h-64l-11.68-70.086c-12.062-3.418-23.544-8.198-34.266-14.192l-57.818 41.298-45.256-45.256 41.298-57.816c-5.994-10.72-10.774-22.206-14.192-34.266l-70.086-11.682v-64l70.086-11.682c3.418-12.060 8.198-23.544 14.192-34.266l-41.298-57.816 45.254-45.256 57.818 41.296c10.722-5.994 22.204-10.774 34.266-14.192l11.682-70.084h64l11.68 70.086c12.062 3.418 23.544 8.198 34.266 14.192l57.818-41.296 45.254 45.256-41.298 57.816c5.994 10.72 10.774 22.206 14.192 34.266l70.088 11.68v64l-70.086 11.682c-3.418 12.060-8.198 23.544-14.192 34.266zM224 96c-35.348 0-64 28.654-64 64s28.652 64 64 64 64-28.654 64-64-28.652-64-64-64zM1024 576v64l-67.382 12.25c-1.242 8.046-2.832 15.978-4.724 23.79l57.558 37.1-24.492 59.128-66.944-14.468c-4.214 6.91-8.726 13.62-13.492 20.13l39.006 56.342-45.256 45.254-56.342-39.006c-6.512 4.766-13.22 9.276-20.13 13.494l14.468 66.944-59.128 24.494-37.1-57.558c-7.812 1.892-15.744 3.482-23.79 4.724l-12.252 67.382h-64l-12.252-67.382c-8.046-1.242-15.976-2.832-23.79-4.724l-37.098 57.558-59.128-24.492 14.468-66.944c-6.91-4.216-13.62-8.728-20.13-13.494l-56.342 39.006-45.254-45.254 39.006-56.342c-4.766-6.51-9.278-13.22-13.494-20.13l-66.944 14.468-24.492-59.128 57.558-37.1c-1.892-7.812-3.482-15.742-4.724-23.79l-67.384-12.252v-64l67.382-12.25c1.242-8.046 2.832-15.978 4.724-23.79l-57.558-37.1 24.492-59.128 66.944 14.468c4.216-6.91 8.728-13.618 13.494-20.13l-39.006-56.342 45.254-45.256 56.342 39.006c6.51-4.766 13.22-9.276 20.13-13.492l-14.468-66.944 59.128-24.492 37.102 57.558c7.81-1.892 15.742-3.482 23.788-4.724l12.252-67.384h64l12.252 67.382c8.044 1.242 15.976 2.832 23.79 4.724l37.1-57.558 59.128 24.492-14.468 66.944c6.91 4.216 13.62 8.726 20.13 13.492l56.342-39.006 45.256 45.256-39.006 56.342c4.766 6.512 9.276 13.22 13.492 20.13l66.944-14.468 24.492 59.13-57.558 37.1c1.892 7.812 3.482 15.742 4.724 23.79l67.382 12.25zM672 468.8c-76.878 0-139.2 62.322-139.2 139.2s62.32 139.2 139.2 139.2 139.2-62.322 139.2-139.2c0-76.878-62.32-139.2-139.2-139.2z" /> <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="&#xe611;" 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="&#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="&#xe612;" 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="&#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="&#xe613;" 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="&#xe614;" 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="&#xe615;" 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="&#xe616;" d="M501.443 484.729c-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="&#xe617;" 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="&#xe618;" 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="&#xe619;" 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="&#xe61a;" 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="&#xe61b;" 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="&#xe61c;" 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="&#xe61d;" 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="&#xe61e;" 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="&#xe61f;" 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="&#xe620;" 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="&#xe621;" 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="&#xe622;" d="M1023.972 804.11c0 86.096-69.794 155.89-155.89 155.89h-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.22zM133.524 60.794c1.024-12.316-8.218-43.952-20.562-43.952h-2.264c-11.426 0-21.18 28.24-22.284 39.962-17.058 190.14 42.982 344.172 96.66 423.692 19.32 28.916 39.128 52.708 57.074 71.626-4.474 10.24-6.98 21.558-6.98 33.524 0 46.216 37.646 83.592 83.86 83.592 46.484 0 83.7-37.376 83.7-83.592 0-46.51-37.24-83.86-83.7-83.86-17.838 0-34.276 5.632-47.94 15.092-15.872-16.95-33.064-47.884-49.746-73.054-71.114-106.442-100.892-241.986-87.82-383.030zM315.31 328.004c-19.080 0-38.4 2.102-56.832 6.198-12.18 2.884-19.43 15.010-16.924 26.894 2.938 12.26 14.794 19.832 26.892 16.976 15.226-3.504 31.016-5.202 46.86-5.202 114.878 0 208.194 93.32 208.194 207.926 0 114.634-93.32 207.952-208.194 207.952-114.606 0-208.088-93.318-208.088-207.952 0-33.254 7.6-65.268 22.824-94.856 5.848-10.94 1.482-24.522-9.862-30.1-11.076-5.902-24.414-1.536-30.154 9.566-18.296 35.382-27.756 75.508-27.756 115.362 0 139.534 113.474 253.036 253.036 253.036 139.642 0 253.196-113.502 253.196-253.036-0.028-139.292-113.582-252.766-253.198-252.766zM785.866 622.162c-22.42-0.486-45.218 4.204-65.698 13.932-71.464 34.008-101.996 119.726-67.988 191.272 33.9 71.49 119.726 101.996 191.272 67.988 71.49-33.874 102.024-119.836 67.852-191.112-4.958-9.836-10.428-19.212-17.14-27.674-4.23-5.47-12.45-6.466-17.948-2.102s-6.36 12.45-1.994 17.948c5.496 6.846 10.212 14.712 13.958 22.798 28.024 58.718 3.1 129.346-55.808 156.968-58.798 28.024-129.186 3.126-156.968-55.942-28.294-58.664-3.26-129.052 55.7-157.076 17.058-8.004 35.462-11.992 54.056-11.236 7.086 0.134 13.068-5.336 13.204-12.45 0.322-7.114-5.308-12.934-12.504-13.312zM1001.284 534.448c-0.162-0.648-0.432-0.862-0.432-1.24-2.938-5.9-9.674-8.73-16.062-6.466-101.538 37.484-155.972 101.026-183.646 147.86-9.944 16.95-17.164 33.038-22.42 46.7-6.576 0.162-12.854 1.886-18.782 4.5-24.064 11.454-34.062 39.614-22.69 63.408 11.21 23.688 39.478 33.792 63.272 22.42 23.688-11.318 33.658-39.64 22.582-63.272-4.366-9-11.346-16.194-19.482-20.696 5.012-12.476 11.48-26.302 20.318-40.986 37.16-62.41 94.316-108.168 169.58-135.924 6.71-2.614 9.972-9.702 7.76-16.302z" /> <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="&#xe623;" 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="&#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;" 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="&#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;" 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="&#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;" 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="&#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="&#xf05b;" d="M684 374.856h-62.286q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h62.286q-18.286 61.714-64.286 107.714t-107.714 64.286v-62.286q0-14.858-10.858-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v62.286q-61.714-18.286-107.714-64.286t-64.286-107.714h62.286q14.858 0 25.714-10.856t10.856-25.714v-73.142q0-14.856-10.856-25.714t-25.714-10.856h-62.286q18.286-61.714 64.286-107.714t107.714-64.286v62.286q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-62.286q61.714 18.286 107.714 64.286t64.286 107.714zM877.714 484.572v-73.144q0-14.856-10.856-25.714t-25.714-10.856h-81.714q-21.144-92-88.286-159.144t-159.144-88.286v-81.714q0-14.856-10.856-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v81.714q-92 21.144-159.144 88.286t-88.286 159.144h-81.714q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h81.714q21.144 92 88.286 159.144t159.144 88.286v81.714q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-81.714q92-21.142 159.144-88.286t88.286-159.144h81.714q14.856 0 25.714-10.856t10.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="&#xf0c7;" 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="&#xf0ed;" d="M731.429 420.571q0 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 292.571q0-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" horiz-adv-x="1097" /> <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="&#xf0ee;" d="M731.429 457.143q0 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 292.571q0-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" horiz-adv-x="1097" /> <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="&#xf11c;" d="M219.429 283.428v-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 429.714v-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 576v-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 283.428v-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 429.714v-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 576v-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 429.714v-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 576v-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 429.714v-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 283.428v-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 576v-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 576v-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 576v-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 146.286v512h-950.857v-512h950.857zM1097.143 658.286v-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" horiz-adv-x="1097" /> <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="&#xf148;" d="M581.714 606.286q-10.286-21.143-33.143-21.143h-109.714v-493.714q0-8-5.143-13.143t-13.143-5.143h-402.286q-12 0-16.571 10.286-4.571 11.429 2.286 20l91.429 109.714q5.143 6.286 14.286 6.286h182.857v365.714h-109.714q-22.857 0-33.143 21.143-9.714 21.143 5.143 38.857l182.857 219.429q10.286 12.571 28 12.571t28-12.571l182.857-219.429q15.429-18.286 5.143-38.857z" horiz-adv-x="585" /> <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" />
<glyph unicode="&#xf149;" d="M18.286 804.571h402.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" horiz-adv-x="585" />
</font></defs></svg> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 30 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 @@
/*
* Copyright (c) 2014-2015, 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
*
* http://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 {
overflow: auto;
}
.header {
margin: 0;
padding: 5px;
}
.header li {
margin-left: 10px;
}
.header .gravatar {
width: 26px;
border-radius: 13px;
}
.header .logo {
height: 21px;
margin-top: -6px;
}
.share-panel {
position: absolute;
z-index: 20;
width: 226px;
padding: 5px 0px;
right: 0px;
line-height: normal;
top: 34px;
}
.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%;
}
#output {
height: 30%;
width: 100%;
border-width: 0;
margin: 0;
padding: 0;
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: 1px 5px;
}
#disqus_thread {
border-top: 1px solid #919191;
padding: 0 30px;
}

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,17 +13,27 @@
* 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.
*/ */
body { body {
overflow: auto; display: flex;
flex-direction: column;
max-height: 100vh;
}
.main {
flex: 1;
min-height: 1px;
display: flex;
} }
.header { .header {
margin: 0; margin: 0;
padding: 10px; padding: 5px;
} }
.font-ico { .header li {
font-size: 26px; margin-left: 0;
margin-right: 20px;
} }
.header .gravatar { .header .gravatar {
@ -31,23 +41,25 @@ body {
border-radius: 13px; border-radius: 13px;
} }
.header > .fn-right { .header .logo {
margin-top: 10px; height: 28px;
margin-top: -6px;
} }
.header .logo { .header .font-ico {
height: 36px; font-size: 18px;
margin-top: -4px; line-height: 28px;
} }
.share-panel { .share-panel {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
width: 258px; width: 190px;
padding: 10px 10px; padding: 5px 0px;
right: 0px; right: 0px;
line-height: normal; line-height: normal;
top: 57px; top: 38px;
border-color: #999;
} }
.share-panel .font-ico { .share-panel .font-ico {
@ -57,7 +69,7 @@ body {
} }
.share-panel .font-ico:hover { .share-panel .font-ico:hover {
transform:rotate(360deg); transform: rotate(360deg);
} }
.footer { .footer {
@ -66,17 +78,21 @@ body {
} }
#editorDiv { #editorDiv {
width: 60%; width: 100%;
float: left; height: 70%;
}
.bottom-window-group {
height: 30%;
} }
#output { #output {
width: 40%; height: 100%;
float: right; width: 100%;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
padding: 0; padding: 2px 5px;
border-left: 1px solid #919191; border-top: 1px solid #919191;
} }
#dialogShare { #dialogShare {
@ -89,7 +105,54 @@ body {
word-wrap: break-word; word-wrap: break-word;
} }
#disqus_thread { .wrapper {
border-top: 1px solid #919191; width: auto;
padding: 0 30px; }
.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,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,43 +14,37 @@
* limitations under the License. * 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 {
width: 20%; width: 20%;
position: absolute; position: absolute;
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;
} }
.side .tabs-panel {
overflow: auto;
}
/* end side */ /* end side */
/* start side right */ /* start side right */
.side-right {
height: 70%;
position: absolute;
right: 0;
width: 20%;
z-index: 8;
}
.side-right-max {
width: 100%;
height: 100%;
z-index: 11;
}
.side-right .tabs-panel > div { .side-right .tabs-panel > div {
overflow: auto; overflow: auto;
} }
.side-right {
flex-flow: column;
}
#outline .ico { #outline .ico {
margin: 1px 5px 0 5px; margin: 1px 5px 0 5px;
} }
@ -88,6 +82,8 @@
.ztree { .ztree {
width: 100%; width: 100%;
padding: 0; padding: 0;
outline: 0px;
border: 0px;
} }
.ztree li a.curSelectedNode { .ztree li a.curSelectedNode {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,9 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.wrapper { .wrapper {
margin: 0 auto; margin: 0 auto;
width: 980px; width: 980px;
display: flex;
align-items: center;
} }
.header .logo { .header .logo {
@ -44,13 +52,20 @@
} }
.header a:hover { .header a:hover {
color: #4183C4; color: #4285f4;
}
.fn-left {
flex: 1;
} }
.content { .content {
border-top: 1px solid #A4A4A4; border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #919191; border-bottom: 1px solid #919191;
background-color: #202021; background-color: #3b3e43;
flex: 1;
display: flex;
align-items: center;
} }
.content h2 { .content h2 {
@ -60,94 +75,104 @@
} }
.content h3 { .content h3 {
color: #4183c4; color: #4285f4;
font-size: 21px; font-size: 21px;
} }
.content .form { .content .form {
padding: 24px 15px;
background-color: #fff;
border-radius: 6px;
width: 320px; width: 320px;
margin-top: -18px;
position: relative; position: relative;
} }
.content .form input { .content a {
border: 1px solid #ccc; color: #4285f4;
border-radius: 3px;
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 {
background-color: #FFF; .login__icon {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75); width: 200px;
transition: all .15s ease-in-out;
padding-right: 24px;
color: #3b3e43;
fill: currentColor;
}
.login__icon:hover {
transform: scale(1.1);
} }
.btn { .btn {
width: 100%; width: 100%;
color: #fff; color: #fff;
text-shadow: 0 -1px 0 rgba(0,0,0,0.25); background-color: #2ebc4f;
background-color: #60b044;
background-image: linear-gradient(#8add6d, #60b044);
border: 1px solid #5ca941;
padding: 10px; padding: 10px;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
border: 0;
display: block;
text-align: center;
} }
.btn:hover { .btn:hover {
background-color: #569e3d; text-decoration: none;
background-image: linear-gradient(#79d858, #569e3d); background-color: #28a745;
border-color: #4a993e; }
.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-white,
.btn.btn-red { .btn.btn-red {
color: #333; color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9); background-color: #fff;
background-color: #eee;
background-image: linear-gradient(#fcfcfc, #eee);
border-color: #d5d5d5;
} }
.btn.btn-red { .btn.btn-red {
color: #9d0000; color: #d23f31;
} }
.btn.btn-white:hover { .btn.btn-white:hover {
color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
background-color: #ddd; background-color: #ddd;
background-image: linear-gradient(#eee, #ddd);
border-color: #ccc;
} }
.btn.btn-red:hover { .btn.btn-red:hover {
background-color: #b33630;
background-image: linear-gradient(#dc5f59, #b33630);
background-repeat: repeat-x;
border-color: #cd504a;
color: #fff; color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3); background-color: #d23f31;
} }
#msg { .desc {
background-color: #fcdede; color: #6a737d;
border: 1px solid #d2b2b2; font-size: 12px;
padding: 15px; }
font-size: 14px;
color: #911; .more-detail {
position: absolute; display: none;
width: 100%; margin: 0 0 8px 60px;
top: -48px; }
.checkbox {
margin-top: 8px;
display: block;
}
.view-more {
cursor: pointer;
display: block;
text-align: center;
color: #4285f4;
margin-top: 8px;
} }
.footer { .footer {
@ -155,27 +180,35 @@
color: #777; color: #777;
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
position: relative;
} }
.footer a { .footer a {
text-decoration: none; text-decoration: none;
color: #4183c4; color: #4285f4;
} }
.footer a:hover { .footer a:hover {
text-decoration: underline; text-decoration: underline;
} }
.footer .github-btns {
height: 25px;
position: absolute;
top: 5px;
right: 0;
}
/* start sign up */ /* start sign up */
.dir { .dir {
color: #4183c4; color: #4285f4;
font-size: 18px; font-size: 18px;
word-wrap: break-word; word-wrap: break-word;
margin-top: 20px; margin-top: 20px;
} }
#dir { #dir {
color: #999; color: #999;
font-size: 13px; font-size: 13px;
} }
@ -188,4 +221,21 @@
margin-top: 20px; margin-top: 20px;
font-size: 16px; font-size: 16px;
} }
/* end sign up */
.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,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -32,14 +32,13 @@
} }
#startPage .title { #startPage .title {
background-color: #9F9F9F; 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; color: #FFF;
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
} }
#startPage .details { #startPage .details {
@ -71,6 +70,8 @@
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 {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,14 +14,18 @@
* limitations under the License. * 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 { .side {
background-color: #303130; background-color: #303130;
border-right: 1px solid #000;
} }
.side-right { .side-right {
background-color: #303130; background-color: #303130;
border-left: 1px solid #000;
color: #999; color: #999;
} }
@ -36,11 +40,13 @@
color: #EBEBEB; color: #EBEBEB;
} }
.font-ico { .font-ico,
.ico-restore {
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
} }
.font-ico:hover, .font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico { .frame li:hover .font-ico {
color: #e1e1e1; color: #e1e1e1;
} }
@ -141,15 +147,33 @@
color: #e1e1e1; color: #e1e1e1;
} }
.bottom-window-group { .bottom-window-group .tabs-panel {
border-top: 1px solid #000;
}
.bottom-window-group .tabs-panel > div{
background-color: #181818; background-color: #181818;
color: #ebebeb; color: #ebebeb;
} }
#dialogPreference .preference { #dialogPreference .preference {
border: 1px solid #000; 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

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,14 +14,19 @@
* limitations under the License. * 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 { .side {
background-color: #FFF; background-color: #FFF;
border-right: 1px solid #919191;
} }
.side-right { .side-right {
background-color: #FFF; background-color: #FFF;
border-left: 1px solid #919191;
} }
.footer { .footer {
@ -34,11 +39,13 @@
background-color: #FFF; background-color: #FFF;
} }
.font-ico { .font-ico,
.ico-restore {
color: #666; color: #666;
} }
.font-ico:hover, .font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico { .frame li:hover .font-ico {
color: #333; color: #333;
} }
@ -122,10 +129,27 @@
background-color: #F0F0F0; background-color: #F0F0F0;
} }
.bottom-window-group {
border-top: 1px solid #919191;
}
#dialogPreference .preference { #dialogPreference .preference {
border: 1px solid #A4A4A4; 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,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,6 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
/*
* 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;
@ -77,9 +83,20 @@
.tabs > div > span.changed { .tabs > div > span.changed {
font-weight: bold; font-weight: bold;
} }
.tabs-panel {
overflow: auto;
flex: 1;
height: 100%;
}
/* end tabs */ /* end tabs */
/* start menu */ /* start menu */
.menu {
display: block !important;
}
.menu > ul > li { .menu > ul > li {
float: left; float: left;
} }
@ -119,9 +136,10 @@
.share-panel { .share-panel {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
width: 226px; width: 190px;
padding: 5px 0; padding: 5px 0;
right: 0px; right: 0px;
top: 21px;
} }
.share-panel .font-ico { .share-panel .font-ico {
@ -143,6 +161,8 @@
width: 60%; width: 60%;
height: 70%; height: 70%;
overflow: hidden; overflow: hidden;
flex-flow: column;
display: flex;
} }
.toolbars { .toolbars {
@ -184,6 +204,7 @@
.CodeMirror, .CodeMirror,
.CodeMirror-hints { .CodeMirror-hints {
font-family: Consolas, 'Courier New', monospace;
} }
.CodeMirror-hints .ico { .CodeMirror-hints .ico {
@ -218,27 +239,8 @@
/* 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;
border-top-width: 0 !important;
}
.bottom-window-group > div > div {
overflow: auto;
} }
.bottom-window-group .output { .bottom-window-group .output {
@ -247,6 +249,7 @@
line-height: 16px; line-height: 16px;
font-size: 12px; font-size: 12px;
overflow-x: scroll; overflow-x: scroll;
outline: 0;
} }
.bottom-window-group .output pre { .bottom-window-group .output pre {
@ -256,22 +259,19 @@
.bottom-window-group .output .start-build, .bottom-window-group .output .start-build,
.bottom-window-group .output .start-test, .start-vet, .bottom-window-group .output .start-test, .start-vet,
.bottom-window-group .output .start-install, .bottom-window-group .output .start-install {
.bottom-window-group .output .start-get {
color: #999; color: #999;
} }
.bottom-window-group .output .build-succ, .bottom-window-group .output .build-succ,
.bottom-window-group .output .test-succ, .vet-succ, .bottom-window-group .output .test-succ, .vet-succ,
.bottom-window-group .output .install-succ, .bottom-window-group .output .install-succ {
.bottom-window-group .output .get-succ {
color: rgb(0,153,0); color: rgb(0,153,0);
} }
.bottom-window-group .output .build-error, .bottom-window-group .output .build-error,
.bottom-window-group .output .test-error, .vet-error, .bottom-window-group .output .test-error, .vet-error,
.bottom-window-group .output .install-error, .bottom-window-group .output .install-error {
.bottom-window-group .output .get-error {
color: #9d0000; color: #9d0000;
} }
@ -295,23 +295,29 @@
line-height: 19px; line-height: 19px;
} }
.bottom-window-group .notification {
outline: 0;
}
.bottom-window-group .notification .type, .bottom-window-group .notification .type,
.bottom-window-group .notification .severity { .bottom-window-group .notification .severity {
width: 50px; width: 50px;
padding: 0 5px; padding: 0 5px;
} }
.bottom-window-group .search {
display: flex;
flex-flow: column;
outline: 0;
}
/* end bottom-window-group */ /* end bottom-window-group */
/* start footer */ /* start footer */
.footer { .footer {
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset; box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.57);
padding-left: 5px; padding-left: 5px;
height: 19px;
line-height: 18px; line-height: 18px;
bottom: 0; display: block !important;
z-index: 11;
width: 100%;
} }
.footer .cursor { .footer .cursor {

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: 15 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,6 +14,13 @@
* limitations under the License. * 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 = { var bottomGroup = {
tabs: undefined, tabs: undefined,
searchTab: undefined, searchTab: undefined,
@ -75,6 +82,7 @@ var bottomGroup = {
fillOutput: function (data) { fillOutput: function (data) {
var $output = $('.bottom-window-group .output'); var $output = $('.bottom-window-group .output');
data = data.replace(/\r/g, '');
data = data.replace(/\n/g, '<br/>'); data = data.replace(/\n/g, '<br/>');
if (-1 !== data.indexOf("<br/>")) { if (-1 !== data.indexOf("<br/>")) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,11 +14,17 @@
* 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"
} }
}); });

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,7 +14,15 @@
* limitations under the License. * 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: {},
getEditorByPath: function (path) { getEditorByPath: function (path) {
@ -25,7 +33,7 @@ var editors = {
} }
}, },
close: function () { close: function () {
$(".edit-panel .tabs > div[data-index=" + $(".edit-panel .frame").data("index") + "]").find(".ico-close").click(); $('.edit-panel .tabs > div[data-index="' + $('.edit-panel .frame').data('index') + ']').find('.ico-close').click();
}, },
closeOther: function () { closeOther: function () {
var currentIndex = $(".edit-panel .frame").data("index"); var currentIndex = $(".edit-panel .frame").data("index");
@ -43,14 +51,14 @@ var editors = {
var firstIndex = removeData.splice(0, 1); var firstIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData); $("#dialogCloseEditor").data("removeData", removeData);
// 开始关闭 // 开始关闭
$(".edit-panel .tabs > div[data-index=" + firstIndex + "]").find(".ico-close").click(); $('.edit-panel .tabs > div[data-index="' + firstIndex + '"]').find(".ico-close").click();
}, },
_removeAllMarker: function () { _removeAllMarker: function () {
var removeData = $("#dialogCloseEditor").data("removeData"); var removeData = $("#dialogCloseEditor").data("removeData");
if (removeData && removeData.length > 0) { if (removeData && removeData.length > 0) {
var removeIndex = removeData.splice(0, 1); var removeIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData); $("#dialogCloseEditor").data("removeData", removeData);
$(".edit-panel .tabs > div[data-index=" + removeIndex + "] .ico-close").click(); $('.edit-panel .tabs > div[data-index="' + removeIndex + '"] .ico-close').click();
} }
if (wide.curEditor) { if (wide.curEditor) {
wide.curEditor.focus(); wide.curEditor.focus();
@ -97,7 +105,7 @@ var editors = {
"afterInit": function () { "afterInit": function () {
$("#dialogCloseEditor button.save").click(function () { $("#dialogCloseEditor button.save").click(function () {
var i = $("#dialogCloseEditor").data("index"); var i = $("#dialogCloseEditor").data("index");
wide.fmt(tree.fileTree.getNodeByTId(editors.data[i].id).path, editors.data[i].editor); wide.fmt(editors.data[i].id, editors.data[i].editor);
editors.tabs.del(editors.data[i].id); editors.tabs.del(editors.data[i].id);
$("#dialogCloseEditor").dialog("close"); $("#dialogCloseEditor").dialog("close");
editors._removeAllMarker(); editors._removeAllMarker();
@ -129,27 +137,9 @@ var editors = {
wide.curEditor = undefined; wide.curEditor = undefined;
$(".footer .cursor").text(''); $(".footer .cursor").text('');
wide.refreshOutline(); wide.refreshOutline();
return false; return false;
} }
// set tree node selected
var node = tree.fileTree.getNodeByTId(id);
tree.fileTree.selectNode(node);
wide.curNode = node;
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) {
wide.curEditor = editors.data[i].editor;
break;
}
}
var cursor = wide.curEditor.getCursor();
wide.curEditor.setCursor(cursor);
wide.curEditor.focus();
wide.refreshOutline();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
}, },
removeBefore: function (id) { removeBefore: function (id) {
if (id === 'startPage') { // 当前关闭的 tab 是起始页 if (id === 'startPage') { // 当前关闭的 tab 是起始页
@ -163,8 +153,8 @@ var editors = {
editors._removeAllMarker(); editors._removeAllMarker();
return true; return true;
} else { } else {
$("#dialogCloseEditor").dialog("open", $(".edit-panel .tabs > div[data-index=" $("#dialogCloseEditor").dialog("open", $('.edit-panel .tabs > div[data-index="'
+ editors.data[i].id + "] > span:eq(0)").text()); + editors.data[i].id + '"] > span:eq(0)').text());
$("#dialogCloseEditor").data("index", i); $("#dialogCloseEditor").data("index", i);
return false; return false;
} }
@ -188,14 +178,22 @@ var editors = {
} }
if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断 if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install', menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
'find', 'find-next', 'find-previous', 'replace', 'replace-all', 'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment', 'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']); 'edit']);
// remove selected tree node
tree.fileTree.cancelSelectedNode();
wide.curNode = undefined;
wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
return false;
} }
if (!nextId) { if (!nextId) {
// 不存在打开的编辑器 // 编辑器区域不存在打开的 Tab
// remove selected tree node // remove selected tree node
tree.fileTree.cancelSelectedNode(); tree.fileTree.cancelSelectedNode();
wide.curNode = undefined; wide.curNode = undefined;
@ -209,30 +207,6 @@ var editors = {
// 关闭的不是当前编辑器 // 关闭的不是当前编辑器
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;
}
}
wide.refreshOutline();
var cursor = wide.curEditor.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
}
});
$(".edit-panel .tabs").on("dblclick", function () {
if ($(".toolbars .ico-max").length === 1) {
windows.maxEditor();
} else {
windows.restoreEditor();
} }
}); });
@ -244,7 +218,7 @@ var editors = {
wide.curEditor = undefined; wide.curEditor = undefined;
wide.refreshOutline(); wide.refreshOutline();
$(".footer .cursor").text(''); $(".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 = {
@ -272,10 +246,9 @@ var editors = {
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>', + '"><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").height($('.side-right').height() - $(".bottom-window-group").children(".tabs").height() - 100); $("#startPage").load('/start?sid=' + config.wideSessionId);
$("#startPage").load(config.context + '/start?sid=' + config.wideSessionId);
$.ajax({ $.ajax({
url: "https://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",
@ -285,20 +258,21 @@ 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>";
} }
@ -309,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");
@ -324,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);
@ -344,10 +320,16 @@ 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',
url: config.context + '/autocomplete', url: '/autocomplete',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
@ -397,12 +379,15 @@ var editors = {
} }
} }
// 清除未保存状态
editor.doc.markClean(); editor.doc.markClean();
$(".edit-panel .tabs > div.current > span").removeClass("changed"); $(".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)};
}); });
@ -411,7 +396,7 @@ var editors = {
if (mode && "go" !== mode.name) { if (mode && "go" !== mode.name) {
return CodeMirror.Pass; return CodeMirror.Pass;
} }
var token = cm.getTokenAt(cm.getCursor()); var token = cm.getTokenAt(cm.getCursor());
if ("comment" === token.type || "string" === token.type) { if ("comment" === token.type || "string" === token.type) {
@ -450,17 +435,18 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/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>');
} }
}); });
}; };
@ -597,13 +583,15 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/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 data = result.data;
var tId = tree.getTIdByPath(data.path); var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId); wide.curNode = tree.fileTree.getNodeByTId(tId);
@ -625,15 +613,15 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/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', '');
} }
}); });
}; };
@ -735,7 +723,7 @@ var editors = {
}, },
// 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab. // 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab.
newEditor: function (data, cursor) { newEditor: function (data, cursor) {
var id = wide.curNode.tId; var id = wide.curNode.id;
editors.tabs.add({ editors.tabs.add({
id: id, id: id,
@ -744,7 +732,7 @@ var editors = {
content: '<textarea id="editor' + id + '"></textarea>' content: '<textarea id="editor' + id + '"></textarea>'
}); });
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', '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', 'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment', 'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']); 'edit']);
@ -763,9 +751,12 @@ var editors = {
theme: config.editorTheme, theme: config.editorTheme,
tabSize: config.editorTabSize, tabSize: config.editorTabSize,
indentUnit: 4, indentUnit: 4,
indentWithTabs: true,
foldGutter: true, foldGutter: true,
cursorHeight: 1, cursorHeight: 1,
path: data.path, path: data.path,
readOnly: wide.curNode.isGOAPI,
profile: 'xhtml', // define Emmet output profile
extraKeys: { extraKeys: {
"Ctrl-\\": "autocompleteAnyWord", "Ctrl-\\": "autocompleteAnyWord",
".": "autocompleteAfterDot", ".": "autocompleteAfterDot",
@ -804,6 +795,10 @@ var editors = {
} }
}); });
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();
@ -811,31 +806,59 @@ var editors = {
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |'); $(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
}); });
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) { editor.on('changes', function (cm) {
if (cm.doc.isClean()) { if (cm.doc.isClean()) { // no modification
// 没有修改过
$(".edit-panel .tabs > div").each(function () { $(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)"); var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) { if ($span.attr("title") === cm.options.path) {
$span.removeClass("changed"); $span.removeClass("changed");
} }
}); });
} else {
// 修改过 return;
$(".edit-panel .tabs > div").each(function () { }
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) { // changed
$span.addClass("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);
}
} }
}); });
@ -843,6 +866,10 @@ var editors = {
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);
} }

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * 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,
@ -14,6 +14,13 @@
* limitations under the License. * 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
@ -36,13 +43,9 @@ var hotkeys = {
which: 49, which: 49,
fun: function () { fun: function () {
// 有些元素需设置 tabindex 为 -1 时才可以 focus // 有些元素需设置 tabindex 为 -1 时才可以 focus
if ($(".footer .ico-restore:eq(0)").css("display") === "inline") { if (windows.outerLayout.west.state.isClosed) {
// 当文件树最小化时 windows.outerLayout.slideOpen('west');
$(".side").css({
"left": "0"
});
} }
$("#files").focus(); $("#files").focus();
} }
}, },
@ -53,11 +56,8 @@ var hotkeys = {
shiftKey: false, shiftKey: false,
which: 50, which: 50,
fun: function () { fun: function () {
if ($(".footer .ico-restore:eq(2)").css("display") === "inline") { if (windows.innerLayout.east.state.isClosed) {
// 当文件树最小化时 windows.innerLayout.slideOpen('east');
$(".side-right").css({
"right": "0"
});
} }
$("#outline").focus(); $("#outline").focus();
@ -216,9 +216,9 @@ var hotkeys = {
if (event.altKey === hotKeys.clearWindow.altKey if (event.altKey === hotKeys.clearWindow.altKey
&& event.which === hotKeys.clearWindow.which) { // Alt-C clear output && event.which === hotKeys.clearWindow.which) { // Alt-C clear output
bottomGroup.clear('output'); bottomGroup.clear('output');
event.preventDefault(); event.preventDefault();
return; return;
} }
}); });
@ -293,7 +293,7 @@ var hotkeys = {
case 40: // down case 40: // down
var node = {}; var node = {};
if (!wide.curNode) { // select the first one if no node been selected 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)) {
@ -426,19 +426,19 @@ var hotkeys = {
|| 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;
} }
} }
bottomGroup.tabs.setCurrent(nextId); bottomGroup.tabs.setCurrent(nextPath);
$(".bottom-window-group ." + nextId).focus(); $(".bottom-window-group ." + nextPath).focus();
event.preventDefault(); event.preventDefault();
@ -446,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;
@ -463,8 +463,10 @@ var hotkeys = {
} }
} }
editors.tabs.setCurrent(nextId); editors.tabs.setCurrent(nextPath);
wide.curNode = tree.fileTree.getNodeByTId(nextId); var nextTId = tree.getTIdByPath(nextPath);
wide.curNode = tree.fileTree.getNodeByTId(nextTId);
tree.fileTree.selectNode(wide.curNode); tree.fileTree.selectNode(wide.curNode);
wide.refreshOutline(); wide.refreshOutline();
var cursor = wide.curEditor.getCursor(); var cursor = wide.curEditor.getCursor();

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

File diff suppressed because one or more lines are too long

View File

@ -1,159 +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) {
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);
}
});
};
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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,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"><h1>CodeMirror</h1><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/codemirror/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,197 +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) {
var htmlTagName = state.htmlMixedState.htmlState.context && state.htmlMixedState.htmlState.context.tagName
? state.htmlMixedState.htmlState.context.tagName
: null;
if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && htmlTagName === 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

@ -58,7 +58,9 @@
if (inp) { if (inp) {
if (options.value) { if (options.value) {
inp.value = options.value; inp.value = options.value;
inp.select(); if (options.selectValueOnOpen !== false) {
inp.select();
}
} }
if (options.onInput) if (options.onInput)

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