Compare commits

...

304 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
135 changed files with 6690 additions and 6430 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

3
.gitignore vendored
View File

@ -1,8 +1,6 @@
/wide.exe /wide.exe
/wide /wide
/static/user/admin/style.css
/header /header
/header.exe /header.exe
@ -11,3 +9,4 @@
/node_modules /node_modules
/nbproject /nbproject
/workspaces /workspaces
.idea

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-2016", "Year": "2014-present",
"Owner": "b3log.org & hacpai.com" "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,23 +0,0 @@
FROM golang:cross
MAINTAINER Liang Ding <dl88250@gmail.com>
ADD . /wide/gogogo/src/github.com/b3log/wide
RUN tar zxf /wide/gogogo/src/github.com/b3log/wide/deps/golang.org.tar.gz -C /wide/gogogo/src/
RUN tar zxf /wide/gogogo/src/github.com/b3log/wide/deps/github.com.tar.gz -C /wide/gogogo/src/
RUN useradd wide && useradd runner
ENV GOROOT /usr/src/go
ENV GOPATH /wide/gogogo
RUN go build github.com/go-fsnotify/fsnotify
RUN go build github.com/gorilla/sessions
RUN go build github.com/gorilla/websocket
RUN go install github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports
WORKDIR /wide/gogogo/src/github.com/b3log/wide
RUN 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,180 +1,18 @@
# [Wide](https://github.com/b3log/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-~4.3K-red.svg?style=flat)](http://pan.baidu.com/s/1dD3XwOT) * [Wide 用户指南](https://ld246.com/article/1538873544275)
* [Wide 开发指南](https://ld246.com/article/1538876422995)
_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!_
先试试我们搭建好的[在线服务](http://wide.b3log.org/signup),你可以在这里[下载](http://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请小伙伴们来玩吧!
> * 关于 Wide 的产品定位,请看[这里](http://hacpai.com/article/1438407961481),并欢迎参与讨论~
> * 加入[**黑客派**](http://hacpai.com/register),与其他程序员、设计师共同成长!
## Introduction
A <b>W</b>eb-based <b>IDE</b> for Teams using Go programming language/Golang.
![Hello, 世界](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png)
## Authors
[Daniel](https://github.com/88250) and [Vanessa](https://github.com/Vanessa219) are the main authors of Wide, [here](https://github.com/b3log/wide/graphs/contributors) are all contributors.
Wide 的主要作者是 [Daniel](https://github.com/88250) 与 [Vanessa](https://github.com/Vanessa219),所有贡献者可以在[这里](https://github.com/b3log/wide/graphs/contributors)看到。
## 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: HTML/JS/CSS editor with [Emmet](http://emmet.io) integrated
* [X] Go tool: go get/install/fmt etc.
* [X] File Import & Export
* [X] Themes: editor and UI adjust, respectively
* [X] Cross-Compilation
* [ ] Debug
* [ ] Git integration: git command on the web
## Screenshots
* **Overview**
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png) ![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) ![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) ![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) ![Theme](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) ![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) ![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
* **Git Clone**
![Git Clone](https://cloud.githubusercontent.com/assets/873584/6545235/2284f230-c5b7-11e4-985e-7e04367921b1.png)
* **Cross-Compilation**
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png) ![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
* **Playground**
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif) ![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
## 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(gotools)````<br/>
3.1 Sets environment variables (e.g. ${GOPATH})<br/>
3.2 ````gocode```` with ````lib-path```` parameter
## Documents
* [用户指南](https://www.gitbook.com/book/88250/wide-user-guide)
* [开发指南](https://www.gitbook.com/book/88250/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 https://github.com/b3log/wide`
2. Get dependencies with
* `go get`
* `go get github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports`
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)
* [Rename directory](https://github.com/b3log/wide/issues/251)
## 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 page, even which you made
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for a commercial license request
* Copyright &copy; b3log.org, all rights reserved
## Credits
Wide is made possible by the following open source projects.
* [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)
* [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-2016, b3log.org & hacpai.com // 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,17 +15,13 @@
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"
) )
// Panel represents a UI panel. // Panel represents a UI panel.
@ -51,14 +47,15 @@ type LatestSessionContent struct {
// User configuration. // User configuration.
type User struct { type User struct {
Id string
Name string Name string
Password string Avatar string
Salt string
Email string
Gravatar string // see http://gravatar.com
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator) Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Locale string Locale string
GoFormat string GoFormat string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
FontFamily string FontFamily string
FontSize string FontSize string
Theme string Theme string
@ -79,26 +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",
Keymap: "wide",
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, "", " ")
@ -109,12 +87,12 @@ func (u *User) Save() bool {
} }
if "" == string(bytes) { if "" == string(bytes) {
logger.Error("Truncated user [" + u.Name + "]") logger.Error("Truncated user [" + u.Id + "]")
return false return false
} }
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err { 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
@ -123,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,50 +0,0 @@
{
"Name": "admin",
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
"Salt": "dnoyeb",
"Email": "",
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Theme": "default",
"Keymap": "wide",
"Created": 1414080000000000000,
"Updated": 1477750918395543731,
"Lived": 1477756867409471881,
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "13px",
"LineHeight": "17px",
"Theme": "lesser-dark",
"TabSize": "4"
},
"LatestSessionContent": {
"fileTree": [
"/Users/Vanessa/Work/GoGoGo/src",
"/Users/Vanessa/Work/GoGoGo/src/github.com",
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log",
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide"
],
"files": [
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide/.gitignore"
],
"currentFile": "/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide/.gitignore",
"layout": {
"side": {
"state": "normal",
"size": 200
},
"sideRight": {
"state": "normal",
"size": 200
},
"bottom": {
"state": "normal",
"size": 100
}
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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 (
@ -39,43 +37,43 @@ const (
PathListSeparator = string(os.PathListSeparator) PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current Wide's version. // WideVersion holds the current Wide's version.
WideVersion = "1.5.2" WideVersion = "1.6.0"
// CodeMirrorVer holds the current editor version. // CodeMirrorVer holds the current editor version.
CodeMirrorVer = "5.1" 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
Context string // server context
Server string // server host and port ({IP}:{Port})
StaticServer string // static resources server scheme, host and port (http://{IP}:{Port})
LogLevel string // logging level: trace/debug/info/warn/error LogLevel string // logging level: trace/debug/info/warn/error
Channel string // channel (ws://{IP}:{Port}) Data string // data directory
RuntimeMode string // runtime mode (dev/prod)
HTTPSessionMaxAge int // HTTP session max age (in seciond) HTTPSessionMaxAge int // HTTP session max age (in seciond)
StaticResourceVersion string // version of static resources StaticResourceVersion string // version of static resources
MaxProcs int // Go max procs
RuntimeMode string // runtime mode (dev/prod)
WD string // current working direcitory, ${pwd}
Locale string // default locale Locale string // default locale
Playground string // playground directory
UsersWorkspaces string // users' workspaces directory (admin defaults to ${GOPATH}, others using this)
AllowRegister bool // allow register or not
Autocomplete bool // default autocomplete Autocomplete bool // default autocomplete
SiteStatCode template.HTML // site statistic code
ReadOnly bool // read-only mode
OAuthLoginURL string
OAuthAccessTokenURL string
OAuthUserInfoURL string
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
@ -83,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, confUsersWorkspaces string) {
// 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, confUsersWorkspaces) 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)
@ -123,8 +128,7 @@ func initUsers() {
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, skip loading this user", name, err) logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
@ -137,6 +141,17 @@ func initUsers() {
user.Keymap = "wide" 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)
} }
@ -144,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, confUsersWorkspaces string) { bytes, err := os.ReadFile(confPath)
bytes, err := ioutil.ReadFile(confPath)
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
@ -162,102 +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 Directory
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)
// Users' workspaces Directory Wide.Data = filepath.Clean(Wide.Data)
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${WD}", Wide.WD, 1) if err := os.MkdirAll(Wide.Data+"/playground/", 0755); nil != err {
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${home}", home, 1) logger.Errorf("Create data directory [%s] error", err)
if "" != confUsersWorkspaces {
Wide.UsersWorkspaces = confUsersWorkspaces
}
Wide.UsersWorkspaces = filepath.Clean(Wide.UsersWorkspaces)
if !util.File.IsExist(Wide.Playground) {
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
logger.Errorf("Create Playground [%s] error", err)
os.Exit(-1) os.Exit(-1)
} }
} if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
// IP
if "" != confIP {
Wide.IP = confIP
} else {
ip, err := util.Net.LocalIP()
if nil != err {
logger.Error(err)
os.Exit(-1) os.Exit(-1)
} }
if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
logger.Debugf("${ip} [%s]", ip) os.Exit(-1)
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
} }
if "" != confPort {
Wide.Port = confPort
}
// Docker flag
Docker = confDocker
// 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
} }
time := strconv.FormatInt(time.Now().UnixNano(), 10) time := strconv.FormatInt(time.Now().UnixNano(), 10)
logger.Debugf("${time} [%s]", time) logger.Debugf("${time} [%s]", time)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1) Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
// Channel
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).
@ -275,7 +252,7 @@ func FixedTimeCheckEnv() {
} }
func checkEnv() { func checkEnv() {
defer util.Recover() defer gulu.Panic.Recover(nil)
cmd := exec.Command("go", "version") cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
@ -292,16 +269,16 @@ 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], please install it with this command: go get github.com/nsf/gocode", gocode) logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/stamblerre/gocode", gocode)
} }
ideStub := util.Go.GetExecutableInGOBIN("gotools") 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 {
@ -311,11 +288,11 @@ func checkEnv() {
} }
} }
// 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()
} }
} }
@ -323,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"
@ -341,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
} }
} }
@ -360,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
} }
} }
@ -388,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)
@ -419,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 {
@ -442,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,19 +1,16 @@
{ {
"IP": "${ip}", "Server": "http://127.0.0.1:7070",
"Port": "7070",
"Context": "",
"Server": "{IP}:{Port}",
"StaticServer": "",
"LogLevel": "debug", "LogLevel": "debug",
"Channel": "ws://{IP}:{Port}", "Data": "${home}/wide",
"RuntimeMode": "prod",
"HTTPSessionMaxAge": 86400, "HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "${time}", "StaticResourceVersion": "${time}",
"MaxProcs": 4, "Locale": "zh_CN",
"RuntimeMode": "dev", "SiteStatCode": "",
"WD": "${pwd}", "ReadOnly": false,
"Locale": "en_US", "OAuthLoginURL": "",
"Playground": "${home}/playground", "OAuthAccessTokenURL": "",
"UsersWorkspaces": "${WD}/workspaces", "OAuthUserInfoURL": "",
"AllowRegister": true, "OAuthClientID": "",
"Autocomplete": true "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

BIN
deps/github.com.tar.gz vendored

Binary file not shown.

BIN
deps/golang.org.tar.gz vendored

Binary file not shown.

View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,8 +92,8 @@ 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() exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)} argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
@ -168,7 +102,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
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
} }
@ -179,16 +113,16 @@ 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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
} }
@ -201,7 +135,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -211,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)
result.Succ = false result.Code = -1
return return
} }
@ -223,24 +157,24 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset) logger.Tracef("offset [%d]", offset)
ideStub := util.Go.GetExecutableInGOBIN("gotools") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", 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 {
result.Succ = false result.Code = -1
return return
} }
@ -250,21 +184,21 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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
} }
@ -277,7 +211,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -287,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)
result.Succ = false result.Code = -1
return return
} }
@ -299,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("gotools") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", 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 {
result.Succ = false result.Code = -1
return return
} }
@ -338,22 +272,22 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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
} }
@ -366,7 +300,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -376,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)
result.Succ = false result.Code = -1
return return
} }
@ -387,24 +321,24 @@ 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("gotools") ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", 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
} }
out := strings.TrimSpace(string(output)) out := strings.TrimSpace(string(output))
if "" == out { if "" == out {
result.Succ = false result.Code = -1
return return
} }
@ -453,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-2016, b3log.org & hacpai.com // 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,36 @@ 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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) {
result.Succ = false result.Code = -1
return return
} }
@ -63,7 +69,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -73,7 +79,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)
result.Succ = false result.Code = -1
return return
} }
@ -85,7 +91,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
result.Data = data result.Data = data
fmt := conf.GetGoFmt(username) fmt := conf.GetGoFmt(uid)
argv := []string{filePath} argv := []string{filePath}
cmd := exec.Command(fmt, argv...) cmd := exec.Command(fmt, argv...)
@ -94,7 +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
result.Succ = true result.Code = 0
return return
} }
@ -106,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)
result.Succ = false result.Code = -1
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,8 +18,7 @@ package event
import ( import (
"os" "os"
"github.com/b3log/wide/log" "github.com/88250/gulu"
"github.com/b3log/wide/util"
) )
const ( const (
@ -39,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 {
@ -70,7 +69,7 @@ var UserEventQueues = queues{}
// Load initializes the event handling. // Load initializes the event handling.
func Load() { func Load() {
go func() { go func() {
defer util.Recover() 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)
@ -107,7 +106,7 @@ func (ueqs queues) New(sid string) *UserEventQueue {
ueqs[sid] = q ueqs[sid] = q
go func() { // start listening go func() { // start listening
defer util.Recover() 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)

View File

@ -1,57 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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 (
"encoding/json"
"net/http"
"path/filepath"
"github.com/b3log/wide/util"
)
// DecompressHandler handles request of decompressing zip/tar.gz.
func DecompressHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
path := args["path"].(string)
// base := filepath.Base(path)
dir := filepath.Dir(path)
if !util.File.IsExist(path) {
result.Succ = false
result.Msg = "Can't find file [" + path + "] to descompress"
return
}
err := util.Zip.Unzip(path, dir)
if nil != err {
logger.Error(err)
result.Succ = false
return
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,7 +20,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/b3log/wide/util" "github.com/88250/gulu"
) )
// GetZipHandler handles request of retrieving zip file. // GetZipHandler handles request of retrieving zip file.
@ -34,7 +34,7 @@ func GetZipHandler(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
@ -51,13 +51,13 @@ func GetZipHandler(w http.ResponseWriter, r *http.Request) {
// CreateZipHandler handles request of creating zip. // CreateZipHandler handles request of creating zip.
func CreateZipHandler(w http.ResponseWriter, r *http.Request) { func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
data := util.NewResult() data := gulu.Ret.NewResult()
defer util.RetResult(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
} }
@ -75,24 +75,24 @@ func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
dir := filepath.Dir(path) dir := filepath.Dir(path)
if !util.File.IsExist(path) { if !gulu.File.IsExist(path) {
data.Succ = false data.Code = -1
data.Msg = "Can't find file [" + path + "]" data.Msg = "Can't find file [" + path + "]"
return return
} }
zipPath := filepath.Join(dir, name) zipPath := filepath.Join(dir, name)
zipFile, err := util.Zip.Create(zipPath + ".zip") 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)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,15 +24,14 @@ 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 {
@ -61,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{}}
@ -74,18 +73,18 @@ func initAPINode() {
// 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 GetFilesHandler(w http.ResponseWriter, r *http.Request) { func GetFilesHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetGzResult(w, r, result) defer gulu.Ret.RetGzResult(w, r, result)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}} root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}}
@ -123,18 +122,18 @@ func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
// RefreshDirectoryHandler handles request of refresh a directory of file tree. // RefreshDirectoryHandler handles request of refresh a directory of file tree.
func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) { func RefreshDirectoryHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
r.ParseForm() r.ParseForm()
path := r.FormValue("path") path := r.FormValue("path")
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -156,37 +155,37 @@ func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
// GetFileHandler handles request of opening file by editor. // GetFileHandler handles request of opening file by editor.
func GetFileHandler(w http.ResponseWriter, r *http.Request) { func GetFileHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
size := util.File.GetFileSize(path) size := gulu.File.GetFileSize(path)
if size > 5242880 { // 5M if size > 5242880 { // 5M
result.Succ = false result.Code = -1
result.Msg = "This file is too large to open :(" result.Msg = "This file is too large to open :("
return return
@ -199,30 +198,30 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
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) {
result.Succ = false result.Code = -1
result.Msg = "Can't open a binary file :(" result.Msg = "Can't open a binary file :("
} else { } else {
data["content"] = content data["content"] = content
@ -232,22 +231,28 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
// SaveFileHandler handles request of saving file. // SaveFileHandler handles request of saving file.
func SaveFileHandler(w http.ResponseWriter, r *http.Request) { func SaveFileHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
@ -255,7 +260,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
sid := args["sid"].(string) sid := args["sid"].(string)
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -265,7 +270,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -276,7 +281,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
logger.Error(err) logger.Error(err)
result.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,
@ -288,29 +293,29 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
// NewFileHandler handles request of creating file or directory. // NewFileHandler handles request of creating file or directory.
func NewFileHandler(w http.ResponseWriter, r *http.Request) { func NewFileHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
if util.Go.IsAPI(path) || !session.CanAccess(username, path) { if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -322,7 +327,7 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !createFile(path, fileType) { if !createFile(path, fileType) {
result.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}
@ -331,38 +336,38 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
} }
if "f" == fileType { if "f" == fileType {
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.Username) logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
} else { } else {
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.Username) logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
} }
} }
// RemoveFileHandler handles request of removing file or directory. // RemoveFileHandler handles request of removing file or directory.
func RemoveFileHandler(w http.ResponseWriter, r *http.Request) { func RemoveFileHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
path := args["path"].(string) path := args["path"].(string)
if util.Go.IsAPI(path) || !session.CanAccess(username, path) { if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -373,7 +378,7 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !removeFile(path) { if !removeFile(path) {
result.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}
@ -381,41 +386,41 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.Username) logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
} }
// RenameFileHandler handles request of renaming file or directory. // RenameFileHandler handles request of renaming file or directory.
func RenameFileHandler(w http.ResponseWriter, r *http.Request) { func RenameFileHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
oldPath := args["oldPath"].(string) oldPath := args["oldPath"].(string)
if util.Go.IsAPI(oldPath) || if gulu.Go.IsAPI(oldPath) ||
!session.CanAccess(username, oldPath) { !session.CanAccess(uid, oldPath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
newPath := args["newPath"].(string) newPath := args["newPath"].(string)
if util.Go.IsAPI(newPath) || !session.CanAccess(username, newPath) { if gulu.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -426,7 +431,7 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
if !renameFile(oldPath, newPath) { if !renameFile(oldPath, newPath) {
result.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}
@ -434,7 +439,7 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.Username) 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.
@ -451,27 +456,27 @@ func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
// FindHandler 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 FindHandler(w http.ResponseWriter, r *http.Request) { func FindHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -479,10 +484,10 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
name := args["name"].(string) name := args["name"].(string)
userWorkspace := conf.GetUserWorkspace(username) 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)
} }
@ -492,7 +497,7 @@ func FindHandler(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: filepath.ToSlash(*r), score: len(substr)}) founds = append(founds, &foundPath{Path: filepath.ToSlash(*r), score: len(substr)})
} }
@ -505,21 +510,21 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
// SearchTextHandler 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 SearchTextHandler(w http.ResponseWriter, r *http.Request) { func SearchTextHandler(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.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
@ -527,7 +532,7 @@ func SearchTextHandler(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 {
result.Succ = false result.Code = -1
return return
} }
@ -536,7 +541,7 @@ func SearchTextHandler(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]
} }
@ -545,7 +550,7 @@ func SearchTextHandler(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)
@ -644,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 "
} }
@ -763,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
} }
@ -836,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
} }

View File

@ -1,77 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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
}
// UploadHandler handles request of file upload.
func UploadHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
q := r.URL.Query()
dir := q["path"][0]
result.Data = handleUploads(r, dir)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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 {
@ -34,14 +34,14 @@ type element struct {
// GetOutlineHandler gets outfile of a go file. // GetOutlineHandler gets outfile of a go file.
func GetOutlineHandler(w http.ResponseWriter, r *http.Request) { func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
@ -51,7 +51,7 @@ func GetOutlineHandler(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 {
result.Succ = false result.Code = -1
return return
} }

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=

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * Copyright (c) 2014-2018, 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,49 +18,57 @@
* @file frontend tool. * @file frontend tool.
* *
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a> * @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
* @version 0.1.0.0, Dec 15, 2015 * @version 0.2.0.0, Oct 5, 2018
*/ */
var gulp = require("gulp"); var gulp = require('gulp')
var concat = require('gulp-concat'); var concat = require('gulp-concat')
var minifyCSS = require('gulp-minify-css'); var cleanCSS = require('gulp-clean-css')
var uglify = require('gulp-uglify'); var uglify = require('gulp-uglify')
var sourcemaps = require("gulp-sourcemaps"); var sourcemaps = require('gulp-sourcemaps')
gulp.task('cc', function () { function minLibCSS () {
// css // css
var cssLibs = ['./static/js/lib/jquery-layout/layout-default-latest.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/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.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/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css', './static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css', './static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
'./static/js/overwrite/codemirror/theme/*.css']; './static/js/overwrite/codemirror/theme/*.css']
gulp.src(cssLibs) return gulp.src(cssLibs).
.pipe(minifyCSS()) pipe(cleanCSS()).
.pipe(concat('lib.min.css')) pipe(concat('lib.min.css')).
.pipe(gulp.dest('./static/css/')); pipe(gulp.dest('./static/css/'))
}
gulp.src('./static/js/lib/ztree/zTreeStyle.css') function minZTreeStyleCSS () {
.pipe(minifyCSS()) return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
.pipe(concat('zTreeStyle.min.css')) pipe(cleanCSS()).
.pipe(gulp.dest('./static/js/lib/ztree/')); pipe(concat('zTreeStyle.min.css')).
pipe(gulp.dest('./static/js/lib/ztree/'))
}
var cssWide = ['./static/css/dialog.css', function minWideCSS () {
var cssWide = [
'./static/css/dialog.css',
'./static/css/base.css', './static/css/base.css',
'./static/css/wide.css', './static/css/wide.css',
'./static/css/side.css', './static/css/side.css',
'./static/css/start.css', './static/css/start.css',
'./static/css/about.css' './static/css/about.css',
]; ]
gulp.src(cssWide)
.pipe(minifyCSS())
.pipe(concat('wide.min.css'))
.pipe(gulp.dest('./static/css/'));
return gulp.src(cssWide).
pipe(cleanCSS()).
pipe(concat('wide.min.css')).
pipe(gulp.dest('./static/css/'))
}
function minLibJS () {
// js // js
var jsLibs = ['./static/js/lib/jquery-2.1.1.min.js', var jsLibs = [
'./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js', './static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js', './static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js', './static/js/lib/reconnecting-websocket.js',
@ -93,8 +101,8 @@ gulp.task('cc', function () {
'./static/js/lib/codemirror-5.1/addon/fold/xml-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/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js', './static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/mode/loadmode.js', './static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment/comment.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/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.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/clike/clike.js',
@ -107,13 +115,16 @@ gulp.task('cc', function () {
'./static/js/lib/codemirror-5.1/mode/sql/sql.js', './static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js', './static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js', './static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js']; './static/js/lib/lint/go-lint.js']
gulp.src(jsLibs) return gulp.src(jsLibs).
.pipe(uglify()) pipe(uglify()).
.pipe(concat('lib.min.js')) pipe(concat('lib.min.js')).
.pipe(gulp.dest('./static/js/')); pipe(gulp.dest('./static/js/'))
}
var jsWide = ['./static/js/tabs.js', function minWideJS () {
var jsWide = [
'./static/js/tabs.js',
'./static/js/tabs.js', './static/js/tabs.js',
'./static/js/dialog.js', './static/js/dialog.js',
'./static/js/editors.js', './static/js/editors.js',
@ -124,12 +135,16 @@ gulp.task('cc', function () {
'./static/js/menu.js', './static/js/menu.js',
'./static/js/windows.js', './static/js/windows.js',
'./static/js/hotkeys.js', './static/js/hotkeys.js',
'./static/js/bottomGroup.js' './static/js/bottomGroup.js',
]; ]
gulp.src(jsWide) return gulp.src(jsWide).
.pipe(sourcemaps.init()) pipe(sourcemaps.init()).
.pipe(uglify()) pipe(uglify()).
.pipe(concat('wide.min.js')) pipe(concat('wide.min.js')).
.pipe(sourcemaps.write(".")) pipe(sourcemaps.write('.')).
.pipe(gulp.dest('./static/js/')); 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,11 +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",
"start-git_clone": "START [git clone]",
"git_clone-done": "[git clone] DONE",
"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",
@ -120,7 +114,6 @@
"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",
@ -139,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",
@ -158,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",
@ -172,12 +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",
"git_clone": "Git Clone",
"terms": "Terms", "terms": "Terms",
"download": "Download", "download": "Download",
"decompress": "Decompress", "decompress": "Decompress",
"keymap": "Keymap", "keymap": "Keymap",
"resize": "Resize" "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,11 +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] 失敗",
"start-git_clone": "[git clone] 開始",
"git_clone-done": "[git clone] 終わった",
"check_version": "更新をチェック中", "check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります", "new_version_available": "新しいバージョンがあります",
"go_env": "Go", "go_env": "Go",
@ -120,7 +114,6 @@
"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": "クローズ",
@ -139,7 +132,6 @@
"clearOutput": "空の出力", "clearOutput": "空の出力",
"export": "輸出", "export": "輸出",
"refresh": "リフレッシュ", "refresh": "リフレッシュ",
"import": "インポート",
"theme": "テーマ", "theme": "テーマ",
"tab_size": "Tab サイズ", "tab_size": "Tab サイズ",
"copy_file_path": "ファイルパスをコピー", "copy_file_path": "ファイルパスをコピー",
@ -158,26 +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": "埋め込む",
"git_clone": "Git クローン",
"terms": "利用規約", "terms": "利用規約",
"download": "ダウンロード", "download": "ダウンロード",
"decompress": "解凍する", "decompress": "解凍する",
"keymap": "キーマップ", "keymap": "キーマップ",
"resize": "サイズ変更" "resize": "サイズ変更",
"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,11 +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] 실패",
"start-git_clone": "시작 [git clone]",
"git_clone-done": "[git clone] 완성",
"check_version": "최신버전검색중", "check_version": "최신버전검색중",
"new_version_available": "최신업데이트 사용 가능", "new_version_available": "최신업데이트 사용 가능",
"go_env": "Go 환경", "go_env": "Go 환경",
@ -120,7 +114,6 @@
"team": "단체", "team": "단체",
"sing_up_error": "가입실패", "sing_up_error": "가입실패",
"user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,", "user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,",
"invalid_email": "유효하지 않은 email주소",
"password_no_match": "비밀번호 오류", "password_no_match": "비밀번호 오류",
"discard": "취소", "discard": "취소",
"close": "닫기", "close": "닫기",
@ -139,7 +132,6 @@
"clearOutput": "ouput 클리어", "clearOutput": "ouput 클리어",
"export": "내보내기", "export": "내보내기",
"refresh": "새로고침", "refresh": "새로고침",
"import": "가져오기",
"theme": "주제", "theme": "주제",
"tab_size": "Tab 크기", "tab_size": "Tab 크기",
"copy_file_path": "경로복사", "copy_file_path": "경로복사",
@ -158,26 +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": "삽입",
"git_clone": "Git clone",
"terms": "사용계약", "terms": "사용계약",
"download": "다운로드", "download": "다운로드",
"decompress": "압축풀기", "decompress": "압축풀기",
"keymap": "단축키", "keymap": "단축키",
"resize": "크기조절" "resize": "크기조절",
"sponsor": "후원사"
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,11 +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] 失败",
"start-git_clone": "开始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在检查更新", "check_version": "正在检查更新",
"new_version_available": "新版本可用", "new_version_available": "新版本可用",
"go_env": "Go 环境", "go_env": "Go 环境",
@ -120,7 +114,6 @@
"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": "关闭",
@ -139,7 +132,6 @@
"clearOutput": "清空输出", "clearOutput": "清空输出",
"export": "导出", "export": "导出",
"refresh": "刷新", "refresh": "刷新",
"import": "导入",
"theme": "主题", "theme": "主题",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"copy_file_path": "复制文件路径", "copy_file_path": "复制文件路径",
@ -158,26 +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": "嵌入",
"git_clone": "Git 克隆",
"terms": "使用条款", "terms": "使用条款",
"download": "下载", "download": "下载",
"decompress": "解压缩", "decompress": "解压缩",
"keymap": "快捷键", "keymap": "快捷键",
"resize": "调整大小" "resize": "调整大小",
"sponsor": "赞助"
} }

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,11 +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] 失敗",
"start-git_clone": "開始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在檢查更新", "check_version": "正在檢查更新",
"new_version_available": "可用新版本", "new_version_available": "可用新版本",
"go_env": "Go 環境", "go_env": "Go 環境",
@ -120,7 +114,6 @@
"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": "關閉",
@ -139,7 +132,6 @@
"clearOutput": "清空輸出", "clearOutput": "清空輸出",
"export": "導出", "export": "導出",
"refresh": "刷新", "refresh": "刷新",
"import": "導入",
"theme": "主題", "theme": "主題",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"copy_file_path": "複製檔案位置", "copy_file_path": "複製檔案位置",
@ -158,11 +150,8 @@
"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",
@ -172,12 +161,11 @@
"restore_outline": "恢復大綱", "restore_outline": "恢復大綱",
"share": "分享", "share": "分享",
"url": "連結", "url": "連結",
"short_url": "短網址",
"embeded": "嵌入", "embeded": "嵌入",
"git_clone": "Git Clone",
"terms": "使用條款", "terms": "使用條款",
"download": "下載", "download": "下載",
"decompress": "解壓縮", "decompress": "解壓縮",
"keymap": "快速鍵", "keymap": "快速鍵",
"resize": "調整大小" "resize": "調整大小",
"sponsor": "贊助"
} }

View File

@ -1,217 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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-2016, b3log.org & hacpai.com
//
// 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
}
}

287
main.go
View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,171 +23,142 @@ import (
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal"
"runtime" "runtime"
"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/scm/git" "github.com/88250/wide/session"
"github.com/b3log/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", "", "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")
confUsersWorkspaces := flag.String("users_workspaces", "", "this will overwrite Wide.UsersWorkspaces 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, *confUsersWorkspaces)
conf.FixedTimeCheckEnv() conf.FixedTimeCheckEnv()
session.FixedTimeSave() session.FixedTimeSave()
session.FixedTimeRelease() session.FixedTimeRelease()
if *confStat {
session.FixedTimeReport() session.FixedTimeReport()
}
logger.Debug("host ["+runtime.Version()+", "+runtime.GOOS+"_"+runtime.GOARCH+"], cross-compilation ", logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
util.Go.GetCrossPlatforms())
} }
// 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.SaveContentHandler)) 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 // cross-compilation
http.HandleFunc(conf.Wide.Context+"/cross", handlerWrapper(output.CrossCompilationHandler)) http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
// file tree // file tree
http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFilesHandler)) http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectoryHandler)) http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFileHandler)) http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFileHandler)) http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFileHandler)) http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFileHandler)) http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFileHandler)) http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchTextHandler)) http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.FindHandler)) http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
// outline // outline
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutlineHandler)) http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
// file export/import // file export
http.HandleFunc(conf.Wide.Context+"/file/zip/new", handlerWrapper(file.CreateZipHandler)) http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZipHandler)) http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.UploadHandler))
http.HandleFunc(conf.Wide.Context+"/file/decompress", handlerWrapper(file.DecompressHandler))
// 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.SignUpUserHandler)) 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))
// git logger.Infof("Wide is running [%s]", conf.Wide.Server)
http.HandleFunc(conf.Wide.Context+"/git/clone", handlerWrapper(git.CloneHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context) err := http.ListenAndServe("0.0.0.0:7070", nil)
err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
@ -195,57 +166,51 @@ 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 conf.Wide.Context+"/" != r.RequestURI { if "/" != r.RequestURI {
http.Redirect(w, r, conf.Wide.Context+"/", http.StatusFound) 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)
user := conf.GetUser(username) user := conf.GetUser(uid)
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,
"username": username, "sid": session.WideSessions.GenId(), "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(), "crossPlatforms": util.Go.GetCrossPlatforms()} "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
} }
@ -253,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) {
@ -262,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)
@ -286,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, "sid": sid} "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
} }
@ -302,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}
@ -324,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
} }
@ -334,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()}
@ -357,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
} }
@ -436,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-2016, b3log.org & hacpai.com // 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-2016, b3log.org & hacpai.com // 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,94 +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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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) {
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return 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)
result.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)
result.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)
result.Succ = false result.Code = -1
return return
} }
@ -112,90 +150,108 @@ 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)
result.Succ = false result.Code = -1
return return
} }
if !result.Succ { if 0 != result.Code {
return return
} }
channelRet := map[string]interface{}{} if err := cmd.Start(); nil != err {
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) logger.Error(err)
result.Code = -1
return return
} }
channelRet["cmd"] = "build"
channelRet["executable"] = executable
outReader := bufio.NewReader(stdout)
/////////
go func() {
defer gulu.Panic.Recover(nil)
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := outReader.ReadString('\n')
if io.EOF == err {
break
}
_, ok := err.(*os.PathError)
if ok {
// 构建时报 “read |0: file already closed” https://github.com/b3log/wide/issues/363
break
}
if nil != err {
logger.Warnf("%#v", err)
break
}
channelRet["output"] = line
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}()
errReader := bufio.NewReader(stderr)
var lines []string
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := errReader.ReadString('\n')
if io.EOF == err {
break
}
lines = append(lines, line)
if nil != err {
logger.Warn(err)
break
}
// path process
errOutWithPath := parsePath(curDir, line)
channelRet["output"] = "<span class='stderr'>" + errOutWithPath + "</span>"
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh() wsChannel.Refresh()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if nil == cmd.Wait() {
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir)
// 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["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n" channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
} else {
go func() { // go install, for subsequent gocode lib-path channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n"
defer util.Recover()
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 // lint process
if lines[0][0] == '#' { if lines[0][0] == '#' {
lines = lines[1:] // skip the first line lines = lines[1:] // skip the first line
} }
@ -203,7 +259,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
lints := []*Lint{} lints := []*Lint{}
for _, line := range lines { for _, line := range lines {
if len(line) < 1 { if len(line) < 1 || !strings.Contains(line, ":") {
continue continue
} }
@ -234,7 +290,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
} }
lint := &Lint{ lint := &Lint{
File: filepath.Join(curDir, file), File: filepath.ToSlash(filepath.Join(curDir, file)),
LineNo: lineNo - 1, LineNo: lineNo - 1,
Severity: lintSeverityError, Severity: lintSeverityError,
Msg: msg, Msg: msg,
@ -246,17 +302,14 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
channelRet["lints"] = lints 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] wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet) if nil == wsChannel {
return
}
err = wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Warn(err) logger.Warn(err)
} }
wsChannel.Refresh() wsChannel.Refresh()
} }
}(rand.Int())
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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"
) )
// CrossCompilationHandler handles request of cross compilation. // CrossCompilationHandler handles request of cross compilation.
func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) { func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.Succ = false result.Code = -1
return return
} }
@ -58,7 +64,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string) sid := args["sid"].(string)
filePath := args["path"].(string) filePath := args["path"].(string)
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -75,10 +81,15 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
suffix = ".exe" suffix = ".exe"
} }
cmd := exec.Command("go", "build") user := conf.GetUser(uid)
goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
for i, env := range cmd.Env { for i, env := range cmd.Env {
if strings.HasPrefix(env, "GOOS=") { if strings.HasPrefix(env, "GOOS=") {
@ -101,7 +112,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
result.Succ = false result.Code = -1
return return
} }
@ -109,12 +120,12 @@ func CrossCompilationHandler(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)
result.Succ = false result.Code = -1
return return
} }
if !result.Succ { if 0 != result.Code {
return return
} }
@ -141,17 +152,15 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err { if err := cmd.Start(); nil != err {
logger.Error(err) logger.Error(err)
result.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 building [id=%d, dir=%s]", username, sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -230,8 +239,6 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { 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] wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {

View File

@ -1,146 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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) {
result := util.NewResult()
defer util.RetResult(w, r, result)
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)
result.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)
result.Succ = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
if !result.Succ {
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.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.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.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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)
result.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)
result.Succ = false result.Code = -1
return return
} }
if !result.Succ { 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)
result.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,7 +189,7 @@ 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)

View File

@ -1,25 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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-2016, b3log.org & hacpai.com
//
// 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-2016, b3log.org & hacpai.com // 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-2016, b3log.org & hacpai.com
//
// 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-2016, b3log.org & hacpai.com // 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,272 +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 = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
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) {
result := util.NewResult() session.RunHandler(w, r, session.OutputWS)
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
} }
sid := args["sid"].(string) // StopHandler handles request of stopping a running process.
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.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)
result.Succ = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
}
wsChannel := session.OutputWS[sid]
channelRet := map[string]interface{}{}
if !result.Succ {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(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.Warn(err)
return
}
wsChannel.Refresh()
}
go func() {
defer util.Recover()
buf := outputBuf{}
count := 0
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
count++
if nil != err {
// remove the exited process from user's process set
Processes.Remove(wSession, cmd.Process)
logger.Debugf("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.Warn(err)
break
}
wsChannel.Refresh()
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
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
}
}()
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.Warn(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
}
// StopHandler handles request of stoping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) { func StopHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() session.StopHandler(w, r)
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
return
}
Processes.Kill(wSession, pid)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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)
result.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)
result.Succ = false result.Code = -1
return return
} }
if !result.Succ { if 0 != result.Code {
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)
result.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)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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)
result.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)
result.Succ = false result.Code = -1
return return
} }
if !result.Succ { if 0 != result.Code {
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)
result.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)
} }

3773
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

12
pkg.sh
View File

@ -24,7 +24,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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' 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 rm -f wide gotools gocode
@ -33,7 +33,7 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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' 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 rm -f wide gotools gocode
@ -45,7 +45,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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' 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 rm -f wide gotools gocode
@ -54,7 +54,7 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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' 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 rm -f wide gotools gocode
@ -66,7 +66,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.zip echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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 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 rm -f wide.exe gotools.exe gocode.exe
@ -75,6 +75,6 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.zip echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build go build
go build github.com/visualfc/gotools go build github.com/visualfc/gotools
go build github.com/nsf/gocode 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 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 rm -f wide.exe gotools.exe gocode.exe

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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-2016, b3log.org & hacpai.com // 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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,23 +48,23 @@ 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)
result.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"
} }
data := map[string]interface{}{} data := map[string]interface{}{}
result.Data = &data result.Data = &data
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1)) 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()
@ -66,7 +72,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
data["output"] = template.HTML(string(out)) data["output"] = template.HTML(string(out))
if nil != err { if nil != err {
result.Succ = false result.Code = -1
return return
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) 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)
result.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)
result.Succ = false result.Code = -1
return return
} }
@ -85,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)
result.Succ = false result.Code = -1
return return
} }
} }
// ShortURLHandler handles request of short URL.
func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
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)
result.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)
result.Succ = false
return
}
shortURL := url
if 0 == response["status"].(float64) {
shortURL = response["tinyurl"].(string)
}
result.Data = shortURL
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,43 +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)
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,15 +89,14 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(), "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-2016, b3log.org & hacpai.com // 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,264 +15,16 @@
package playground package playground
import ( import (
"bufio" "github.com/88250/wide/session"
"encoding/json"
"math/rand"
"net/http" "net/http"
"os/exec"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/output"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
) )
const (
outputBufMax = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
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) {
result := util.NewResult() session.RunHandler(w, r, session.PlaygroundWS)
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
} }
sid := args["sid"].(string) // StopHandler handles request of stopping a running process.
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.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)
result.Succ = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
}
wsChannel := session.PlaygroundWS[sid]
channelRet := map[string]interface{}{}
if !result.Succ {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(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()
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.Warn(err)
return
}
wsChannel.Refresh()
}
go func() {
defer util.Recover()
buf := outputBuf{}
count := 0
for {
wsChannel := session.PlaygroundWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
count++
if nil != err {
// remove the exited process from user process set
output.Processes.Remove(wSession, cmd.Process)
logger.Debugf("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.Warn(err)
break
}
wsChannel.Refresh()
break
}
oneRuneStr := string(r)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.PlaygroundWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
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"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
}
// StopHandler handles request of stoping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) { func StopHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() session.StopHandler(w, r)
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
return
}
output.Processes.Kill(wSession, pid)
} }

View File

@ -1,146 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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 git
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"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"
)
// Logger.
var logger = log.NewLogger(os.Stdout)
// Clone handles request of git clone.
func CloneHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
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)
result.Succ = false
return
}
sid := args["sid"].(string)
path := args["path"].(string)
repository := args["repository"].(string)
cmd := exec.Command("git", "clone", repository)
cmd.Dir = path
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
if !result.Succ {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [git clone]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-git_clone").(string) + "</span>\n"
channelRet["cmd"] = "start-git_clone"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [git clone] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "git clone"
// read all
buf, err := ioutil.ReadAll(reader)
if nil != err {
logger.Warn(err)
// TODO: handle clone error
}
logger.Debugf("User [%s, %s] 's running [git clone] [runningId=%d] has done: %s", username, sid, runningId, string(buf))
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "git_clone-done").(string) + "</span>\n"
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

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-2016, b3log.org & hacpai.com // 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,
@ -31,14 +31,15 @@ import (
"path/filepath" "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/go-fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -46,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>
@ -74,7 +77,7 @@ 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
@ -102,7 +105,7 @@ 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 util.Recover() 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")
@ -110,7 +113,7 @@ func FixedTimeRelease() {
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)
} }
@ -121,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
@ -129,14 +132,14 @@ 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 util.Recover() defer gulu.Panic.Recover(nil)
for _ = range time.Tick(10 * time.Minute) { for _ = range time.Tick(10 * time.Minute) {
users := userReports{} users := userReports{}
@ -146,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
} }
@ -154,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})
} }
} }
@ -173,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
} }
} }
@ -219,7 +222,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession := WideSessions.Get(sid) wSession := WideSessions.Get(sid)
if nil == wSession { if nil == wSession {
httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession, _ := HTTPSession.Get(r, CookieName)
if httpSession.IsNew { if httpSession.IsNew {
return return
@ -230,7 +233,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession = WideSessions.new(httpSession, sid) wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username) 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))
@ -262,7 +265,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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)
return return
} }
@ -281,8 +284,8 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
// SaveContentHandler handles request of session content string. // SaveContentHandler handles request of session content string.
func SaveContentHandler(w http.ResponseWriter, r *http.Request) { func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) defer gulu.Ret.RetResult(w, r, result)
args := struct { args := struct {
Sid string Sid string
@ -291,14 +294,14 @@ func SaveContentHandler(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)
result.Succ = false result.Code = -1
return return
} }
wSession := WideSessions.Get(args.Sid) wSession := WideSessions.Get(args.Sid)
if nil == wSession { if nil == wSession {
result.Succ = false result.Code = -1
return return
} }
@ -306,7 +309,7 @@ func SaveContentHandler(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
@ -375,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)
} }
} }
@ -409,12 +412,12 @@ func (sessions *wSessions) Remove(sid string) {
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
} }
@ -422,14 +425,14 @@ 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)
} }
} }
@ -442,12 +445,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
now := time.Now() now := time.Now()
ret := &WideSession{ ret := &WideSession{
ID: sid, ID: sid,
Username: username, UserId: uid,
HTTPSession: httpSession, HTTPSession: httpSession,
EventQueue: nil, EventQueue: nil,
State: sessionStateActive, State: sessionStateActive,
@ -458,7 +461,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
*sessions = append(*sessions, ret) *sessions = append(*sessions, ret)
if "playground" == username { if "playground" == uid {
return ret return ret
} }
@ -474,12 +477,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
} }
go func() { go func() {
defer util.Recover() defer gulu.Panic.Recover(nil)
for { for {
ch := SessionWS[sid] ch := SessionWS[sid]
if nil == ch { if nil == ch {
return // release this gorutine return // release this goroutine
} }
select { select {
@ -489,7 +492,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
ch = SessionWS[sid] ch = SessionWS[sid]
if nil == ch { if nil == ch {
return // release this gorutine return // release this goroutine
} }
logger.Trace(event) logger.Trace(event)
@ -497,7 +500,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
if event.Op&fsnotify.Create == fsnotify.Create { if event.Op&fsnotify.Create == fsnotify.Create {
fileType := "f" fileType := "f"
if util.File.IsDir(path) { if gulu.File.IsDir(path) {
fileType = "d" fileType = "d"
if err = watcher.Add(path); nil != err { if err = watcher.Add(path); nil != err {
@ -505,17 +508,13 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
} }
} }
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
"cmd": "create-file", "type": fileType}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove { } else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
"cmd": "remove-file", "type": ""}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename { } else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
"cmd": "rename-file", "type": ""}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} }
case err := <-watcher.Errors: case err := <-watcher.Errors:
@ -527,12 +526,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
}() }()
go func() { go func() {
defer util.Recover() defer gulu.Panic.Recover(nil)
workspaces := filepath.SplitList(conf.GetUserWorkspace(username)) workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
for _, workspace := range workspaces { for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error { filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if ".git" == f.Name() { // XXX: discard other unconcered dirs if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
return filepath.SkipDir return filepath.SkipDir
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com // 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,33 +15,26 @@
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"
notAllowRegister = "not allow register"
) )
// Exclusive lock for adding user. // Exclusive lock for adding user.
@ -49,57 +42,70 @@ 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
result := util.NewResult() result := gulu.Ret.NewResult()
defer util.RetResult(w, r, result) defer gulu.Ret.RetResult(w, r, result)
args := struct { args := struct {
FontFamily string FontFamily string
FontSize string FontSize string
GoFmt string GoFmt string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
Keymap string Keymap string
Workspace string Workspace string
Username string Username string
Password string
Email string
Locale string Locale string
Theme string Theme string
EditorFontFamily string EditorFontFamily string
@ -111,7 +117,7 @@ func PreferenceHandler(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)
result.Succ = false result.Code = -1
return return
} }
@ -119,17 +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 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
@ -139,144 +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
result.Succ = user.Save() if user.Save() {
result.Code = 0
} else {
result.Code = -1
} }
// LoginHandler handles request of user login.
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
result := util.NewResult()
defer util.RetResult(w, r, result)
args := struct {
Username string
Password string
}{}
args.Username = r.FormValue("username")
args.Password = r.FormValue("password")
result.Succ = false
for _, user := range conf.Users {
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
result.Succ = true
break
}
}
if !result.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) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// SignUpUserHandler handles request of registering user.
func SignUpUserHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": conf.Wide.UsersWorkspaces,
"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
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
username := args["username"].(string)
password := args["password"].(string)
email := args["email"].(string)
msg := addUser(username, password, email)
if userCreated != msg {
result.Succ = false
result.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).
@ -284,25 +158,19 @@ func SignUpUserHandler(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() {
defer util.Recover() defer gulu.Panic.Recover(nil)
for _ = range time.Tick(time.Minute) { for _ = range time.Tick(time.Minute) {
users := getOnlineUsers() SaveOnlineUsers()
for _, u := range users {
if u.Save() {
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
} }
}() }()
} }
// CanAccess determines whether the user specified by the given username can access the specified path. // CanAccess determines whether the user specified by the given user id can access the specified path.
func CanAccess(username, path string) bool { func CanAccess(userId, path string) bool {
path = filepath.FromSlash(path) path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(userId)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces { for _, workspace := range workspaces {
@ -314,23 +182,33 @@ func CanAccess(username, path string) bool {
return false 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
} }
@ -340,135 +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 !conf.Wide.AllowRegister {
return notAllowRegister
}
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
}
}
workspace := filepath.Join(conf.Wide.UsersWorkspaces, 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,189 +0,0 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// 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"
"net/http"
"os"
"os/exec"
"runtime"
"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)
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"sid": session.WideSessions.GenId()}
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-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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 @@
* themes for base. * themes for base.
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015 * @version 0.2.0.0, Oct 5, 2018
*/ */
/* start reset & function */ /* start reset & function */
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -140,18 +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-"] {
[class^="ico-"] { /* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon'; 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;
@ -160,177 +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-git:before {
content: "\e624";
}
.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-2016, b3log.org & hacpai.com * 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
*/ */

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

Binary file not shown.

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

@ -6,52 +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="&#xe624;" 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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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="&#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;" 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: 31 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.

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,11 @@
@@ -1,100 +0,0 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,
@ -16,7 +15,15 @@
*/ */
body { body {
overflow: auto; display: flex;
flex-direction: column;
max-height: 100vh;
}
.main {
flex: 1;
min-height: 1px;
display: flex;
} }
.header { .header {
@ -47,7 +54,7 @@ body{
.share-panel { .share-panel {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
width: 226px; width: 190px;
padding: 5px 0px; padding: 5px 0px;
right: 0px; right: 0px;
line-height: normal; line-height: normal;
@ -75,8 +82,12 @@ body{
height: 70%; height: 70%;
} }
#output { .bottom-window-group {
height: 30%; height: 30%;
}
#output {
height: 100%;
width: 100%; width: 100%;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
@ -115,6 +126,10 @@ body{
border-left: 1px solid #919191; border-left: 1px solid #919191;
} }
#goNews::-webkit-scrollbar {
display: none;
}
#goNews li a { #goNews li a {
display: block; display: block;
padding: 8px 10px; padding: 8px 10px;
@ -123,6 +138,12 @@ body{
color: #666; color: #666;
} }
#goNews li a.fn-right {
padding: 0;
border: 0;
color: #4285f4;
}
#goNews li a:hover { #goNews li a:hover {
text-decoration: none; text-decoration: none;
background-color: #f9f9f9; background-color: #f9f9f9;

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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,15 +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;
height: 632px; flex: 1;
padding-top: 222px; display: flex;
align-items: center;
} }
.content h2 { .content h2 {
@ -62,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 {
@ -162,7 +185,7 @@
.footer a { .footer a {
text-decoration: none; text-decoration: none;
color: #4183c4; color: #4285f4;
} }
.footer a:hover { .footer a:hover {
@ -178,7 +201,7 @@
/* 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;
@ -198,4 +221,21 @@
margin-top: 20px; margin-top: 20px;
font-size: 16px; font-size: 16px;
} }
.start {
text-align: center;
}
.start .btn {
color: #fff;
}
.start svg {
fill: currentColor;
}
.start__aciton {
display: flex;
align-items: center;
}
/* end sign up */ /* end sign up */

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,
@ -39,7 +39,6 @@
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 {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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,
@ -136,7 +136,7 @@
.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; top: 21px;
@ -259,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;
} }

View File

@ -1,6 +1,6 @@
.dialog-close-icon,.dialog-close-icon:hover{text-decoration:none}.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;filter:alpha(opacity=30);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}.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} .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}
body,ul{margin:0}body,button,input{font-family:Helvetica}.list li,body{overflow:hidden}::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:0 solid 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;color:#000}ul{padding:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}.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;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?35cb2z);src:url(fonts/icomoon.eot?#iefix35cb2z) format('embedded-opentype'),url(fonts/icomoon.woff?35cb2z) format('woff'),url(fonts/icomoon.ttf?35cb2z) format('truetype'),url(fonts/icomoon.svg?35cb2z#icomoon) format('svg');font-weight:400;font-style:normal}.font-ico,[class^=ico-]{font-family:icomoon;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-book:before{content:"\e623"}.ico-price:before{content:"\e616"}.ico-start:before{content:"\e9d7"}.ico-share:before{content:"\e61f"}.ico-github:before{content:"\f00a"}.ico-git:before{content:"\e624"}.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{content:"\e602"}.ico-editor:before{content:"\e604"}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-notification:before{content:"\e607"}.ico-report:before{content:"\e605"}.ico-comment:before{content:"\e620"}.ico-goline:before{content:"\e61e"}.ico-info:before{content:"\e61d"}.ico-signup:before{content:"\e606"}.ico-signout:before{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{content:"\f0ed"}.ico-refresh:before{content:"\f021"}.ico-remove:before{content:"\e60b"}.ico-save:before{content:"\f0c7"}.ico-max:before{content:"\e609"}.ico-format:before{content:"\e612"}.ico-buildrun:before{content:"\e60c"}.ico-stop:before{content:"\e60f"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-close:before{content:"\e611"} ::-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 li,.tabs>div{padding:0 5px;cursor:pointer}.footer .cursor,.frame li,.menu>ul>li>span,.notification-count,.tabs>div{cursor:pointer}.ico,.menu .split,.menu>ul>li,.tabs>div{float:left}.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{line-height:25px}.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{line-height:20px;height:20px}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.edit-exprinfo,.edit-panel{position:absolute;overflow:hidden}.menu{display:block!important}.menu>ul>li>span{font-size:12px;line-height:21px;padding:4px 7px}.menu .split{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:226px;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{left:20%;width:60%;height:70%;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{z-index:10;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-get,.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 .get-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 .get-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}.notification-count{float:right;display:none;background-color:#9d0000;color:#FFF;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px} .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}.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} .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;text-shadow:0 1px rgba(0,0,0,.4)}#startPage .details li.border,#startPage .news li{border-bottom:1px solid #919191}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px}#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 .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap} #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} #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-2016, b3log.org & hacpai.com * 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,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.0.1, Dec 8, 2015 * @version 1.1.1.1, Mar 15, 2017
*/ */
var bottomGroup = { var bottomGroup = {
tabs: undefined, tabs: undefined,
@ -82,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-2016, b3log.org & hacpai.com * 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,7 +24,7 @@
$.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-2016, b3log.org & hacpai.com * 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,
@ -178,7 +178,7 @@ 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']);
@ -246,9 +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").load(config.context + '/start?sid=' + config.wideSessionId); $("#startPage").load('/start?sid=' + config.wideSessionId);
$.ajax({ $.ajax({
url: "https://hacpai.com/apis/articles?tags=wide,golang&p=1&size=20", 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",
@ -264,7 +264,8 @@ var editors = {
length = 9; 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>"
@ -328,7 +329,7 @@ var editors = {
$.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) {
@ -434,11 +435,11 @@ 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 (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -582,11 +583,11 @@ 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 (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -612,11 +613,11 @@ 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 (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -731,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']);

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,

124
static/js/lib.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,7 @@ xquery version &quot;1.0-ml&quot;;
: 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 &quot;AS IS&quot; BASIS, : distributed under the License is distributed on an &quot;AS IS&quot; BASIS,

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015 * @version 1.0.1.3, Oct 5, 2018
*/ */
var menu = { var menu = {
init: function () { init: function () {
@ -55,14 +55,13 @@ var menu = {
var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content') var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content')
+ " #golang#"); + " #golang#");
urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic; urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic;
urls.tencent = "http://share.v.t.qq.com/index.php?c=share&a=index&title=" + title + urls.qqz = "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=" + url + "&sharesource=qzone&title=" + title+ "&pics=" + pic;
"&url=" + url + "&pic=" + pic;
window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618"); window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618");
}); });
}, },
_initAbout: function () { _initAbout: function () {
$("#dialogAbout").load(config.context + '/about', function () { $("#dialogAbout").load('/about', function () {
$("#dialogAbout").dialog({ $("#dialogAbout").dialog({
"modal": true, "modal": true,
"title": config.label.about, "title": config.label.about,
@ -160,12 +159,12 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/logout', url: '/logout',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (result.succ) { if (0 == result.code) {
window.location.href = config.context + "/login"; window.location.href = "/login";
} }
} }
}); });
@ -173,33 +172,6 @@ var menu = {
openAbout: function () { openAbout: function () {
$("#dialogAbout").dialog("open"); $("#dialogAbout").dialog("open");
}, },
goget: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.go-get").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: config.context + '/go/get',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function () {
bottomGroup.resetOutput();
},
success: function (result) {
}
});
},
goinstall: function () { goinstall: function () {
menu.saveAllFiles(); menu.saveAllFiles();
@ -217,7 +189,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/install', url: '/go/install',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -245,7 +217,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/test', url: '/go/test',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -273,7 +245,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/vet', url: '/go/vet',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -308,15 +280,16 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
bottomGroup.resetOutput(); bottomGroup.resetOutput();
},
success: function (result) {
$("#buildRun").addClass("ico-stop") $("#buildRun").addClass("ico-stop")
.removeClass("ico-buildrun").attr("title", config.label.stop); .removeClass("ico-buildrun").attr("title", config.label.stop);
},
success: function (result) {
} }
}); });
}, },
@ -340,7 +313,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -351,7 +324,7 @@ var menu = {
}); });
}, },
_initPreference: function () { _initPreference: function () {
$("#dialogPreference").load(config.context + '/preference', function () { $("#dialogPreference").load('/preference', function () {
$("#dialogPreference input").keyup(function () { $("#dialogPreference input").keyup(function () {
var isChange = false, var isChange = false,
emptys = [], emptys = [],
@ -424,6 +397,9 @@ var menu = {
$fontFamily = $dialogPreference.find("input[name=fontFamily]"), $fontFamily = $dialogPreference.find("input[name=fontFamily]"),
$fontSize = $dialogPreference.find("input[name=fontSize]"), $fontSize = $dialogPreference.find("input[name=fontSize]"),
$goFmt = $dialogPreference.find("select[name=goFmt]"), $goFmt = $dialogPreference.find("select[name=goFmt]"),
$GoBuildArgsForLinux = $dialogPreference.find("input[name=GoBuildArgsForLinux]"),
$GoBuildArgsForWindows = $dialogPreference.find("input[name=GoBuildArgsForWindows]"),
$GoBuildArgsForDarwin = $dialogPreference.find("input[name=GoBuildArgsForDarwin]"),
$workspace = $dialogPreference.find("input[name=workspace]"), $workspace = $dialogPreference.find("input[name=workspace]"),
$password = $dialogPreference.find("input[name=password]"), $password = $dialogPreference.find("input[name=password]"),
$email = $dialogPreference.find("input[name=email]"), $email = $dialogPreference.find("input[name=email]"),
@ -440,9 +416,11 @@ var menu = {
"fontFamily": $fontFamily.val(), "fontFamily": $fontFamily.val(),
"fontSize": $fontSize.val(), "fontSize": $fontSize.val(),
"goFmt": $goFmt.val(), "goFmt": $goFmt.val(),
"GoBuildArgsForLinux": $GoBuildArgsForLinux.val(),
"GoBuildArgsForWindows": $GoBuildArgsForWindows.val(),
"GoBuildArgsForDarwin": $GoBuildArgsForDarwin.val(),
"workspace": $workspace.val(), "workspace": $workspace.val(),
"password": $password.val(), "password": $password.val(),
"email": $email.val(),
"locale": $locale.val(), "locale": $locale.val(),
"theme": $theme.val(), "theme": $theme.val(),
"editorFontFamily": $editorFontFamily.val(), "editorFontFamily": $editorFontFamily.val(),
@ -459,16 +437,19 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/preference', url: '/preference',
data: JSON.stringify(request), data: JSON.stringify(request),
success: function (result, textStatus, jqXHR) { success: function (result, textStatus, jqXHR) {
if (!result.succ) { if (0 != result.code) {
return false; return false;
} }
$fontFamily.data("value", $fontFamily.val()); $fontFamily.data("value", $fontFamily.val());
$fontSize.data("value", $fontSize.val()); $fontSize.data("value", $fontSize.val());
$goFmt.data("value", $goFmt.val()); $goFmt.data("value", $goFmt.val());
$GoBuildArgsForLinux.data("value", $GoBuildArgsForLinux.val());
$GoBuildArgsForWindows.data("value", $GoBuildArgsForWindows.val());
$GoBuildArgsForDarwin.data("value", $GoBuildArgsForDarwin.val());
$workspace.data("value", $workspace.val()); $workspace.data("value", $workspace.val());
$password.data("value", $password.val()); $password.data("value", $password.val());
$email.data("value", $email.val()); $email.data("value", $email.val());
@ -487,7 +468,7 @@ var menu = {
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)"); var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
$okBtn.prop("disabled", true); $okBtn.prop("disabled", true);
$("#themesLink").attr("href", config.staticServer + '/static/css/themes/' + $theme.val() + '.css'); $("#themesLink").attr("href", '/static/css/themes/' + $theme.val() + '.css');
config.editorTheme = $editorTheme.val(); config.editorTheme = $editorTheme.val();
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015 * @version 1.0.0.2, Jun 23, 2019
*/ */
var notification = { var notification = {
init: function () { init: function () {
@ -35,7 +35,7 @@ var notification = {
var notificationWS = new ReconnectingWebSocket(config.channel + '/notification/ws?sid=' + config.wideSessionId); var notificationWS = new ReconnectingWebSocket(config.channel + '/notification/ws?sid=' + config.wideSessionId);
notificationWS.onopen = function () { notificationWS.onopen = function () {
console.log('[notification onopen] connected'); // console.log('[notification onopen] connected');
}; };
notificationWS.onmessage = function (e) { notificationWS.onmessage = function (e) {
@ -44,7 +44,7 @@ var notification = {
notificationHTML = ''; notificationHTML = '';
if (data.cmd && "init-notification" === data.cmd) { if (data.cmd && "init-notification" === data.cmd) {
console.log('[notification onmessage]' + e.data); // console.log('[notification onmessage]' + e.data);
return; return;
} }
@ -58,11 +58,11 @@ var notification = {
}; };
notificationWS.onclose = function (e) { notificationWS.onclose = function (e) {
console.log('[notification onclose] disconnected (' + e.code + ')'); // console.log('[notification onclose] disconnected (' + e.code + ')');
}; };
notificationWS.onerror = function (e) { notificationWS.onerror = function (e) {
console.log('[notification onerror]'); console.log('[notification onerror]', e);
}; };
} }
}; };

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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

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

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,14 +19,13 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015 * @version 1.0.0.3, Jun 23, 2019
*/ */
var playground = { var playground = {
autocompleteMutex: false, autocompleteMutex: false,
editor: undefined, editor: undefined,
pid: undefined, pid: undefined,
_resize: function () { _resize: function () {
$('#goNews, #editorDivWrap').height($(window).height() - 40 - $(".footer").height());
playground.editor.setSize("auto", ($("#editorDiv").parent().height() * 0.7) + "px"); playground.editor.setSize("auto", ($("#editorDiv").parent().height() * 0.7) + "px");
}, },
_initShare: function () { _initShare: function () {
@ -53,8 +52,7 @@ var playground = {
var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content') var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content')
+ " #golang#"); + " #golang#");
urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic; urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic;
urls.tencent = "http://share.v.t.qq.com/index.php?c=share&a=index&title=" + title + urls.qqz = "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=" + url + "&sharesource=qzone&title=" + title+ "&pics=" + pic;
"&url=" + url + "&pic=" + pic;
window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618"); window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618");
@ -83,7 +81,6 @@ var playground = {
var autocompleteHints = []; var autocompleteHints = [];
if (playground.autocompleteMutex && editor.state.completionActive) { if (playground.autocompleteMutex && editor.state.completionActive) {
console.log(1);
return; return;
} }
@ -92,7 +89,7 @@ var playground = {
$.ajax({ $.ajax({
async: false, // 同步执行 async: false, // 同步执行
type: 'POST', type: 'POST',
url: config.context + '/playground/autocomplete', url: '/playground/autocomplete',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
@ -276,28 +273,26 @@ var playground = {
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId); var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
sessionWS.onopen = function () { sessionWS.onopen = function () {
console.log('[session onopen] connected'); // console.log('[session onopen] connected');
}; };
sessionWS.onmessage = function (e) { sessionWS.onmessage = function (e) {
console.log('[session onmessage]' + e.data); // console.log('[session onmessage]' + e.data);
}; };
sessionWS.onclose = function (e) { sessionWS.onclose = function (e) {
console.log('[session onclose] disconnected (' + e.code + ')'); // console.log('[session onclose] disconnected (' + e.code + ')');
}; };
sessionWS.onerror = function (e) { sessionWS.onerror = function (e) {
console.log('[session onerror] ' + JSON.parse(e)); // console.log('[session onerror] ' + JSON.parse(e));
}; };
var playgroundWS = new ReconnectingWebSocket(config.channel + '/playground/ws?sid=' + config.wideSessionId); var playgroundWS = new ReconnectingWebSocket(config.channel + '/playground/ws?sid=' + config.wideSessionId);
playgroundWS.onopen = function () { playgroundWS.onopen = function () {
console.log('[playground onopen] connected'); // console.log('[playground onopen] connected');
}; };
playgroundWS.onmessage = function (e) { playgroundWS.onmessage = function (e) {
console.log('[playground onmessage]' + e.data);
var data = JSON.parse(e.data); var data = JSON.parse(e.data);
if ("init-playground" === data.cmd) { if ("init-playground" === data.cmd) {
@ -306,19 +301,30 @@ var playground = {
playground.pid = data.pid; playground.pid = data.pid;
var val = $("#output").val(); var output = $("#output").html();
$("#output").val(val + data.output); if ("" === output) {
output = "<pre>" + data.output + "</pre>";
} else {
output = output.replace(/<\/pre>$/g, data.output + '</pre>');
}
output = output.replace(/\r/g, '');
output = output.replace(/\n/g, '<br/>');
if (-1 !== output.indexOf("<br/>")) {
output = Autolinker.link(output);
}
$("#output").html(output);
}; };
playgroundWS.onclose = function (e) { playgroundWS.onclose = function (e) {
console.log('[playground onclose] disconnected (' + e.code + ')'); // console.log('[playground onclose] disconnected (' + e.code + ')');
}; };
playgroundWS.onerror = function (e) { playgroundWS.onerror = function (e) {
console.log('[playground onerror] ' + JSON.parse(e)); console.log('[playground onerror] ', e);
}; };
}, },
_initGoNews: function () { _initGoNews: function () {
$.ajax({ $.ajax({
url: "https://hacpai.com/apis/articles?tags=wide,golang&p=1&size=20", 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",
@ -330,7 +336,8 @@ var playground = {
var length = articles.length; var length = articles.length;
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>"
@ -356,7 +363,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -364,8 +371,7 @@ var playground = {
playground.editor.setValue(data.code); playground.editor.setValue(data.code);
if (!result.succ) { if (0 != result.code) {
console.log(data);
return; return;
} }
@ -373,23 +379,9 @@ var playground = {
var request = newWideRequest(); var request = newWideRequest();
request.url = url; request.url = url;
$.ajax({
type: 'POST',
url: config.context + '/playground/short-url',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (!result.succ) {
console.log(result);
return;
}
var html = '<div class="fn-clear"><label>' + config.label.url var html = '<div class="fn-clear"><label>' + config.label.url
+ config.label.colon + '</label><a href="' + config.label.colon + '</label><a href="'
+ url + '" target="_blank">' + url + "</a><br/>"; + url + '" target="_blank">' + url + "</a><br/>";
html += '<label>' + config.label.short_url + config.label.colon
+ '</label><a href="' + result.data + '" target="_blank">'
+ result.data + '</a><br/>';
html += '<label>' + config.label.embeded + config.label.colon html += '<label>' + config.label.embeded + config.label.colon
+ '</label><br/><textarea rows="5" style="width:100%" readonly><iframe style="border:1px solid" src="' + '</label><br/><textarea rows="5" style="width:100%" readonly><iframe style="border:1px solid" src="'
+ url + '" width="99%" height="600"></iframe></textarea>'; + url + '" width="99%" height="600"></iframe></textarea>';
@ -397,7 +389,6 @@ var playground = {
$("#dialogShare").html(html); $("#dialogShare").html(html);
$("#dialogShare").dialog("open"); $("#dialogShare").dialog("open");
}});
} }
}); });
}, },
@ -420,7 +411,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/stop', url: '/playground/stop',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json" dataType: "json"
}); });
@ -439,11 +430,11 @@ var playground = {
var request = newWideRequest(); var request = newWideRequest();
request.code = code; request.code = code;
$("#output").val(""); $("#output").html("");
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -452,7 +443,7 @@ var playground = {
playground.editor.setValue(data.code); playground.editor.setValue(data.code);
playground.editor.setCursor(cursor); playground.editor.setCursor(cursor);
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -462,17 +453,15 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/build', url: '/playground/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
console.log(result);
var data = result.data; var data = result.data;
$("#output").val(data.output); $("#output").html(data.output);
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -482,7 +471,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/run', url: '/playground/run',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -509,7 +498,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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 @@
* @file session.js * @file session.js
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 1.1.0.1, Dec 8, 2015 * @version 1.1.0.2, Jun 23, 2019
*/ */
var session = { var session = {
init: function () { init: function () {
@ -74,7 +74,7 @@ var session = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/session/save', url: '/session/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -158,8 +158,6 @@ var session = {
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId); var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
sessionWS.onopen = function () { sessionWS.onopen = function () {
console.log('[session onopen] connected');
var dateFormat = function (time, fmt) { var dateFormat = function (time, fmt) {
var date = new Date(time); var date = new Date(time);
var dateObj = { var dateObj = {
@ -238,7 +236,7 @@ var session = {
} }
}; };
sessionWS.onclose = function (e) { sessionWS.onclose = function (e) {
console.log('[session onclose] disconnected (' + e.code + ')'); // console.log('[session onclose] disconnected (' + e.code + ')');
var data = {type: "Network", severity: "ERROR", var data = {type: "Network", severity: "ERROR",
message: "Disconnected from server, trying to reconnect it [sid=" + config.wideSessionId + "]"}, message: "Disconnected from server, trying to reconnect it [sid=" + config.wideSessionId + "]"},
@ -253,7 +251,7 @@ var session = {
$(".notification-count").show(); $(".notification-count").show();
}; };
sessionWS.onerror = function (e) { sessionWS.onerror = function (e) {
console.log('[session onerror]'); console.log('[session onerror]', e);
}; };
} }
}; };

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* 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.
*/
/*
* @file shell.js
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015
*/
var shell = {
_shellWS: undefined,
_initWS: function () {
shell.shellWS = new ReconnectingWebSocket(config.channel + '/shell/ws?sid=' + config.wideSessionId);
shell.shellWS.onopen = function () {
console.log('[shell onopen] connected');
};
shell.shellWS.onmessage = function (e) {
console.log('[shell onmessage]' + e.data);
var data = JSON.parse(e.data);
if ('init-shell' !== data.cmd) {
$('#shellOutput').val(data.output);
}
};
shell.shellWS.onclose = function (e) {
console.log('[shell onclose] disconnected (' + e.code + ')');
};
shell.shellWS.onerror = function (e) {
console.log('[shell onerror] ' + e);
};
},
init: function () {
this._initWS();
$('#shellInput').keydown(function (event) {
if (13 === event.which) {
var input = {
cmd: $('#shellInput').val()
};
shell.shellWS.send(JSON.stringify(input));
$('#shellInput').val('');
}
});
}
};
$(document).ready(function () {
shell.init();
});

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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-2016, b3log.org & hacpai.com * 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,
@ -160,11 +160,11 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file/zip/new', url: '/file/zip/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogAlert").dialog("open", result.msg); $("#dialogAlert").dialog("open", result.msg);
return false; return false;
@ -175,7 +175,7 @@ var tree = {
}); });
if (isSucc) { if (isSucc) {
window.open(config.context + '/file/zip?path=' + wide.curNode.path + ".zip"); window.open('/file/zip?path=' + wide.curNode.path + ".zip");
} }
}, },
crossCompile: function (platform) { crossCompile: function (platform) {
@ -186,11 +186,11 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/cross', url: '/cross',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogAlert").dialog("open", result.msg); $("#dialogAlert").dialog("open", result.msg);
return false; return false;
@ -198,28 +198,6 @@ var tree = {
} }
}); });
}, },
decompress: function () {
var request = newWideRequest();
request.path = wide.curNode.path;
$.ajax({
async: false,
type: 'POST',
url: config.context + '/file/decompress',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (!result.succ) {
$("#dialogAlert").dialog("open", result.msg);
return false;
}
var dir = wide.curNode.getParentNode();
tree.fileTree.reAsyncChildNodes(dir, "refresh");
}
});
},
refresh: function (it) { refresh: function (it) {
if (it) { if (it) {
if ($(it).hasClass("disabled")) { if ($(it).hasClass("disabled")) {
@ -229,31 +207,6 @@ var tree = {
tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh", true); tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh", true);
}, },
gitClone: function (it) {
if (it) {
if ($(it).hasClass("disabled")) {
return false;
}
}
$("#dialogGitClonePrompt").dialog('open');
},
import: function () {
var request = newWideRequest();
request.path = wide.curNode.path;
$('#importFileupload').fileupload({
url: "/file/upload?path=" + request.path,
dataType: 'json',
formData: request,
done: function (e, result) {
tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh");
},
fail: function () {
console.log(arguments);
}
});
},
init: function () { init: function () {
$("#file").click(function () { $("#file").click(function () {
$(this).focus(); $(this).focus();
@ -263,11 +216,11 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/files', url: '/files',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (result.succ) { if (0 == result.code) {
var $dirRMenu = $("#dirRMenu"); var $dirRMenu = $("#dirRMenu");
var $fileRMenu = $("#fileRMenu"); var $fileRMenu = $("#fileRMenu");
var setting = { var setting = {
@ -282,7 +235,7 @@ var tree = {
}, },
async: { async: {
enable: true, enable: true,
url: config.context + "/file/refresh", url: "/file/refresh",
autoParam: ["path"] autoParam: ["path"]
}, },
callback: { callback: {
@ -417,11 +370,11 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file', url: '/file',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogAlert").dialog("open", result.msg); $("#dialogAlert").dialog("open", result.msg);
return false; return false;
@ -444,7 +397,7 @@ var tree = {
if ("img" === data.mode) { // 是图片文件的话新建 tab 打开 if ("img" === data.mode) { // 是图片文件的话新建 tab 打开
// 最好是开 tab但这个最终取决于浏览器设置 // 最好是开 tab但这个最终取决于浏览器设置
var w = window.open(config.context + data.path); var w = window.open(data.path);
return false; return false;
} }
@ -506,11 +459,11 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/search/text', url: '/file/search/text',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -542,11 +495,11 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/rename', url: '/file/rename',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogRenamePrompt").dialog("close"); $("#dialogRenamePrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification"); bottomGroup.tabs.setCurrent("notification");
windows.flowBottom(); windows.flowBottom();

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2016, b3log.org & hacpai.com * 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,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015 * @version 1.0.0.2, Jun 23, 2019
*/ */
var wide = { var wide = {
curNode: undefined, curNode: undefined,
@ -38,11 +38,11 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
async: false, async: false,
url: config.context + '/outline', url: '/outline',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -121,11 +121,11 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/remove', url: '/file/remove',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogRemoveConfirm").dialog("close"); $("#dialogRemoveConfirm").dialog("close");
bottomGroup.tabs.setCurrent("notification"); bottomGroup.tabs.setCurrent("notification");
windows.flowBottom(); windows.flowBottom();
@ -159,11 +159,11 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/new', url: '/file/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogNewFilePrompt").dialog("close"); $("#dialogNewFilePrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification"); bottomGroup.tabs.setCurrent("notification");
windows.flowBottom(); windows.flowBottom();
@ -203,11 +203,11 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/new', url: '/file/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogNewDirPrompt").dialog("close"); $("#dialogNewDirPrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification"); bottomGroup.tabs.setCurrent("notification");
windows.flowBottom(); windows.flowBottom();
@ -264,11 +264,11 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/find/name', url: '/file/find/name',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
return; return;
} }
@ -339,44 +339,15 @@ var wide = {
editor.focus(); editor.focus();
} }
}); });
$("#dialogGitClonePrompt").dialog({
"modal": true,
"height": 52,
"width": 360,
"title": config.label.git_clone,
"okText": config.label.confirm,
"cancelText": config.label.cancel,
"afterOpen": function () {
$("#dialogGitClonePrompt > input").val('').focus();
$("#dialogGitClonePrompt").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
},
"ok": function () {
$("#dialogGitClonePrompt").dialog("close");
var request = newWideRequest();
request.path = wide.curNode.path;
request.repository = $("#dialogGitClonePrompt > input").val();
$.ajax({
type: 'POST',
url: config.context + '/git/clone',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
}
});
}
});
}, },
_initWS: function () { _initWS: function () {
var outputWS = new ReconnectingWebSocket(config.channel + '/output/ws?sid=' + config.wideSessionId); var outputWS = new ReconnectingWebSocket(config.channel + '/output/ws?sid=' + config.wideSessionId);
outputWS.onopen = function () { outputWS.onopen = function () {
console.log('[output onopen] connected'); // console.log('[output onopen] connected');
}; };
outputWS.onmessage = function (e) { outputWS.onmessage = function (e) {
console.log('[output onmessage]' + e.data); // console.log('[output onmessage]' + e.data);
var data = JSON.parse(e.data); var data = JSON.parse(e.data);
if (goLintFound) { if (goLintFound) {
@ -389,7 +360,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/run', url: '/run',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json" dataType: "json"
}); });
@ -419,15 +390,12 @@ var wide = {
case 'start-test': case 'start-test':
case 'start-vet': case 'start-vet':
case 'start-install': case 'start-install':
case 'start-get':
case 'start-git_clone':
bottomGroup.fillOutput(data.output); bottomGroup.fillOutput(data.output);
break; break;
case 'go test': case 'go test':
case 'go vet': case 'go vet':
case 'go install': case 'go install':
case 'go get':
bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output); bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output);
break; break;
@ -471,11 +439,11 @@ var wide = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file/zip/new', url: '/file/zip/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (!result.succ) { if (0 != result.code) {
$("#dialogAlert").dialog("open", result.msg); $("#dialogAlert").dialog("open", result.msg);
return false; return false;
@ -486,7 +454,7 @@ var wide = {
}); });
if (path) { if (path) {
window.open(config.context + '/file/zip?path=' + path + ".zip"); window.open('/file/zip?path=' + path + ".zip");
} }
} }
} }
@ -495,10 +463,10 @@ var wide = {
} }
}; };
outputWS.onclose = function (e) { outputWS.onclose = function (e) {
console.log('[output onclose] disconnected (' + e.code + ')'); // console.log('[output onclose] disconnected (' + e.code + ')');
}; };
outputWS.onerror = function (e) { outputWS.onerror = function (e) {
console.log('[output onerror]'); console.log('[output onerror]',e);
}; };
}, },
_initFooter: function () { _initFooter: function () {
@ -551,7 +519,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/save', url: '/file/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -587,7 +555,7 @@ var wide = {
request.nextCmd = ""; // build only, no following operation request.nextCmd = ""; // build only, no following operation
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -620,7 +588,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/stop', url: '/stop',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -642,11 +610,11 @@ var wide = {
$.ajax({ $.ajax({
async: false, // sync async: false, // sync
type: 'POST', type: 'POST',
url: config.context + '/go/fmt', url: '/go/fmt',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (result.succ) { if (0 == result.code) {
editor.setValue(result.data.code); editor.setValue(result.data.code);
editor.setCursor(cursor); editor.setCursor(cursor);
editor.scrollTo(null, scrollInfo.top); editor.scrollTo(null, scrollInfo.top);
@ -675,11 +643,11 @@ var wide = {
$.ajax({ $.ajax({
async: false, // sync async: false, // sync
type: 'POST', type: 'POST',
url: config.context + '/go/fmt', url: '/go/fmt',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (result.succ) { if (0 == result.code) {
formatted = result.data.code; formatted = result.data.code;
} }
} }

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