Compare commits
548 Commits
Author | SHA1 | Date |
---|---|---|
|
c3c3031f37 | |
|
a7b9baaaaf | |
|
593d9241be | |
|
35ac5a50d6 | |
|
f6a72ff464 | |
|
24d4654030 | |
|
58611c8f1e | |
|
9076912b9d | |
|
68fee17793 | |
|
877e96df4d | |
|
029982f00e | |
|
5cfb9ca5fb | |
|
93654cfa32 | |
|
de0110eadf | |
|
2e53a02446 | |
|
3a4aabc297 | |
|
9b8c3eb888 | |
|
7f9f726f71 | |
|
a0ec5a70b7 | |
|
51a3f6b932 | |
|
791f438ff9 | |
|
e3c3b165c8 | |
|
3256f60990 | |
|
e51b78ea85 | |
|
3bf430e35f | |
|
12c5da94f6 | |
|
cd9d0165c0 | |
|
231a35c725 | |
|
dbd41730ae | |
|
c84ead6140 | |
|
786a0f5c4a | |
|
624ef3a152 | |
|
1287018811 | |
|
3193610b7f | |
|
c065239e69 | |
|
7d222f22cb | |
|
ab4feaba81 | |
|
aa65a8db04 | |
|
e4f0e410b8 | |
|
0568356fdc | |
|
215820f02b | |
|
c3b7c26119 | |
|
89e3b7be8f | |
|
358067ea10 | |
|
bd54c4ef10 | |
|
47374dd8c8 | |
|
e84f994f2b | |
|
90303f2aed | |
|
f633ca07eb | |
|
a6f9de5a62 | |
|
d26f35bc5f | |
|
5f0612530a | |
|
e281d0f554 | |
|
dd949ae6a3 | |
|
6d8a334131 | |
|
3cf3063e76 | |
|
fbb824de6e | |
|
ef52c48b26 | |
|
1c295785d0 | |
|
b57889bdac | |
|
9d0113f979 | |
|
7a254f89d8 | |
|
544cb886ec | |
|
cdc1f5408b | |
|
f6bdc5f176 | |
|
d3ab996830 | |
|
bae3ad8c20 | |
|
7dafee49e9 | |
|
efce7adc51 | |
|
d474e913f6 | |
|
c5fdd3f0a6 | |
|
6dc68d4aa5 | |
|
c0c00bf9c8 | |
|
4f9221c313 | |
|
f3e9502443 | |
|
00439ae418 | |
|
c931e611db | |
|
b53d2d28b7 | |
|
83d52b107a | |
|
4930408b17 | |
|
1405d7482a | |
|
0253ad8e45 | |
|
4da07183a9 | |
|
1eb2a78c3b | |
|
6bd4efd16f | |
|
17e59cf55a | |
|
879b3d78c3 | |
|
ce8351f38c | |
|
58ae60da85 | |
|
5c549a56c5 | |
|
38f88c2c17 | |
|
1857df995d | |
|
67c36e0089 | |
|
d764f13192 | |
|
9716c3027f | |
|
e2c0b8ec8a | |
|
2e0f150cdb | |
|
4a28d10a9d | |
|
eb896e3db3 | |
|
d3f522acd9 | |
|
00051a9e39 | |
|
b1194b4763 | |
|
ffd99ff8bd | |
|
c7372868e4 | |
|
c86f0daa94 | |
|
03cbb8fce4 | |
|
763e140775 | |
|
2b50910784 | |
|
72a7281513 | |
|
7576910f43 | |
|
ff3546a844 | |
|
b84338bb87 | |
|
f8d26e0c57 | |
|
02a809803e | |
|
9a16c9214f | |
|
e952c8fa09 | |
|
a236fc195c | |
|
1787db9091 | |
|
802178cbf3 | |
|
43a3ffe2a0 | |
|
04a2c8ed2f | |
|
1d802f8717 | |
|
e230dfdb2d | |
|
fb124a3a5b | |
|
76dd5cacba | |
|
4a5f8a1314 | |
|
8341881400 | |
|
f764d61323 | |
|
f32e0a21d3 | |
|
0d5f5f80f8 | |
|
4760796915 | |
|
58de20eb86 | |
|
7d7e51e8a6 | |
|
ad6df5b1b4 | |
|
4b6f6af949 | |
|
6bdd404469 | |
|
72e46e3e52 | |
|
29d4d93019 | |
|
359ce01227 | |
|
beb58995af | |
|
7e62a1db3f | |
|
8c520527ce | |
|
bc1f8a5853 | |
|
ade8fa8892 | |
|
ec22992396 | |
|
d0396da439 | |
|
8b0b1503c3 | |
|
b8507efed3 | |
|
c8a82c1694 | |
|
283270c4e1 | |
|
7167b83c9e | |
|
4d16aac364 | |
|
e7a3fa9334 | |
|
eabce6b343 | |
|
aa17c25400 | |
|
4f48e827f6 | |
|
be63fc0d16 | |
|
5989d0fb7b | |
|
003354ffe4 | |
|
b5aa4f9a7d | |
|
912c5821f1 | |
|
2cbf74ddf7 | |
|
cc928edb4a | |
|
44aee6b548 | |
|
ec89d3ee4e | |
|
b115e34f15 | |
|
f32bc7ab5f | |
|
ad5ffb7510 | |
|
7f28661cb4 | |
|
efa278c773 | |
|
c24e4f4711 | |
|
7deb7c60e5 | |
|
e97ce75b92 | |
|
b97ba481ef | |
|
c13b585d3a | |
|
1c3c96e371 | |
|
73f873c62f | |
|
c62d40e3a9 | |
|
93a64bb9d7 | |
|
8058946f14 | |
|
0d36d56631 | |
|
9a3316b124 | |
|
51fa9146d1 | |
|
c80d6746c0 | |
|
c7e4034407 | |
|
6bc8f32e85 | |
|
41e2316cc5 | |
|
be2d5b74b3 | |
|
07e9f4654f | |
|
318f453125 | |
|
f1cc112a52 | |
|
bb3d4979b7 | |
|
d30e3b76b9 | |
|
1d84db7740 | |
|
1a4feddf34 | |
|
88f16e1057 | |
|
efcceccac4 | |
|
11dbe42c9a | |
|
a8121d05f8 | |
|
1d42fb1215 | |
|
6cdd14c783 | |
|
f86ca5b632 | |
|
ce2ae8f97f | |
|
6b4879fcbd | |
|
60b3de2159 | |
|
2edbefd883 | |
|
d4980a0e4d | |
|
10d8d65d72 | |
|
138583d96f | |
|
22670dcd7f | |
|
36f0921c55 | |
|
e23e26dbdf | |
|
3666c12738 | |
|
50b03b1ec8 | |
|
fb9a273af1 | |
|
87c528eac3 | |
|
8c7d2636b2 | |
|
e39b302bdc | |
|
982ef9c07f | |
|
0b8da63175 | |
|
0a51a03f35 | |
|
bfd836c6fc | |
|
1d1b548a68 | |
|
1fcc40af30 | |
|
b80a132866 | |
|
aea0392beb | |
|
f96c8befdf | |
|
01ccdc329a | |
|
688cb7cce4 | |
|
3f48fd3250 | |
|
7dc9e0e867 | |
|
7b040f9f4f | |
|
c023c9ae8c | |
|
a399713787 | |
|
9f067a8361 | |
|
3548f97239 | |
|
eed97d5fd8 | |
|
8229848549 | |
|
b7f209abcc | |
|
eeeacf917c | |
|
d905075fc1 | |
|
fc36272dc8 | |
|
ae678d84d0 | |
|
f859a19e7b | |
|
5121acdc58 | |
|
0b0a67cc33 | |
|
197a33f0e8 | |
|
02301d15fb | |
|
1fb29e4602 | |
|
2108a9b407 | |
|
eba96fd0b6 | |
|
8e972abee0 | |
|
26548d8717 | |
|
d919da01a7 | |
|
2895959529 | |
|
65e65e2d8a | |
|
41d641224e | |
|
4893c974bc | |
|
81f3bce1c5 | |
|
e456a17443 | |
|
0e6495cb66 | |
|
2b790ce300 | |
|
a000f6b44c | |
|
8ffe989c2e | |
|
f69d7c3f06 | |
|
687c953931 | |
|
3cb76438ff | |
|
4ab1cb3293 | |
|
ec3c3b52f4 | |
|
b61764ef37 | |
|
f4fec2978e | |
|
dfcbeaa9c8 | |
|
bc9466f435 | |
|
b5365794eb | |
|
d7550b110a | |
|
2293e37e72 | |
|
ed7170a02f | |
|
4b1cdbb249 | |
|
b33638dc06 | |
|
e1dca8c16d | |
|
9314f2b721 | |
|
290625c8e9 | |
|
2f4ead4731 | |
|
699ba5e0c5 | |
|
d1a54532c9 | |
|
ee069e75ac | |
|
0309fd01a2 | |
|
e4e2c029e1 | |
|
4a4788be85 | |
|
ea40ff990b | |
|
029bc7c917 | |
|
a89af9a374 | |
|
a93a52197d | |
|
bfbc47c3c4 | |
|
130006608f | |
|
d5493ea380 | |
|
b7eb57ac99 | |
|
7f4df9b4de | |
|
d82fa32715 | |
|
9500ea33a2 | |
|
771dffc7d4 | |
|
e074ddd235 | |
|
b5453f02b7 | |
|
8155c94ffc | |
|
bed29d8418 | |
|
0ecb57a871 | |
|
78a44a9744 | |
|
be80e292d0 | |
|
23f299f0bf | |
|
28d8f99f85 | |
|
79984ab9ac | |
|
817c556df4 | |
|
0c42e682a6 | |
|
3f888d0884 | |
|
0686ef3020 | |
|
def80b904f | |
|
910cf9c536 | |
|
0062c28edb | |
|
487d97d844 | |
|
6b4ec1b924 | |
|
bcbc6048c4 | |
|
38f1b190f1 | |
|
6d9e4aaf6a | |
|
714dd8768f | |
|
5963f23d33 | |
|
be04f26b0d | |
|
ada7da92c9 | |
|
35576c42b5 | |
|
f1e1c1e085 | |
|
952daeb76c | |
|
3a3ffb1bc4 | |
|
3b6fd9b248 | |
|
b8d6544059 | |
|
a6bc92f4f1 | |
|
7128572b0a | |
|
6318f50b59 | |
|
db1f2d29a9 | |
|
f4f909ee6c | |
|
d9fb62c2b1 | |
|
991526927a | |
|
e48568fbe7 | |
|
2e03c1449c | |
|
638dd3e67c | |
|
743dbe2fcc | |
|
aea671c393 | |
|
ed3e213c72 | |
|
676d4736f4 | |
|
2bd137cc63 | |
|
121c022ff9 | |
|
55de8c75ad | |
|
d9f7bb2869 | |
|
30f11f3867 | |
|
b4b96b3985 | |
|
77cd76d0c0 | |
|
f8a693f20c | |
|
43ad792460 | |
|
6ed1c99b53 | |
|
b36d04071f | |
|
6da4023ad2 | |
|
f1acbd3682 | |
|
9bc0d8e036 | |
|
134a6f8178 | |
|
43c3ae0c69 | |
|
a1c5ac31df | |
|
2a41cbae8b | |
|
b44fd6d959 | |
|
8f4fc25324 | |
|
10de6cd3d9 | |
|
ac0cc1ea04 | |
|
f3dadc4795 | |
|
0ee3704a5b | |
|
a732640bc1 | |
|
039e826053 | |
|
0482e148e3 | |
|
8d2400c9c4 | |
|
aceb391c9f | |
|
53530862bb | |
|
84cdbe88b3 | |
|
bf7ebfd618 | |
|
25b628c844 | |
|
1dd79cf684 | |
|
42093ebfc1 | |
|
0780cdb04f | |
|
7772fcdb16 | |
|
bff474033b | |
|
02d4bbab34 | |
|
4deb98baaf | |
|
abe5672490 | |
|
fdba27c224 | |
|
2210eac230 | |
|
9119a1da45 | |
|
3c30d09cb6 | |
|
7347c0b151 | |
|
60570b6680 | |
|
d42310fb84 | |
|
15f3c12579 | |
|
6fe83917dd | |
|
c34f9cdda6 | |
|
c49a47cd21 | |
|
fa75a59f10 | |
|
59bb310fa0 | |
|
54cfad611d | |
|
4657c48865 | |
|
426783b963 | |
|
f0b5cbca16 | |
|
9908cb5c43 | |
|
ac579ab769 | |
|
f4693a6eee | |
|
a119370a23 | |
|
b758ec854e | |
|
151757cc8a | |
|
82a9f5a331 | |
|
915f7e3cee | |
|
38c742b405 | |
|
8de99bcd08 | |
|
c743c6f007 | |
|
bb8b5534d2 | |
|
fc2c0e4b81 | |
|
5794df245c | |
|
66654b7b15 | |
|
b5f59eadf0 | |
|
fd6636df3f | |
|
08a7524ee0 | |
|
3d9c50cb8e | |
|
9edc58b6d5 | |
|
5259b54304 | |
|
78ac45ae24 | |
|
9b9a17a842 | |
|
6d31a0a20d | |
|
3f8544442d | |
|
eaa93281d1 | |
|
0a96f993fb | |
|
1d99c130da | |
|
012eab83c7 | |
|
2a8788e6e9 | |
|
af3224484a | |
|
e947f6333e | |
|
87aa489fd0 | |
|
534d7f331c | |
|
5c87368bb8 | |
|
94d7f0c220 | |
|
d80c1b7a54 | |
|
cc70eb66ff | |
|
67b511ad86 | |
|
2b0e356979 | |
|
4b41e905f6 | |
|
aa454d7513 | |
|
1ec18a5268 | |
|
bcf41780ab | |
|
1c661915b7 | |
|
d1fe1d757a | |
|
e76d4f3fd9 | |
|
2ebaa98efb | |
|
23c924e286 | |
|
95c7f65395 | |
|
ab24e1b6d9 | |
|
931e573f52 | |
|
eea2ae469d | |
|
519a3f0909 | |
|
a286aa5e95 | |
|
8bec021a47 | |
|
ef40f975b2 | |
|
38e16a4658 | |
|
95cdebcc09 | |
|
5dff1fd490 | |
|
e16696ac63 | |
|
a001bd6e35 | |
|
c4018f7903 | |
|
8ecd8eb2fc | |
|
38b64b491a | |
|
bb12838d1c | |
|
da9c6079f0 | |
|
492e74e1e8 | |
|
84214a4a1a | |
|
fed20889f0 | |
|
e8c27e762f | |
|
e3b5fbbfa8 | |
|
3ee68aa6ce | |
|
46ecea41c0 | |
|
f1b258ff7f | |
|
4e1189f338 | |
|
d05bce6e27 | |
|
22dcf590e7 | |
|
4a697eeff2 | |
|
23438f7af8 | |
|
e63e8f70ca | |
|
869ca61809 | |
|
f288d76eb9 | |
|
9bc8addfbf | |
|
f573841b59 | |
|
e54e89f1b8 | |
|
8f42e3cc47 | |
|
06d56e7151 | |
|
fec7480be9 | |
|
6f5d1fe086 | |
|
78fcc0d19a | |
|
fb22dbd273 | |
|
39c85c03a1 | |
|
003a94ba3f | |
|
f87cabc558 | |
|
e3c07ed720 | |
|
ca2f593df3 | |
|
f30f32b0ac | |
|
9758d8508b | |
|
0cd5a093d5 | |
|
59e260ea6f | |
|
4ed531a6be | |
|
bd1a2778c3 | |
|
fce39ce4e2 | |
|
af808c2024 | |
|
ec22bcd3ad | |
|
8c1a705a6a | |
|
c13108a7c0 | |
|
50ee59a2b7 | |
|
9aaea36153 | |
|
4b9c72f440 | |
|
73cfcdce55 | |
|
3abdab7d32 | |
|
785d1ee021 | |
|
175901bb93 | |
|
eaaee8443a | |
|
e10eadb1e0 | |
|
7fcc39be60 | |
|
1be7dc89e0 | |
|
39f4a358c7 | |
|
71ccdd4a72 | |
|
a872615b12 | |
|
55efb753e6 | |
|
a0ac768410 | |
|
9307dcaf77 | |
|
68f6c1d314 | |
|
02a660109a | |
|
46e187a1a8 | |
|
04888ede4c | |
|
e433d354e2 | |
|
4ba9c895de | |
|
d2638f8f7a | |
|
fe2162ed3c | |
|
8288e7da01 | |
|
188307ee0a | |
|
0af3f70e53 | |
|
a31f71c838 | |
|
168ee7c5ee | |
|
d35d476162 | |
|
63b969df55 | |
|
fcb529cfe3 | |
|
d8f3cf7cad | |
|
fa4c1ef06f |
|
@ -0,0 +1,3 @@
|
||||||
|
static/js/lib/* linguist-vendored
|
||||||
|
static/js/overwrite/* linguist-vendored
|
||||||
|
*.min.js linguist-vendored
|
|
@ -1,7 +1,12 @@
|
||||||
/wide.exe
|
/wide.exe
|
||||||
/wide
|
/wide
|
||||||
|
|
||||||
/static/user/admin/style.css
|
|
||||||
|
|
||||||
/header
|
/header
|
||||||
/header.exe
|
/header.exe
|
||||||
|
|
||||||
|
/**/.DS_Store
|
||||||
|
|
||||||
|
/node_modules
|
||||||
|
/nbproject
|
||||||
|
/workspaces
|
||||||
|
.idea
|
21
.gobuild.yml
21
.gobuild.yml
|
@ -1,21 +0,0 @@
|
||||||
author: DL88250@gmail.com
|
|
||||||
description: A Web-based IDE for Teams using Golang.
|
|
||||||
|
|
||||||
filesets:
|
|
||||||
depth: 10
|
|
||||||
includes:
|
|
||||||
- conf
|
|
||||||
- doc
|
|
||||||
- i18n
|
|
||||||
- static
|
|
||||||
- views
|
|
||||||
- README.md
|
|
||||||
- LICENSE
|
|
||||||
excludes:
|
|
||||||
- \.git
|
|
||||||
settings:
|
|
||||||
targetdir: ""
|
|
||||||
build: |
|
|
||||||
test -d Godeps && go(){ godep go "$@";} ; go install -v
|
|
||||||
outfiles:
|
|
||||||
- wide
|
|
|
@ -10,11 +10,12 @@
|
||||||
],
|
],
|
||||||
"Excludes": [
|
"Excludes": [
|
||||||
"static/js/lib/*",
|
"static/js/lib/*",
|
||||||
"static/user/*"
|
"static/user/*",
|
||||||
|
"vendor/**"
|
||||||
],
|
],
|
||||||
"UseDefaultExcludes": true,
|
"UseDefaultExcludes": true,
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"Year": "2014-2015",
|
"Year": "2014-present",
|
||||||
"Owner": "b3log.org"
|
"Owner": "b3log.org"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,16 +0,0 @@
|
||||||
FROM golang:latest
|
|
||||||
MAINTAINER Liang Ding <dl88250@gmail.com>
|
|
||||||
|
|
||||||
ADD . /wide/gogogo/src/github.com/b3log/wide
|
|
||||||
|
|
||||||
RUN useradd wide && useradd runner
|
|
||||||
|
|
||||||
ENV GOROOT /usr/src/go
|
|
||||||
ENV GOPATH /wide/gogogo
|
|
||||||
|
|
||||||
RUN go get -v golang.org/x/tools/cmd/vet github.com/88250/ide_stub github.com/nsf/gocode github.com/bradfitz/goimports
|
|
||||||
|
|
||||||
WORKDIR /wide/gogogo/src/github.com/b3log/wide
|
|
||||||
RUN go get -v && go build -v
|
|
||||||
|
|
||||||
EXPOSE 7070
|
|
2
LICENSE
2
LICENSE
|
@ -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,
|
||||||
|
|
158
README.md
158
README.md
|
@ -1,156 +1,18 @@
|
||||||
# Wide [](https://travis-ci.org/b3log/wide) [](https://coveralls.io/r/b3log/wide) [](http://www.apache.org/licenses/LICENSE-2.0) [](http://godoc.org/github.com/b3log/wide) [](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)并在本地环境运行,然后邀请你的小伙伴们加入吧!
|
|
||||||
|
|
||||||
## Intro
|
|
||||||
|
|
||||||
A <b>W</b>eb-based <b>IDE</b> for Teams using Golang.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
* **Team** IDE:
|
|
||||||
* _Safe and reliable_: the project source code stored on the server in real time, the developer's machine crashes without losing any source code
|
|
||||||
* _Unified environment_: server unified development environment configuration, the developer machine without any additional configuration
|
|
||||||
* _Out of the box_: 5 minutes to setup a server then open browser to develop, debug
|
|
||||||
* _Version Control_: each developer has its own source code repository, easy sync with the trunk
|
|
||||||
* **Web-based** IDE:
|
|
||||||
* Developer needs a browser only
|
|
||||||
* Cross-platform, even on mobile devices
|
|
||||||
* Easy to extend
|
|
||||||
* Easy to integrate with other systems
|
|
||||||
* For the geeks
|
|
||||||
* A try for commercial-open source: versions customized for enterprises, close to their development work flows respectively
|
|
||||||
* Currently more popular Go IDE has some defects or regrets:
|
|
||||||
* Text editor (vim/emacs/sublime/Atom, etc.): For the Go newbie is too complex
|
|
||||||
* Plug-in (goclipse, etc.): the need for the original IDE support, not professional
|
|
||||||
* LiteIDE: no modern user interface :p
|
|
||||||
* No team development experience
|
|
||||||
* There are a few of GO IDEs, and no one developed by Go itself, this is a nice try
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* [X] Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc.
|
|
||||||
* [X] Autocomplete: Go/HTML etc.
|
|
||||||
* [X] Format: Go/HTML/JSON etc.
|
|
||||||
* [X] Build & Run
|
|
||||||
* [X] Multiplayer: a real team development experience
|
|
||||||
* [X] Navigation, Jump to declaration, Find usages, File search etc.
|
|
||||||
* [X] Shell: run command on the server
|
|
||||||
* [X] Web development: Frontend devlopment (HTML/JS/CSS) all in one
|
|
||||||
* [X] Go tool: go get/install/fmt etc.
|
|
||||||
* [X] File Import & Export
|
|
||||||
* [X] Themes
|
|
||||||
* [ ] Debug
|
|
||||||
* [ ] Git integration: git command on the web
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
* **Overview**
|
|
||||||

|

|
||||||
* **Goto File**
|
|
||||||

|

|
||||||
* **Autocomplete**
|
|
||||||

|

|
||||||
* **Theme**
|
|
||||||

|

|
||||||
* **Show Expression Info**
|
|
||||||

|

|
||||||
* **Build Error Info**
|
|
||||||

|

|
||||||
* **About**
|
|
||||||

|
|
||||||
|
|
||||||
## Architecture
|

|
||||||
|
|
||||||
### Build & Run
|

|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* Autocompletion
|
|
||||||
* Find Usages/Jump To Declaration/etc.
|
|
||||||
|
|
||||||
Flow:
|
|
||||||
1. Browser sends code assist request
|
|
||||||
2. Handler gets user workspace of the request with HTTP session
|
|
||||||
3. Server executes ````gocode````/````ide_stub````<br/>
|
|
||||||
3.1 Sets environment variables (e.g. ${GOPATH})<br/>
|
|
||||||
3.2 ````gocode```` with ````lib-path```` parameter
|
|
||||||
|
|
||||||
## Documents
|
|
||||||
|
|
||||||
* [用户指南](http://88250.gitbooks.io/wide-user-guide)
|
|
||||||
* [开发指南](http://88250.gitbooks.io/wide-dev-guide)
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
### Download Binary
|
|
||||||
|
|
||||||
We have provided OS-specific executable binary as follows:
|
|
||||||
|
|
||||||
* linux-amd64/386
|
|
||||||
* windows-amd64/386
|
|
||||||
* darwin-amd64/386
|
|
||||||
|
|
||||||
Download [HERE](http://pan.baidu.com/s/1dD3XwOT)!
|
|
||||||
|
|
||||||
### Build Wide for yourself
|
|
||||||
|
|
||||||
1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone`
|
|
||||||
2. Get dependencies with
|
|
||||||
* `go get`
|
|
||||||
* `go get github.com/88250/ide_stub`
|
|
||||||
* `go get github.com/nsf/gocode`
|
|
||||||
3. Compile wide with `go build`
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
1. Get image: `sudo docker pull 88250/wide:latest`
|
|
||||||
2. Run: `sudo docker run -p 127.0.0.1:7070:7070 88250/wide:latest ./wide -docker=true -channel=ws://127.0.0.1:7070`
|
|
||||||
3. Open browser: http://127.0.0.1:7070
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
|
|
||||||
|
|
||||||
## Terms
|
|
||||||
|
|
||||||
* This software is open sourced under the Apache License 2.0
|
|
||||||
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any pages, even the pages are developed by you
|
|
||||||
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for request a commercial license
|
|
||||||
* Copyright (c) b3log.org, all rights reserved
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
* [golang](http://golang.org)
|
|
||||||
* [CodeMirror](https://github.com/marijnh/CodeMirror)
|
|
||||||
* [zTree](https://github.com/zTree/zTree_v3)
|
|
||||||
* [LiteIDE](https://github.com/visualfc/liteide)
|
|
||||||
* [gocode](https://github.com/nsf/gocode)
|
|
||||||
* [Gorilla](https://github.com/gorilla)
|
|
||||||
* [GoBuild](http://gobuild.io)
|
|
||||||
* [Docker](https://docker.com)
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />
|
|
||||||
|
|
4
TERMS.md
4
TERMS.md
|
@ -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
|
||||||
|
|
128
conf/user.go
128
conf/user.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -15,49 +15,51 @@
|
||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Layout represents the layot of a window.
|
// Panel represents a UI panel.
|
||||||
|
type Panel struct {
|
||||||
|
State string `json:"state"` // panel state, "min"/"max"/"normal"
|
||||||
|
Size uint16 `json:"size"` // panel size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout represents the UI layout.
|
||||||
type Layout struct {
|
type Layout struct {
|
||||||
State string // min/max/normal
|
Side *Panel `json:"side"` // Side panel
|
||||||
|
SideRight *Panel `json:"sideRight"` // Right-Side panel
|
||||||
|
Bottom *Panel `json:"bottom"` // Bottom panel
|
||||||
}
|
}
|
||||||
|
|
||||||
// LatestSessionContent represents the latest session content.
|
// LatestSessionContent represents the latest session content.
|
||||||
type LatestSessionContent struct {
|
type LatestSessionContent struct {
|
||||||
FileTree []string // paths of expanding nodes of file tree
|
FileTree []string `json:"fileTree"` // paths of expanding nodes of file tree
|
||||||
Files []string // paths of files of opening editor tabs
|
Files []string `json:"files"` // paths of files of opening editor tabs
|
||||||
CurrentFile string // path of file of the current focused editor tab
|
CurrentFile string `json:"currentFile"` // path of file of the current focused editor tab
|
||||||
|
Layout *Layout `json:"layout"` // UI Layout
|
||||||
FileTreeLayout *Layout
|
|
||||||
EditorLayout *Layout
|
|
||||||
OutlineLayout *Layout
|
|
||||||
BottomLayout *Layout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User configuration.
|
// User configuration.
|
||||||
type User struct {
|
type User struct {
|
||||||
|
Id string
|
||||||
Name string
|
Name string
|
||||||
Password string
|
Avatar string
|
||||||
Salt string
|
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
|
||||||
Email string
|
|
||||||
Gravatar string // see http://gravatar.com
|
|
||||||
Workspace string // the GOPATH of this user
|
|
||||||
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
|
||||||
|
Keymap string // wide/vim
|
||||||
Created int64 // user create time in unix nano
|
Created int64 // user create time in unix nano
|
||||||
Updated int64 // preference update time in unix nano
|
Updated int64 // preference update time in unix nano
|
||||||
Lived int64 // the latest session activity in unix nano
|
Lived int64 // the latest session activity in unix nano
|
||||||
|
@ -74,25 +76,7 @@ type editor struct {
|
||||||
TabSize string
|
TabSize string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUser creates a user with the specified username, password, email and workspace.
|
// Save saves the user's configurations in conf/users/{userId}.json.
|
||||||
func NewUser(username, password, email, workspace string) *User {
|
|
||||||
md5hash := md5.New()
|
|
||||||
md5hash.Write([]byte(email))
|
|
||||||
gravatar := hex.EncodeToString(md5hash.Sum(nil))
|
|
||||||
|
|
||||||
salt := util.Rand.String(16)
|
|
||||||
password = Salt(password, salt)
|
|
||||||
|
|
||||||
now := time.Now().UnixNano()
|
|
||||||
|
|
||||||
return &User{Name: username, Password: password, Salt: salt, Email: email, Gravatar: gravatar, Workspace: workspace,
|
|
||||||
Locale: Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
|
|
||||||
Created: now, Updated: now, Lived: now,
|
|
||||||
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
|
|
||||||
Theme: "wide", TabSize: "4"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves the user's configurations in conf/users/{username}.json.
|
|
||||||
func (u *User) Save() bool {
|
func (u *User) Save() bool {
|
||||||
bytes, err := json.MarshalIndent(u, "", " ")
|
bytes, err := json.MarshalIndent(u, "", " ")
|
||||||
|
|
||||||
|
@ -102,7 +86,13 @@ func (u *User) Save() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err {
|
if "" == string(bytes) {
|
||||||
|
logger.Error("Truncated user [" + u.Id + "]")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(filepath.Join(Wide.Data, "users", u.Id+".json"), bytes, 0644); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -111,34 +101,62 @@ func (u *User) Save() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWorkspace gets workspace path of the user.
|
// NewUser creates a user with the specified username and workspace.
|
||||||
|
func NewUser(id, name, avatar, workspace string) *User {
|
||||||
|
now := time.Now().UnixNano()
|
||||||
|
|
||||||
|
return &User{Id: id, Name: name, Avatar: avatar, Workspace: workspace,
|
||||||
|
Locale: Wide.Locale, GoFormat: "gofmt",
|
||||||
|
GoBuildArgsForLinux: "", GoBuildArgsForWindows: "", GoBuildArgsForDarwin: "",
|
||||||
|
FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
|
||||||
|
Keymap: "wide",
|
||||||
|
Created: now, Updated: now, Lived: now,
|
||||||
|
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
|
||||||
|
Theme: "wide", TabSize: "4"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkspacePath gets workspace path of the user.
|
||||||
//
|
//
|
||||||
// Compared to the use of Wide.Workspace, this function will be processed as follows:
|
// Compared to the use of Wide.Workspace, this function will be processed as follows:
|
||||||
// 1. Replace {WD} variable with the actual directory path
|
// 1. Replace {WD} variable with the actual directory path
|
||||||
// 2. Replace ${GOPATH} with enviorment variable GOPATH
|
// 2. Replace ${GOPATH} with enviorment variable GOPATH
|
||||||
// 3. Replace "/" with "\\" (Windows)
|
// 3. Replace "/" with "\\" (Windows)
|
||||||
func (u *User) GetWorkspace() string {
|
func (u *User) WorkspacePath() string {
|
||||||
w := strings.Replace(u.Workspace, "{WD}", Wide.WD, 1)
|
w := u.Workspace
|
||||||
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
|
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
|
||||||
|
|
||||||
return filepath.FromSlash(w)
|
return filepath.FromSlash(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildArgs get build args with the specified os.
|
||||||
|
func (u *User) BuildArgs(os string) []string {
|
||||||
|
var tmp string
|
||||||
|
if os == "windows" {
|
||||||
|
tmp = u.GoBuildArgsForWindows
|
||||||
|
}
|
||||||
|
if os == "linux" {
|
||||||
|
tmp = u.GoBuildArgsForLinux
|
||||||
|
}
|
||||||
|
if os == "darwin" {
|
||||||
|
tmp = u.GoBuildArgsForDarwin
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)'`)
|
||||||
|
ret := exp.FindAllString(tmp, -1)
|
||||||
|
for idx := range ret {
|
||||||
|
ret[idx] = strings.Replace(ret[idx], "\"", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// GetOwner gets the user the specified path belongs to. Returns "" if not found.
|
// GetOwner gets the user the specified path belongs to. Returns "" if not found.
|
||||||
func GetOwner(path string) string {
|
func GetOwner(path string) string {
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
if strings.HasPrefix(path, user.GetWorkspace()) {
|
if strings.HasPrefix(path, user.WorkspacePath()) {
|
||||||
return user.Name
|
return user.Id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salt salts the specified password with the specified salt.
|
|
||||||
func Salt(password, salt string) string {
|
|
||||||
sha1hash := sha1.New()
|
|
||||||
sha1hash.Write([]byte(password + salt))
|
|
||||||
|
|
||||||
return hex.EncodeToString(sha1hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"Name": "admin",
|
|
||||||
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
|
|
||||||
"Salt": "dnoyeb",
|
|
||||||
"Email": "",
|
|
||||||
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
|
|
||||||
"Workspace": "${GOPATH}",
|
|
||||||
"Locale": "en_US",
|
|
||||||
"GoFormat": "gofmt",
|
|
||||||
"FontFamily": "Helvetica",
|
|
||||||
"FontSize": "13px",
|
|
||||||
"Theme": "default",
|
|
||||||
"Created": 1414080000000000000,
|
|
||||||
"Updated": 1414080000000000000,
|
|
||||||
"Lived": 1414080000000000000,
|
|
||||||
"Editor": {
|
|
||||||
"FontFamily": "Consolas, 'Courier New', monospace",
|
|
||||||
"FontSize": "13px",
|
|
||||||
"LineHeight": "17px",
|
|
||||||
"Theme": "wide",
|
|
||||||
"TabSize": "4"
|
|
||||||
},
|
|
||||||
"LatestSessionContent": {
|
|
||||||
"FileTree": [],
|
|
||||||
"Files": [],
|
|
||||||
"CurrentFile": "",
|
|
||||||
"FileTreeLayout": null,
|
|
||||||
"EditorLayout": null,
|
|
||||||
"OutlineLayout": null,
|
|
||||||
"BottomLayout": null
|
|
||||||
}
|
|
||||||
}
|
|
227
conf/wide.go
227
conf/wide.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -17,19 +17,17 @@ package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/event"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/event"
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,41 +36,44 @@ const (
|
||||||
// PathListSeparator holds the OS-specific path list separator.
|
// PathListSeparator holds the OS-specific path list separator.
|
||||||
PathListSeparator = string(os.PathListSeparator)
|
PathListSeparator = string(os.PathListSeparator)
|
||||||
|
|
||||||
// WideVersion holds the current wide version.
|
// WideVersion holds the current Wide's version.
|
||||||
WideVersion = "1.2.0"
|
WideVersion = "1.6.0"
|
||||||
// CodeMirrorVer holds the current editor version.
|
// CodeMirrorVer holds the current editor version.
|
||||||
CodeMirrorVer = "4.10"
|
CodeMirrorVer = "5.1"
|
||||||
|
// UserAgent represents HTTP client user agent.
|
||||||
|
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
|
||||||
|
|
||||||
HelloWorld = `package main
|
HelloWorld = `package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Hello, 世界")
|
fmt.Println("欢迎通过《边看边练 Go 系列》来学习 Go 语言 https://ld246.com/article/1437497122181")
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration.
|
// Configuration.
|
||||||
type conf struct {
|
type conf struct {
|
||||||
IP string // server ip, ${ip}
|
Server string // server
|
||||||
Port string // server port
|
|
||||||
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
|
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
|
||||||
|
@ -80,21 +81,28 @@ var Wide *conf
|
||||||
// configurations of users.
|
// configurations of users.
|
||||||
var Users []*User
|
var Users []*User
|
||||||
|
|
||||||
// Indicates whether runs via Docker.
|
// Indicates whether Docker is available.
|
||||||
var Docker bool
|
var Docker bool
|
||||||
|
|
||||||
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json.
|
// Docker image to run user's program
|
||||||
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
const DockerImageGo = "golang"
|
||||||
confPlayground string, confDocker bool) {
|
|
||||||
// XXX: ugly args list....
|
|
||||||
|
|
||||||
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
// Load loads the Wide configurations from wide.json and users' configurations from users/{userId}.json.
|
||||||
confPlayground, confDocker)
|
func Load(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||||
|
initWide(confPath, confData, confServer, confLogLevel, confReadOnly, confSiteStatCode)
|
||||||
initUsers()
|
initUsers()
|
||||||
|
|
||||||
|
cmd := exec.Command("docker", "version")
|
||||||
|
_, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
logger.Warnf("Not found 'docker' installed, running user's code will cause security problem")
|
||||||
|
} else {
|
||||||
|
Docker = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initUsers() {
|
func initUsers() {
|
||||||
f, err := os.Open("conf/users")
|
f, err := os.Open(Wide.Data + PathSeparator + "users")
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
@ -114,17 +122,36 @@ func initUsers() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ".json" != filepath.Ext(name) { // such as backup (*.json~) not be created by Wide
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
user := &User{}
|
user := &User{}
|
||||||
|
|
||||||
bytes, _ := ioutil.ReadFile("conf/users/" + name)
|
bytes, _ := os.ReadFile(filepath.Join(Wide.Data, "users", name))
|
||||||
|
|
||||||
err := json.Unmarshal(bytes, user)
|
err := json.Unmarshal(bytes, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Parses [%s] error: %v", name, err)
|
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
|
||||||
|
|
||||||
os.Exit(-1)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compatibility upgrade (1.3.0): https://github.com/b3log/wide/issues/83
|
||||||
|
if "" == user.Keymap {
|
||||||
|
user.Keymap = "wide"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compatibility upgrade (1.5.3): https://github.com/b3log/wide/issues/308
|
||||||
|
//if "" == user.GoBuildArgsForLinux {
|
||||||
|
// user.GoBuildArgsForLinux = "-i"
|
||||||
|
//}
|
||||||
|
//if "" == user.GoBuildArgsForWindows {
|
||||||
|
// user.GoBuildArgsForWindows = "-i"
|
||||||
|
//}
|
||||||
|
//if "" == user.GoBuildArgsForDarwin {
|
||||||
|
// user.GoBuildArgsForDarwin = "-i"
|
||||||
|
//}
|
||||||
|
|
||||||
Users = append(Users, user)
|
Users = append(Users, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,9 +159,8 @@ func initUsers() {
|
||||||
initCustomizedConfs()
|
initCustomizedConfs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||||
confPlayground string, confDocker bool) {
|
bytes, err := os.ReadFile(confPath)
|
||||||
bytes, err := ioutil.ReadFile(confPath)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
@ -150,92 +176,65 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wide.Autocomplete = true // default to true
|
||||||
|
|
||||||
// Logging Level
|
// Logging Level
|
||||||
log.SetLevel(Wide.LogLevel)
|
gulu.Log.SetLevel(Wide.LogLevel)
|
||||||
if "" != confLogLevel {
|
if "" != confLogLevel {
|
||||||
Wide.LogLevel = confLogLevel
|
Wide.LogLevel = confLogLevel
|
||||||
log.SetLevel(confLogLevel)
|
gulu.Log.SetLevel(confLogLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Conf: \n" + string(bytes))
|
logger.Debug("Conf: \n" + string(bytes))
|
||||||
|
|
||||||
// Working Driectory
|
|
||||||
Wide.WD = util.OS.Pwd()
|
|
||||||
logger.Debugf("${pwd} [%s]", Wide.WD)
|
|
||||||
|
|
||||||
// User Home
|
// User Home
|
||||||
home, err := util.OS.Home()
|
home, err := gulu.OS.Home()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error("Can't get user's home, please report this issue to developer", err)
|
logger.Error("Can't get user's home, please report this issue to developer", err)
|
||||||
|
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("${user.home} [%s]", home)
|
logger.Debugf("${user.home} [%s]", home)
|
||||||
|
|
||||||
// Playground Directory
|
// Data directory
|
||||||
Wide.Playground = strings.Replace(Wide.Playground, "${home}", home, 1)
|
if "" != confData {
|
||||||
if "" != confPlayground {
|
Wide.Data = confData
|
||||||
Wide.Playground = confPlayground
|
|
||||||
}
|
}
|
||||||
|
Wide.Data = strings.Replace(Wide.Data, "${home}", home, -1)
|
||||||
if !util.File.IsExist(Wide.Playground) {
|
Wide.Data = filepath.Clean(Wide.Data)
|
||||||
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
|
if err := os.MkdirAll(Wide.Data+"/playground/", 0755); nil != err {
|
||||||
logger.Errorf("Create Playground [%s] error", err)
|
logger.Errorf("Create data directory [%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
|
|
||||||
ip, err := util.Net.LocalIP()
|
|
||||||
if err != nil {
|
|
||||||
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)
|
||||||
|
|
||||||
Docker = confDocker
|
|
||||||
|
|
||||||
if "" != confIP {
|
|
||||||
ip = confIP
|
|
||||||
}
|
|
||||||
|
|
||||||
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
|
|
||||||
|
|
||||||
if "" != confPort {
|
|
||||||
Wide.Port = confPort
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server
|
// Server
|
||||||
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
|
|
||||||
Wide.Server = strings.Replace(Wide.Server, "{Port}", Wide.Port, 1)
|
|
||||||
if "" != confServer {
|
if "" != confServer {
|
||||||
Wide.Server = confServer
|
Wide.Server = confServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static Server
|
if "" != confReadOnly {
|
||||||
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1)
|
Wide.ReadOnly, _ = strconv.ParseBool(confReadOnly)
|
||||||
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{Port}", Wide.Port, 1)
|
|
||||||
if "" != confStaticServer {
|
|
||||||
Wide.StaticServer = confStaticServer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context
|
// SiteStatCode
|
||||||
if "" != confContext {
|
if "" != confSiteStatCode {
|
||||||
Wide.Context = confContext
|
Wide.SiteStatCode = confSiteStatCode
|
||||||
}
|
}
|
||||||
|
|
||||||
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", strconv.FormatInt(time.Now().UnixNano(), 10), 1)
|
time := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
logger.Debugf("${time} [%s]", time)
|
||||||
// Channel
|
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
|
||||||
Wide.Channel = strings.Replace(Wide.Channel, "{IP}", Wide.IP, 1)
|
|
||||||
Wide.Channel = strings.Replace(Wide.Channel, "{Port}", Wide.Port, 1)
|
|
||||||
if "" != confChannel {
|
|
||||||
Wide.Channel = confChannel
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
|
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
|
||||||
|
@ -253,6 +252,8 @@ func FixedTimeCheckEnv() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkEnv() {
|
func checkEnv() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
cmd := exec.Command("go", "version")
|
cmd := exec.Command("go", "version")
|
||||||
buf, err := cmd.CombinedOutput()
|
buf, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
@ -268,30 +269,30 @@ func checkEnv() {
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
gocode := util.Go.GetExecutableInGOBIN("gocode")
|
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
|
||||||
cmd = exec.Command(gocode)
|
cmd = exec.Command(gocode)
|
||||||
_, err = cmd.Output()
|
_, err = cmd.Output()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
|
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
|
||||||
|
|
||||||
logger.Warnf("Not found gocode [%s]", gocode)
|
logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/stamblerre/gocode", gocode)
|
||||||
}
|
}
|
||||||
|
|
||||||
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
|
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||||
cmd = exec.Command(ideStub, "version")
|
cmd = exec.Command(ideStub, "version")
|
||||||
_, err = cmd.Output()
|
_, err = cmd.Output()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound}
|
event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound}
|
||||||
|
|
||||||
logger.Warnf("Not found ide_stub [%s]", ideStub)
|
logger.Warnf("Not found gotools [%s], please install it with this command: go get github.com/visualfc/gotools", ideStub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserWorkspace gets workspace path with the specified username, returns "" if not found.
|
// GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
|
||||||
func GetUserWorkspace(username string) string {
|
func GetUserWorkspace(userId string) string {
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
if user.Name == username {
|
if user.Id == userId {
|
||||||
return user.GetWorkspace()
|
return user.WorkspacePath()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,14 +300,14 @@ func GetUserWorkspace(username string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
|
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
|
||||||
func GetGoFmt(username string) string {
|
func GetGoFmt(userId string) string {
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
if user.Name == username {
|
if user.Id == userId {
|
||||||
switch user.GoFormat {
|
switch user.GoFormat {
|
||||||
case "gofmt":
|
case "gofmt":
|
||||||
return "gofmt"
|
return "gofmt"
|
||||||
case "goimports":
|
case "goimports":
|
||||||
return util.Go.GetExecutableInGOBIN("goimports")
|
return gulu.Go.GetExecutableInGOBIN("goimports")
|
||||||
default:
|
default:
|
||||||
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
|
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
|
||||||
return "gofmt"
|
return "gofmt"
|
||||||
|
@ -317,15 +318,14 @@ func GetGoFmt(username string) string {
|
||||||
return "gofmt"
|
return "gofmt"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser gets configuration of the user specified by the given username, returns nil if not found.
|
// GetUser gets configuration of the user specified by the given user id, returns nil if not found.
|
||||||
func GetUser(username string) *User {
|
func GetUser(id string) *User {
|
||||||
if "playground" == username { // reserved user for Playground
|
if "playground" == id { // reserved user for Playground
|
||||||
// mock it
|
return NewUser("playground", "playground", "", "")
|
||||||
return NewUser("playground", "", "", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
if user.Name == username {
|
if user.Id == id {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,17 +336,17 @@ func GetUser(username string) *User {
|
||||||
// initCustomizedConfs initializes the user customized configurations.
|
// initCustomizedConfs initializes the user customized configurations.
|
||||||
func initCustomizedConfs() {
|
func initCustomizedConfs() {
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
UpdateCustomizedConf(user.Name)
|
UpdateCustomizedConf(user.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
|
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
|
||||||
//
|
//
|
||||||
// 1. /static/user/{username}/style.css
|
// 1. /static/users/{userId}/style.css
|
||||||
func UpdateCustomizedConf(username string) {
|
func UpdateCustomizedConf(userId string) {
|
||||||
var u *User
|
var u *User
|
||||||
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
|
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
|
||||||
if user.Name == username {
|
if user.Id == userId {
|
||||||
u = user
|
u = user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -364,8 +364,7 @@ func UpdateCustomizedConf(username string) {
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
wd := util.OS.Pwd()
|
dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
|
||||||
dir := filepath.Clean(wd + "/static/user/" + u.Name)
|
|
||||||
if err := os.MkdirAll(dir, 0755); nil != err {
|
if err := os.MkdirAll(dir, 0755); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
@ -395,7 +394,7 @@ func initWorkspaceDirs() {
|
||||||
paths := []string{}
|
paths := []string{}
|
||||||
|
|
||||||
for _, user := range Users {
|
for _, user := range Users {
|
||||||
paths = append(paths, filepath.SplitList(user.GetWorkspace())...)
|
paths = append(paths, filepath.SplitList(user.WorkspacePath())...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
|
@ -418,7 +417,7 @@ func CreateWorkspaceDir(path string) {
|
||||||
|
|
||||||
// createDir creates a directory on the path if it not exists.
|
// createDir creates a directory on the path if it not exists.
|
||||||
func createDir(path string) {
|
func createDir(path string) {
|
||||||
if !util.File.IsExist(path) {
|
if !gulu.File.IsExist(path) {
|
||||||
if err := os.MkdirAll(path, 0775); nil != err {
|
if err := os.MkdirAll(path, 0775); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"IP": "${ip}",
|
"Server": "http://127.0.0.1:7070",
|
||||||
"Port": "7070",
|
"LogLevel": "debug",
|
||||||
"Context": "",
|
"Data": "${home}/wide",
|
||||||
"Server": "{IP}:{Port}",
|
"RuntimeMode": "prod",
|
||||||
"StaticServer": "",
|
|
||||||
"LogLevel": "info",
|
|
||||||
"Channel": "ws://{IP}:{Port}",
|
|
||||||
"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": "",
|
||||||
|
"OAuthUserInfoURL": "",
|
||||||
|
"OAuthClientID": "",
|
||||||
|
"OAuthClientSecret": ""
|
||||||
}
|
}
|
24
coverage.sh
24
coverage.sh
|
@ -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
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
* [User Guide](https://www.gitbook.io/book/88250/wide-user-guide)
|
|
||||||
* [Developer Guide](https://www.gitbook.io/book/88250/wide-dev-guide)
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -25,129 +25,63 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/file"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/file"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// WSHandler handles request of creating editor channel.
|
|
||||||
// XXX: NOT used at present
|
|
||||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if httpSession.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := httpSession.Values["id"].(string)
|
|
||||||
|
|
||||||
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
|
||||||
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
|
||||||
|
|
||||||
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
|
|
||||||
err := editorChan.WriteJSON(&ret)
|
|
||||||
if nil != err {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session.EditorWS[sid] = &editorChan
|
|
||||||
|
|
||||||
logger.Tracef("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS))
|
|
||||||
|
|
||||||
args := map[string]interface{}{}
|
|
||||||
for {
|
|
||||||
if err := session.EditorWS[sid].ReadJSON(&args); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
code := args["code"].(string)
|
|
||||||
line := int(args["cursorLine"].(float64))
|
|
||||||
ch := int(args["cursorCh"].(float64))
|
|
||||||
|
|
||||||
offset := getCursorOffset(code, line, ch)
|
|
||||||
|
|
||||||
logger.Tracef("offset: %d", offset)
|
|
||||||
|
|
||||||
gocode := util.Go.GetExecutableInGOBIN("gocode")
|
|
||||||
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
|
|
||||||
|
|
||||||
var output bytes.Buffer
|
|
||||||
|
|
||||||
cmd := exec.Command(gocode, argv...)
|
|
||||||
cmd.Stdout = &output
|
|
||||||
|
|
||||||
stdin, _ := cmd.StdinPipe()
|
|
||||||
cmd.Start()
|
|
||||||
stdin.Write([]byte(code))
|
|
||||||
stdin.Close()
|
|
||||||
cmd.Wait()
|
|
||||||
|
|
||||||
ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"}
|
|
||||||
|
|
||||||
if err := session.EditorWS[sid].WriteJSON(&ret); err != nil {
|
|
||||||
logger.Error("Editor WS ERROR: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutocompleteHandler handles request of code autocompletion.
|
// AutocompleteHandler handles request of code autocompletion.
|
||||||
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var args map[string]interface{}
|
if conf.Wide.ReadOnly {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
uid := session.Values["uid"].(string)
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
|
|
||||||
fout, err := os.Create(path)
|
fout, err := os.Create(path)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
code := args["code"].(string)
|
code := args["code"].(string)
|
||||||
fout.WriteString(code)
|
fout.WriteString(code)
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
line := int(args["cursorLine"].(float64))
|
line := int(args["cursorLine"].(float64))
|
||||||
ch := int(args["cursorCh"].(float64))
|
ch := int(args["cursorCh"].(float64))
|
||||||
|
|
||||||
offset := getCursorOffset(code, line, ch)
|
offset := getCursorOffset(code, line, ch)
|
||||||
|
|
||||||
logger.Tracef("offset: %d", offset)
|
logger.Tracef("offset: %d", offset)
|
||||||
|
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
userWorkspace := conf.GetUserWorkspace(uid)
|
||||||
workspaces := filepath.SplitList(userWorkspace)
|
workspaces := filepath.SplitList(userWorkspace)
|
||||||
libPath := ""
|
libPath := ""
|
||||||
for _, workspace := range workspaces {
|
for _, workspace := range workspaces {
|
||||||
|
@ -158,22 +92,17 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
logger.Tracef("gocode set lib-path [%s]", libPath)
|
logger.Tracef("gocode set lib-path [%s]", libPath)
|
||||||
|
|
||||||
|
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
|
||||||
// FIXME: using gocode set lib-path has some issues while accrossing workspaces
|
// FIXME: using gocode set lib-path has some issues while accrossing workspaces
|
||||||
gocode := util.Go.GetExecutableInGOBIN("gocode")
|
exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
|
||||||
argv := []string{"set", "lib-path", libPath}
|
|
||||||
exec.Command(gocode, argv...).Run()
|
|
||||||
|
|
||||||
argv = []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
|
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
|
||||||
cmd := exec.Command(gocode, argv...)
|
cmd := exec.Command(gocode, argv...)
|
||||||
|
|
||||||
stdin, _ := cmd.StdinPipe()
|
|
||||||
stdin.Write([]byte(code))
|
|
||||||
stdin.Close()
|
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -184,29 +113,29 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// GetExprInfoHandler handles request of getting expression infomation.
|
// GetExprInfoHandler handles request of getting expression infomation.
|
||||||
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
username := session.Values["username"].(string)
|
uid := session.Values["uid"].(string)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
curDir := path[:strings.LastIndex(path, conf.PathSeparator)]
|
curDir := filepath.Dir(path)
|
||||||
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:]
|
filename := filepath.Base(path)
|
||||||
|
|
||||||
fout, err := os.Create(path)
|
fout, err := os.Create(path)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -216,7 +145,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -228,61 +157,61 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
logger.Tracef("offset [%d]", offset)
|
logger.Tracef("offset [%d]", offset)
|
||||||
|
|
||||||
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
|
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."}
|
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
|
||||||
cmd := exec.Command(ideStub, argv...)
|
cmd := exec.Command(ideStub, argv...)
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exprInfo := strings.TrimSpace(string(output))
|
exprInfo := strings.TrimSpace(string(output))
|
||||||
if "" == exprInfo {
|
if "" == exprInfo {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data["info"] = exprInfo
|
result.Data = exprInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindDeclarationHandler handles request of finding declaration.
|
// FindDeclarationHandler handles request of finding declaration.
|
||||||
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
uid := session.Values["uid"].(string)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
curDir := path[:strings.LastIndex(path, conf.PathSeparator)]
|
curDir := filepath.Dir(path)
|
||||||
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:]
|
filename := filepath.Base(path)
|
||||||
|
|
||||||
fout, err := os.Create(path)
|
fout, err := os.Create(path)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -292,7 +221,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -304,24 +233,24 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
logger.Tracef("offset [%d]", offset)
|
logger.Tracef("offset [%d]", offset)
|
||||||
|
|
||||||
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
|
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."}
|
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
|
||||||
cmd := exec.Command(ideStub, argv...)
|
cmd := exec.Command(ideStub, argv...)
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
found := strings.TrimSpace(string(output))
|
found := strings.TrimSpace(string(output))
|
||||||
if "" == found {
|
if "" == found {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -329,45 +258,49 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
part := found[:strings.LastIndex(found, ":")]
|
part := found[:strings.LastIndex(found, ":")]
|
||||||
cursorSep := strings.LastIndex(part, ":")
|
cursorSep := strings.LastIndex(part, ":")
|
||||||
path = found[:cursorSep]
|
path = found[:cursorSep]
|
||||||
|
|
||||||
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
|
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
|
||||||
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
|
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
|
||||||
|
|
||||||
data["path"] = path
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
|
data["path"] = filepath.ToSlash(path)
|
||||||
data["cursorLine"] = cursorLine
|
data["cursorLine"] = cursorLine
|
||||||
data["cursorCh"] = cursorCh
|
data["cursorCh"] = cursorCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindUsagesHandler handles request of finding usages.
|
// FindUsagesHandler handles request of finding usages.
|
||||||
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
uid := session.Values["uid"].(string)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := args["path"].(string)
|
filePath := args["path"].(string)
|
||||||
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
|
curDir := filepath.Dir(filePath)
|
||||||
filename := filePath[strings.LastIndex(filePath, conf.PathSeparator)+1:]
|
filename := filepath.Base(filePath)
|
||||||
|
|
||||||
fout, err := os.Create(filePath)
|
fout, err := os.Create(filePath)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -377,7 +310,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -388,36 +321,36 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
offset := getCursorOffset(code, line, ch)
|
offset := getCursorOffset(code, line, ch)
|
||||||
logger.Tracef("offset [%d]", offset)
|
logger.Tracef("offset [%d]", offset)
|
||||||
|
|
||||||
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
|
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."}
|
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
|
||||||
cmd := exec.Command(ideStub, argv...)
|
cmd := exec.Command(ideStub, argv...)
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := strings.TrimSpace(string(output))
|
out := strings.TrimSpace(string(output))
|
||||||
if "" == result {
|
if "" == out {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
founds := strings.Split(result, "\n")
|
founds := strings.Split(out, "\n")
|
||||||
usages := []*file.Snippet{}
|
usages := []*file.Snippet{}
|
||||||
for _, found := range founds {
|
for _, found := range founds {
|
||||||
found = strings.TrimSpace(found)
|
found = strings.TrimSpace(found)
|
||||||
|
|
||||||
part := found[:strings.LastIndex(found, ":")]
|
part := found[:strings.LastIndex(found, ":")]
|
||||||
cursorSep := strings.LastIndex(part, ":")
|
cursorSep := strings.LastIndex(part, ":")
|
||||||
path := found[:cursorSep]
|
path := filepath.ToSlash(found[:cursorSep])
|
||||||
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
|
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
|
||||||
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
|
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
|
||||||
|
|
||||||
|
@ -425,7 +358,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
usages = append(usages, usage)
|
usages = append(usages, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
data["founds"] = usages
|
result.Data = usages
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCursorOffset calculates the cursor offset.
|
// getCursorOffset calculates the cursor offset.
|
||||||
|
@ -454,8 +387,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
|
||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCmdEnv(cmd *exec.Cmd, username string) {
|
func setCmdEnv(cmd *exec.Cmd, userId string) {
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
userWorkspace := conf.GetUserWorkspace(userId)
|
||||||
|
|
||||||
cmd.Env = append(cmd.Env,
|
cmd.Env = append(cmd.Env,
|
||||||
"GOPATH="+userWorkspace,
|
"GOPATH="+userWorkspace,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -20,9 +20,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoFmtHandler handles request of formatting Go source code.
|
// GoFmtHandler handles request of formatting Go source code.
|
||||||
|
@ -31,30 +31,37 @@ import (
|
||||||
// 1. gofmt
|
// 1. gofmt
|
||||||
// 2. goimports
|
// 2. goimports
|
||||||
func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
uid := session.Values["uid"].(string)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := args["file"].(string)
|
filePath := args["file"].(string)
|
||||||
|
|
||||||
if util.Go.IsAPI(filePath) {
|
if gulu.Go.IsAPI(filePath) {
|
||||||
// ignore it
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +69,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -72,12 +79,19 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fout.WriteString(code)
|
fout.WriteString(code)
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt := conf.GetGoFmt(username)
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
|
data["code"] = code
|
||||||
|
|
||||||
|
result.Data = data
|
||||||
|
|
||||||
|
fmt := conf.GetGoFmt(uid)
|
||||||
|
|
||||||
argv := []string{filePath}
|
argv := []string{filePath}
|
||||||
cmd := exec.Command(fmt, argv...)
|
cmd := exec.Command(fmt, argv...)
|
||||||
|
@ -86,8 +100,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
output := string(bytes)
|
output := string(bytes)
|
||||||
if "" == output {
|
if "" == output {
|
||||||
// format error, returns the original content
|
// format error, returns the original content
|
||||||
data["succ"] = true
|
result.Code = 0
|
||||||
data["code"] = code
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -99,7 +112,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fout.WriteString(code)
|
fout.WriteString(code)
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -18,7 +18,7 @@ package event
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/gulu"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,7 +28,7 @@ const (
|
||||||
EvtCodeGOROOTNotFound
|
EvtCodeGOROOTNotFound
|
||||||
// EvtCodeGocodeNotFound indicates an event: not found gocode
|
// EvtCodeGocodeNotFound indicates an event: not found gocode
|
||||||
EvtCodeGocodeNotFound
|
EvtCodeGocodeNotFound
|
||||||
// EvtCodeIDEStubNotFound indicates an event: not found ide_stub
|
// EvtCodeIDEStubNotFound indicates an event: not found gotools
|
||||||
EvtCodeIDEStubNotFound
|
EvtCodeIDEStubNotFound
|
||||||
// EvtCodeServerInternalError indicates an event: server internal error
|
// EvtCodeServerInternalError indicates an event: server internal error
|
||||||
EvtCodeServerInternalError
|
EvtCodeServerInternalError
|
||||||
|
@ -38,7 +38,7 @@ const (
|
||||||
const maxQueueLength = 10
|
const maxQueueLength = 10
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// Event represents an event.
|
// Event represents an event.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
|
@ -69,6 +69,8 @@ var UserEventQueues = queues{}
|
||||||
// Load initializes the event handling.
|
// Load initializes the event handling.
|
||||||
func Load() {
|
func Load() {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
for event := range EventQueue {
|
for event := range EventQueue {
|
||||||
logger.Debugf("Received a global event [code=%d]", event.Code)
|
logger.Debugf("Received a global event [code=%d]", event.Code)
|
||||||
|
|
||||||
|
@ -84,21 +86,19 @@ func Load() {
|
||||||
|
|
||||||
// AddHandler adds the specified handlers to user event queues.
|
// AddHandler adds the specified handlers to user event queues.
|
||||||
func (uq *UserEventQueue) AddHandler(handlers ...Handler) {
|
func (uq *UserEventQueue) AddHandler(handlers ...Handler) {
|
||||||
for _, handler := range handlers {
|
uq.Handlers = append(uq.Handlers, handlers...)
|
||||||
uq.Handlers = append(uq.Handlers, handler)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes a user event queue with the specified wide session id.
|
// New initializes a user event queue with the specified wide session id.
|
||||||
func (ueqs queues) New(sid string) *UserEventQueue {
|
func (ueqs queues) New(sid string) *UserEventQueue {
|
||||||
q := ueqs[sid]
|
|
||||||
if nil != q {
|
if q, ok := ueqs[sid]; ok {
|
||||||
logger.Warnf("Already exist a user queue in session [%s]", sid)
|
logger.Warnf("Already exist a user queue in session [%s]", sid)
|
||||||
|
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
q = &UserEventQueue{
|
q := &UserEventQueue{
|
||||||
Sid: sid,
|
Sid: sid,
|
||||||
Queue: make(chan *Event, maxQueueLength),
|
Queue: make(chan *Event, maxQueueLength),
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,8 @@ func (ueqs queues) New(sid string) *UserEventQueue {
|
||||||
ueqs[sid] = q
|
ueqs[sid] = q
|
||||||
|
|
||||||
go func() { // start listening
|
go func() { // start listening
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
for evt := range q.Queue {
|
for evt := range q.Queue {
|
||||||
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
|
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
|
||||||
|
|
||||||
|
@ -121,13 +123,12 @@ func (ueqs queues) New(sid string) *UserEventQueue {
|
||||||
|
|
||||||
// Close closes a user event queue with the specified wide session id.
|
// Close closes a user event queue with the specified wide session id.
|
||||||
func (ueqs queues) Close(sid string) {
|
func (ueqs queues) Close(sid string) {
|
||||||
q := ueqs[sid]
|
|
||||||
if nil == q {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if q, ok := ueqs[sid]; ok {
|
||||||
|
close(q.Queue)
|
||||||
delete(ueqs, sid)
|
delete(ueqs, sid)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handler represents an event handler.
|
// Handler represents an event handler.
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -20,11 +20,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/gulu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetZip handles request of retrieving zip file.
|
// GetZipHandler handles request of retrieving zip file.
|
||||||
func GetZip(w http.ResponseWriter, r *http.Request) {
|
func GetZipHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
path := q["path"][0]
|
path := q["path"][0]
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func GetZip(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.File.IsExist(path) {
|
if !gulu.File.IsExist(path) {
|
||||||
http.Error(w, "Not Found", 404)
|
http.Error(w, "Not Found", 404)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -49,41 +49,54 @@ func GetZip(w http.ResponseWriter, r *http.Request) {
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateZip handles request of creating zip.
|
// CreateZipHandler handles request of creating zip.
|
||||||
func CreateZip(w http.ResponseWriter, r *http.Request) {
|
func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
data := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, data)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
data.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
|
var name string
|
||||||
|
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
|
|
||||||
if !util.File.IsExist(path) {
|
if nil != args["name"] {
|
||||||
data["succ"] = false
|
name = args["name"].(string)
|
||||||
data["msg"] = "Can't find file [" + path + "]"
|
} else {
|
||||||
|
name = base
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
|
if !gulu.File.IsExist(path) {
|
||||||
|
data.Code = -1
|
||||||
|
data.Msg = "Can't find file [" + path + "]"
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zipFile, err := util.Zip.Create(path + ".zip")
|
zipPath := filepath.Join(dir, name)
|
||||||
|
zipFile, err := gulu.Zip.Create(zipPath + ".zip")
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
data.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer zipFile.Close()
|
defer zipFile.Close()
|
||||||
|
|
||||||
if util.File.IsDir(path) {
|
if gulu.File.IsDir(path) {
|
||||||
zipFile.AddDirectory(base, path)
|
zipFile.AddDirectory(base, path)
|
||||||
} else {
|
} else {
|
||||||
zipFile.AddEntry(base, path)
|
zipFile.AddEntry(base, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.Data = zipPath
|
||||||
}
|
}
|
||||||
|
|
390
file/files.go
390
file/files.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -24,21 +24,22 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/event"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/event"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// Node represents a file node in file tree.
|
// Node represents a file node in file tree.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
IconSkin string `json:"iconSkin"` // Value should be end with a space
|
IconSkin string `json:"iconSkin"` // Value should be end with a space
|
||||||
|
IsParent bool `json:"isParent"`
|
||||||
Type string `json:"type"` // "f": file, "d": directory
|
Type string `json:"type"` // "f": file, "d": directory
|
||||||
Creatable bool `json:"creatable"` // whether can create file in this file node
|
Creatable bool `json:"creatable"` // whether can create file in this file node
|
||||||
Removable bool `json:"removable"` // whether can remove this file node
|
Removable bool `json:"removable"` // whether can remove this file node
|
||||||
|
@ -59,7 +60,7 @@ var apiNode *Node
|
||||||
|
|
||||||
// initAPINode builds the Go API file node.
|
// initAPINode builds the Go API file node.
|
||||||
func initAPINode() {
|
func initAPINode() {
|
||||||
apiPath := util.Go.GetAPIPath()
|
apiPath := gulu.Go.GetAPIPath()
|
||||||
|
|
||||||
apiNode = &Node{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
|
apiNode = &Node{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
|
||||||
Creatable: false, Removable: false, IsGoAPI: true, Children: []*Node{}}
|
Creatable: false, Removable: false, IsGoAPI: true, Children: []*Node{}}
|
||||||
|
@ -67,26 +68,26 @@ func initAPINode() {
|
||||||
walk(apiPath, apiNode, false, false, true)
|
walk(apiPath, apiNode, false, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFiles handles request of constructing user workspace file tree.
|
// GetFilesHandler handles request of constructing user workspace file tree.
|
||||||
//
|
//
|
||||||
// The Go API source code package also as a child node,
|
// The Go API source code package also as a child node,
|
||||||
// so that users can easily view the Go API source code in file tree.
|
// so that users can easily view the Go API source code in file tree.
|
||||||
func GetFiles(w http.ResponseWriter, r *http.Request) {
|
func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetGzJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if session.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetGzResult(w, r, result)
|
||||||
|
|
||||||
|
userWorkspace := conf.GetUserWorkspace(uid)
|
||||||
workspaces := filepath.SplitList(userWorkspace)
|
workspaces := filepath.SplitList(userWorkspace)
|
||||||
|
|
||||||
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
|
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}}
|
||||||
|
|
||||||
if nil == apiNode { // lazy init
|
if nil == apiNode { // lazy init
|
||||||
initAPINode()
|
initAPINode()
|
||||||
|
@ -96,9 +97,16 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, workspace := range workspaces {
|
for _, workspace := range workspaces {
|
||||||
workspacePath := workspace + conf.PathSeparator + "src"
|
workspacePath := workspace + conf.PathSeparator + "src"
|
||||||
|
|
||||||
workspaceNode := Node{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
|
workspaceNode := Node{
|
||||||
Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d",
|
Id: filepath.ToSlash(workspacePath), // jQuery API can't accept "\", so we convert it to "/"
|
||||||
Creatable: true, Removable: false, IsGoAPI: false, Children: []*Node{}}
|
Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
|
||||||
|
Path: filepath.ToSlash(workspacePath),
|
||||||
|
IconSkin: "ico-ztree-dir-workspace ",
|
||||||
|
Type: "d",
|
||||||
|
Creatable: true,
|
||||||
|
Removable: false,
|
||||||
|
IsGoAPI: false,
|
||||||
|
Children: []*Node{}}
|
||||||
|
|
||||||
walk(workspacePath, &workspaceNode, true, true, false)
|
walk(workspacePath, &workspaceNode, true, true, false)
|
||||||
|
|
||||||
|
@ -109,14 +117,28 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
// add Go API node
|
// add Go API node
|
||||||
root.Children = append(root.Children, apiNode)
|
root.Children = append(root.Children, apiNode)
|
||||||
|
|
||||||
data["root"] = root
|
result.Data = root
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshDirectory handles request of refresh a directory of file tree.
|
// RefreshDirectoryHandler handles request of refresh a directory of file tree.
|
||||||
func RefreshDirectory(w http.ResponseWriter, r *http.Request) {
|
func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
path := r.FormValue("path")
|
path := r.FormValue("path")
|
||||||
|
|
||||||
|
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
node := Node{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
|
node := Node{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
|
||||||
|
|
||||||
walk(path, &node, true, true, false)
|
walk(path, &node, true, true, false)
|
||||||
|
@ -131,76 +153,106 @@ func RefreshDirectory(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile handles request of opening file by editor.
|
// GetFileHandler handles request of opening file by editor.
|
||||||
func GetFile(w http.ResponseWriter, r *http.Request) {
|
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
|
|
||||||
size := util.File.GetFileSize(path)
|
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
|
||||||
if size > 5242880 { // 5M
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
data["succ"] = false
|
|
||||||
data["msg"] = "This file is too large to open :("
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size := gulu.File.GetFileSize(path)
|
||||||
|
if size > 5242880 { // 5M
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "This file is too large to open :("
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
buf, _ := ioutil.ReadFile(path)
|
buf, _ := ioutil.ReadFile(path)
|
||||||
|
|
||||||
extension := filepath.Ext(path)
|
extension := filepath.Ext(path)
|
||||||
|
|
||||||
if util.File.IsImg(extension) {
|
if gulu.File.IsImg(extension) {
|
||||||
// image file will be open in a browser tab
|
// image file will be open in a browser tab
|
||||||
|
|
||||||
data["mode"] = "img"
|
data["mode"] = "img"
|
||||||
|
|
||||||
username := conf.GetOwner(path)
|
userId := conf.GetOwner(path)
|
||||||
if "" == username {
|
if "" == userId {
|
||||||
logger.Warnf("The path [%s] has no owner")
|
logger.Warnf("The path [%s] has no owner", path)
|
||||||
data["path"] = ""
|
data["path"] = ""
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := conf.GetUser(username)
|
user := conf.GetUser(uid)
|
||||||
|
|
||||||
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1)
|
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.WorkspacePath(), "", 1)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content := string(buf)
|
content := string(buf)
|
||||||
|
|
||||||
if util.File.IsBinary(content) {
|
if gulu.File.IsBinary(content) {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
data["msg"] = "Can't open a binary file :("
|
result.Msg = "Can't open a binary file :("
|
||||||
} else {
|
} else {
|
||||||
data["content"] = content
|
data["content"] = content
|
||||||
data["mode"] = getEditorMode(extension)
|
|
||||||
data["path"] = path
|
data["path"] = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveFile handles request of saving file.
|
// SaveFileHandler handles request of saving file.
|
||||||
func SaveFile(w http.ResponseWriter, r *http.Request) {
|
func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -208,11 +260,17 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
filePath := args["file"].(string)
|
filePath := args["file"].(string)
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
|
|
||||||
|
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fout, err := os.Create(filePath)
|
fout, err := os.Create(filePath)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -223,7 +281,7 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
||||||
|
@ -233,28 +291,43 @@ func SaveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFile handles request of creating file or directory.
|
// NewFileHandler handles request of creating file or directory.
|
||||||
func NewFile(w http.ResponseWriter, r *http.Request) {
|
func NewFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
|
|
||||||
|
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fileType := args["fileType"].(string)
|
fileType := args["fileType"].(string)
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
|
||||||
if !createFile(path, fileType) {
|
if !createFile(path, fileType) {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
||||||
Data: "can't create file " + path}
|
Data: "can't create file " + path}
|
||||||
|
@ -263,64 +336,110 @@ func NewFile(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if "f" == fileType {
|
if "f" == fileType {
|
||||||
extension := filepath.Ext(path)
|
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
|
||||||
data["mode"] = getEditorMode(extension)
|
} else {
|
||||||
}
|
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFile handles request of removing file or directory.
|
}
|
||||||
func RemoveFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{"succ": true}
|
// RemoveFileHandler handles request of removing file or directory.
|
||||||
defer util.RetJSON(w, r, data)
|
func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string)
|
path := args["path"].(string)
|
||||||
|
|
||||||
|
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
|
||||||
if !removeFile(path) {
|
if !removeFile(path) {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
||||||
Data: "can't remove file " + path}
|
Data: "can't remove file " + path}
|
||||||
}
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameFile handles request of renaming file or directory.
|
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
|
||||||
func RenameFile(w http.ResponseWriter, r *http.Request) {
|
}
|
||||||
data := map[string]interface{}{"succ": true}
|
|
||||||
defer util.RetJSON(w, r, data)
|
// RenameFileHandler handles request of renaming file or directory.
|
||||||
|
func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldPath := args["oldPath"].(string)
|
oldPath := args["oldPath"].(string)
|
||||||
|
if gulu.Go.IsAPI(oldPath) ||
|
||||||
|
!session.CanAccess(uid, oldPath) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newPath := args["newPath"].(string)
|
newPath := args["newPath"].(string)
|
||||||
|
if gulu.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
|
||||||
if !renameFile(oldPath, newPath) {
|
if !renameFile(oldPath, newPath) {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
|
||||||
Data: "can't rename file " + oldPath}
|
Data: "can't rename file " + oldPath}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use to find results sorting.
|
// Use to find results sorting.
|
||||||
|
@ -335,34 +454,40 @@ func (f foundPaths) Len() int { return len(f) }
|
||||||
func (f foundPaths) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
func (f foundPaths) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
|
func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
|
||||||
|
|
||||||
// Find handles request of find files under the specified directory with the specified filename pattern.
|
// FindHandler handles request of find files under the specified directory with the specified filename pattern.
|
||||||
func Find(w http.ResponseWriter, r *http.Request) {
|
func FindHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := args["path"].(string) // path of selected file in file tree
|
path := args["path"].(string) // path of selected file in file tree
|
||||||
name := args["name"].(string)
|
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if session.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := session.Values["username"].(string)
|
|
||||||
|
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
name := args["name"].(string)
|
||||||
|
|
||||||
|
userWorkspace := conf.GetUserWorkspace(uid)
|
||||||
workspaces := filepath.SplitList(userWorkspace)
|
workspaces := filepath.SplitList(userWorkspace)
|
||||||
|
|
||||||
if "" != path && !util.File.IsDir(path) {
|
if "" != path && !gulu.File.IsDir(path) {
|
||||||
path = filepath.Dir(path)
|
path = filepath.Dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,27 +497,34 @@ func Find(w http.ResponseWriter, r *http.Request) {
|
||||||
rs := find(workspace+conf.PathSeparator+"src", name, []*string{})
|
rs := find(workspace+conf.PathSeparator+"src", name, []*string{})
|
||||||
|
|
||||||
for _, r := range rs {
|
for _, r := range rs {
|
||||||
substr := util.Str.LCS(path, *r)
|
substr := gulu.Str.LCS(path, *r)
|
||||||
|
|
||||||
founds = append(founds, &foundPath{Path: *r, score: len(substr)})
|
founds = append(founds, &foundPath{Path: filepath.ToSlash(*r), score: len(substr)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(founds)
|
sort.Sort(founds)
|
||||||
|
|
||||||
data["founds"] = founds
|
result.Data = founds
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchText handles request of searching files under the specified directory with the specified keyword.
|
// SearchTextHandler handles request of searching files under the specified directory with the specified keyword.
|
||||||
func SearchText(w http.ResponseWriter, r *http.Request) {
|
func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
defer util.RetJSON(w, r, data)
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -400,7 +532,7 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
if nil == wSession {
|
if nil == wSession {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -409,7 +541,7 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
dir := args["dir"].(string)
|
dir := args["dir"].(string)
|
||||||
if "" == dir {
|
if "" == dir {
|
||||||
userWorkspace := conf.GetUserWorkspace(wSession.Username)
|
userWorkspace := conf.GetUserWorkspace(wSession.UserId)
|
||||||
workspaces := filepath.SplitList(userWorkspace)
|
workspaces := filepath.SplitList(userWorkspace)
|
||||||
dir = workspaces[0]
|
dir = workspaces[0]
|
||||||
}
|
}
|
||||||
|
@ -418,13 +550,13 @@ func SearchText(w http.ResponseWriter, r *http.Request) {
|
||||||
text := args["text"].(string)
|
text := args["text"].(string)
|
||||||
|
|
||||||
founds := []*Snippet{}
|
founds := []*Snippet{}
|
||||||
if util.File.IsDir(dir) {
|
if gulu.File.IsDir(dir) {
|
||||||
founds = search(dir, extension, text, []*Snippet{})
|
founds = search(dir, extension, text, []*Snippet{})
|
||||||
} else {
|
} else {
|
||||||
founds = searchInFile(dir, text)
|
founds = searchInFile(dir, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
data["founds"] = founds
|
result.Data = founds
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk traverses the specified path to build a file tree.
|
// walk traverses the specified path to build a file tree.
|
||||||
|
@ -436,7 +568,13 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
|
||||||
|
|
||||||
fio, _ := os.Lstat(fpath)
|
fio, _ := os.Lstat(fpath)
|
||||||
|
|
||||||
child := Node{Name: filename, Path: fpath, Removable: removable, IsGoAPI: isGOAPI, Children: []*Node{}}
|
child := Node{
|
||||||
|
Id: filepath.ToSlash(fpath), // jQuery API can't accept "\", so we convert it to "/"
|
||||||
|
Name: filename,
|
||||||
|
Path: filepath.ToSlash(fpath),
|
||||||
|
Removable: removable,
|
||||||
|
IsGoAPI: isGOAPI,
|
||||||
|
Children: []*Node{}}
|
||||||
node.Children = append(node.Children, &child)
|
node.Children = append(node.Children, &child)
|
||||||
|
|
||||||
if nil == fio {
|
if nil == fio {
|
||||||
|
@ -449,6 +587,7 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
|
||||||
child.Type = "d"
|
child.Type = "d"
|
||||||
child.Creatable = creatable
|
child.Creatable = creatable
|
||||||
child.IconSkin = "ico-ztree-dir "
|
child.IconSkin = "ico-ztree-dir "
|
||||||
|
child.IsParent = true
|
||||||
|
|
||||||
walk(fpath, &child, creatable, removable, isGOAPI)
|
walk(fpath, &child, creatable, removable, isGOAPI)
|
||||||
} else {
|
} else {
|
||||||
|
@ -457,7 +596,6 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
|
||||||
ext := filepath.Ext(fpath)
|
ext := filepath.Ext(fpath)
|
||||||
|
|
||||||
child.IconSkin = getIconSkin(ext)
|
child.IconSkin = getIconSkin(ext)
|
||||||
child.Mode = getEditorMode(ext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,13 +626,18 @@ func listFiles(dirname string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if fio.IsDir() {
|
if fio.IsDir() {
|
||||||
// exclude the .git direcitory
|
// exclude the .git, .svn, .hg direcitory
|
||||||
if ".git" == fio.Name() {
|
if ".git" == fio.Name() || ".svn" == fio.Name() || ".hg" == fio.Name() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dirs = append(dirs, name)
|
dirs = append(dirs, name)
|
||||||
} else {
|
} else {
|
||||||
|
// exclude the .DS_Store directory on Mac OS X
|
||||||
|
if ".DS_Store" == fio.Name() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
files = append(files, name)
|
files = append(files, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,7 +649,7 @@ func listFiles(dirname string) []string {
|
||||||
//
|
//
|
||||||
// Refers to the zTree document for CSS class names.
|
// Refers to the zTree document for CSS class names.
|
||||||
func getIconSkin(filenameExtension string) string {
|
func getIconSkin(filenameExtension string) string {
|
||||||
if util.File.IsImg(filenameExtension) {
|
if gulu.File.IsImg(filenameExtension) {
|
||||||
return "ico-ztree-img "
|
return "ico-ztree-img "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,34 +677,6 @@ func getIconSkin(filenameExtension string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEditorMode gets editor mode with the specified filename extension.
|
|
||||||
//
|
|
||||||
// Refers to the CodeMirror document for modes.
|
|
||||||
func getEditorMode(filenameExtension string) string {
|
|
||||||
switch filenameExtension {
|
|
||||||
case ".go":
|
|
||||||
return "text/x-go"
|
|
||||||
case ".html":
|
|
||||||
return "text/html"
|
|
||||||
case ".md":
|
|
||||||
return "text/x-markdown"
|
|
||||||
case ".js":
|
|
||||||
return "text/javascript"
|
|
||||||
case ".json":
|
|
||||||
return "application/json"
|
|
||||||
case ".css":
|
|
||||||
return "text/css"
|
|
||||||
case ".xml":
|
|
||||||
return "application/xml"
|
|
||||||
case ".sh":
|
|
||||||
return "text/x-sh"
|
|
||||||
case ".sql":
|
|
||||||
return "text/x-sql"
|
|
||||||
default:
|
|
||||||
return "text/plain"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// createFile creates file on the specified path.
|
// createFile creates file on the specified path.
|
||||||
//
|
//
|
||||||
// fileType:
|
// fileType:
|
||||||
|
@ -580,7 +695,7 @@ func createFile(path, fileType string) bool {
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
logger.Debugf("Created file [%s]", path)
|
logger.Tracef("Created file [%s]", path)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
case "d":
|
case "d":
|
||||||
|
@ -592,7 +707,7 @@ func createFile(path, fileType string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Created directory [%s]", path)
|
logger.Tracef("Created directory [%s]", path)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -610,7 +725,7 @@ func removeFile(path string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Removed [%s]", path)
|
logger.Tracef("Removed [%s]", path)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -623,7 +738,7 @@ func renameFile(oldPath, newPath string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Renamed [%s] to [%s]", oldPath, newPath)
|
logger.Tracef("Renamed [%s] to [%s]", oldPath, newPath)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -653,7 +768,7 @@ func find(dir, name string, results []*string) []*string {
|
||||||
path := dir + fname
|
path := dir + fname
|
||||||
|
|
||||||
if fileInfo.IsDir() {
|
if fileInfo.IsDir() {
|
||||||
if util.Str.Contains(fname, defaultExcludesFind) {
|
if gulu.Str.Contains(fname, defaultExcludesFind) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -726,7 +841,7 @@ func searchInFile(path string, text string) []*Snippet {
|
||||||
}
|
}
|
||||||
|
|
||||||
content := string(bytes)
|
content := string(bytes)
|
||||||
if util.File.IsBinary(content) {
|
if gulu.File.IsBinary(content) {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,7 +851,8 @@ func searchInFile(path string, text string) []*Snippet {
|
||||||
ch := strings.Index(strings.ToLower(line), strings.ToLower(text))
|
ch := strings.Index(strings.ToLower(line), strings.ToLower(text))
|
||||||
|
|
||||||
if -1 != ch {
|
if -1 != ch {
|
||||||
snippet := &Snippet{Path: path, Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
|
snippet := &Snippet{Path: filepath.ToSlash(path),
|
||||||
|
Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
|
||||||
|
|
||||||
ret = append(ret, snippet)
|
ret = append(ret, snippet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileInfo struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleUpload(p *multipart.Part, dir string) (fi *fileInfo) {
|
|
||||||
fi = &fileInfo{
|
|
||||||
Name: p.FileName(),
|
|
||||||
Type: p.Header.Get("Content-Type"),
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Clean(dir + "/" + fi.Name)
|
|
||||||
f, _ := os.Create(path)
|
|
||||||
|
|
||||||
io.Copy(f, p)
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleUploads(r *http.Request, dir string) (fileInfos []*fileInfo) {
|
|
||||||
fileInfos = make([]*fileInfo, 0)
|
|
||||||
mr, err := r.MultipartReader()
|
|
||||||
|
|
||||||
part, err := mr.NextPart()
|
|
||||||
|
|
||||||
for err == nil {
|
|
||||||
if name := part.FormName(); name != "" {
|
|
||||||
if part.FileName() != "" {
|
|
||||||
fileInfos = append(fileInfos, handleUpload(part, dir))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
part, err = mr.NextPart()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload handles request of file upload.
|
|
||||||
func Upload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{"succ": true}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
q := r.URL.Query()
|
|
||||||
dir := q["path"][0]
|
|
||||||
|
|
||||||
data["files"] = handleUploads(r, dir)
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -23,7 +23,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/gulu"
|
||||||
)
|
)
|
||||||
|
|
||||||
type element struct {
|
type element struct {
|
||||||
|
@ -32,16 +32,16 @@ type element struct {
|
||||||
Ch int
|
Ch int
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOutline gets outfile of a go file.
|
// GetOutlineHandler gets outfile of a go file.
|
||||||
func GetOutline(w http.ResponseWriter, r *http.Request) {
|
func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -51,11 +51,14 @@ func GetOutline(w http.ResponseWriter, r *http.Request) {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
f, err := parser.ParseFile(fset, "", code, 0)
|
f, err := parser.ParseFile(fset, "", code, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
// ast.Print(fset, f)
|
// ast.Print(fset, f)
|
||||||
|
|
||||||
line, ch := getCursor(code, int(f.Name.Pos()))
|
line, ch := getCursor(code, int(f.Name.Pos()))
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014-2018, b3log.org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file frontend tool.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
|
||||||
|
* @version 0.2.0.0, Oct 5, 2018
|
||||||
|
*/
|
||||||
|
var gulp = require('gulp')
|
||||||
|
var concat = require('gulp-concat')
|
||||||
|
var cleanCSS = require('gulp-clean-css')
|
||||||
|
var uglify = require('gulp-uglify')
|
||||||
|
var sourcemaps = require('gulp-sourcemaps')
|
||||||
|
|
||||||
|
function minLibCSS () {
|
||||||
|
// css
|
||||||
|
var cssLibs = [
|
||||||
|
'./static/js/lib/jquery-layout/layout-default-latest.css',
|
||||||
|
'./static/js/lib/codemirror-5.1/codemirror.css',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/lint/lint.css',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
|
||||||
|
'./static/js/overwrite/codemirror/theme/*.css']
|
||||||
|
return gulp.src(cssLibs).
|
||||||
|
pipe(cleanCSS()).
|
||||||
|
pipe(concat('lib.min.css')).
|
||||||
|
pipe(gulp.dest('./static/css/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function minZTreeStyleCSS () {
|
||||||
|
return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
|
||||||
|
pipe(cleanCSS()).
|
||||||
|
pipe(concat('zTreeStyle.min.css')).
|
||||||
|
pipe(gulp.dest('./static/js/lib/ztree/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function minWideCSS () {
|
||||||
|
var cssWide = [
|
||||||
|
'./static/css/dialog.css',
|
||||||
|
'./static/css/base.css',
|
||||||
|
'./static/css/wide.css',
|
||||||
|
'./static/css/side.css',
|
||||||
|
'./static/css/start.css',
|
||||||
|
'./static/css/about.css',
|
||||||
|
]
|
||||||
|
|
||||||
|
return gulp.src(cssWide).
|
||||||
|
pipe(cleanCSS()).
|
||||||
|
pipe(concat('wide.min.css')).
|
||||||
|
pipe(gulp.dest('./static/css/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function minLibJS () {
|
||||||
|
// js
|
||||||
|
var jsLibs = [
|
||||||
|
'./static/js/lib/jquery-2.1.1.min.js',
|
||||||
|
'./static/js/lib/jquery-ui.min.js',
|
||||||
|
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
|
||||||
|
'./static/js/lib/reconnecting-websocket.js',
|
||||||
|
'./static/js/lib/Autolinker.min.js',
|
||||||
|
'./static/js/lib/emmet.js',
|
||||||
|
'./static/js/lib/js-beautify-1.5.4/beautify.js',
|
||||||
|
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
|
||||||
|
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
|
||||||
|
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
|
||||||
|
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
|
||||||
|
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/codemirror.min.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
|
||||||
|
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/search/search.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/addon/comment/comment.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/meta.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/go/go.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/css/css.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
|
||||||
|
'./static/js/lib/codemirror-5.1/keymap/vim.js',
|
||||||
|
'./static/js/lib/lint/json-lint.js',
|
||||||
|
'./static/js/lib/lint/go-lint.js']
|
||||||
|
return gulp.src(jsLibs).
|
||||||
|
pipe(uglify()).
|
||||||
|
pipe(concat('lib.min.js')).
|
||||||
|
pipe(gulp.dest('./static/js/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function minWideJS () {
|
||||||
|
var jsWide = [
|
||||||
|
'./static/js/tabs.js',
|
||||||
|
'./static/js/tabs.js',
|
||||||
|
'./static/js/dialog.js',
|
||||||
|
'./static/js/editors.js',
|
||||||
|
'./static/js/notification.js',
|
||||||
|
'./static/js/tree.js',
|
||||||
|
'./static/js/wide.js',
|
||||||
|
'./static/js/session.js',
|
||||||
|
'./static/js/menu.js',
|
||||||
|
'./static/js/windows.js',
|
||||||
|
'./static/js/hotkeys.js',
|
||||||
|
'./static/js/bottomGroup.js',
|
||||||
|
]
|
||||||
|
return gulp.src(jsWide).
|
||||||
|
pipe(sourcemaps.init()).
|
||||||
|
pipe(uglify()).
|
||||||
|
pipe(concat('wide.min.js')).
|
||||||
|
pipe(sourcemaps.write('.')).
|
||||||
|
pipe(gulp.dest('./static/js/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('default',
|
||||||
|
gulp.series(
|
||||||
|
gulp.parallel(minLibCSS, minZTreeStyleCSS, minWideCSS, minLibJS, minWideJS)))
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"colon": ": ",
|
"colon": ": ",
|
||||||
"wide": "Wide",
|
"wide": "Wide",
|
||||||
"wide_title": "Team development, anytime, anywhere",
|
"wide_title": "Playing golang, anytime, anywhere",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"file": "File",
|
"file": "File",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
|
@ -28,7 +28,6 @@
|
||||||
"close_all_files": "Close All",
|
"close_all_files": "Close All",
|
||||||
"save_all_files": "Save All",
|
"save_all_files": "Save All",
|
||||||
"format": "Format",
|
"format": "Format",
|
||||||
"goget": "go get",
|
|
||||||
"goinstall": "go install",
|
"goinstall": "go install",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"build_n_run": "Build&Run",
|
"build_n_run": "Build&Run",
|
||||||
|
@ -102,9 +101,6 @@
|
||||||
"start-install": "START [go install]",
|
"start-install": "START [go install]",
|
||||||
"install-succ": "[go install] SUCCESS",
|
"install-succ": "[go install] SUCCESS",
|
||||||
"install-error": "[go install] ERROR",
|
"install-error": "[go install] ERROR",
|
||||||
"start-get": "START [go get]",
|
|
||||||
"get-succ": "[go get] SUCCESS",
|
|
||||||
"get-error": "[go get] ERROR",
|
|
||||||
"check_version": "Checking update",
|
"check_version": "Checking update",
|
||||||
"new_version_available": "new version available",
|
"new_version_available": "new version available",
|
||||||
"go_env": "Go",
|
"go_env": "Go",
|
||||||
|
@ -118,13 +114,12 @@
|
||||||
"team": "Team",
|
"team": "Team",
|
||||||
"sing_up_error": "Sign Up Error",
|
"sing_up_error": "Sign Up Error",
|
||||||
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
|
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
|
||||||
"invalid_email": "Invalid Email",
|
|
||||||
"password_no_match": "Password doesn't match the confirmation",
|
"password_no_match": "Password doesn't match the confirmation",
|
||||||
"discard": "Discard",
|
"discard": "Discard",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"close_other": "Close Other",
|
"close_other": "Close Other",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"perference": "Perference",
|
"preference": "Preference",
|
||||||
"appearence": "Appearence",
|
"appearence": "Appearence",
|
||||||
"gotool": "Go Tool",
|
"gotool": "Go Tool",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
|
@ -137,7 +132,6 @@
|
||||||
"clearOutput": "Clear Output",
|
"clearOutput": "Clear Output",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"import": "Import",
|
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"tab_size": "Tab Size",
|
"tab_size": "Tab Size",
|
||||||
"copy_file_path": "Copy File Path",
|
"copy_file_path": "Copy File Path",
|
||||||
|
@ -156,11 +150,8 @@
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"toggle_comment": "Toggle Comment",
|
"toggle_comment": "Toggle Comment",
|
||||||
"find_in_files": "Find in Files",
|
"find_in_files": "Find in Files",
|
||||||
"email": "Email",
|
|
||||||
"no_empty": "Can not Empty!",
|
"no_empty": "Can not Empty!",
|
||||||
"change_avatar": "Avatar modify go",
|
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
"pricing": "Pricing",
|
|
||||||
"search_no_match": "No matching files were found.",
|
"search_no_match": "No matching files were found.",
|
||||||
"outline": "Outline",
|
"outline": "Outline",
|
||||||
"govet": "go vet",
|
"govet": "go vet",
|
||||||
|
@ -170,6 +161,11 @@
|
||||||
"restore_outline": "Restore Outline",
|
"restore_outline": "Restore Outline",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"short_url": "Short URL",
|
"embeded": "Embeded",
|
||||||
"embeded": "Embeded"
|
"terms": "Terms",
|
||||||
|
"download": "Download",
|
||||||
|
"decompress": "Decompress",
|
||||||
|
"keymap": "Keymap",
|
||||||
|
"resize": "Resize",
|
||||||
|
"sponsor": "Sponsor"
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"colon": ":",
|
"colon": ":",
|
||||||
"wide": "Wide",
|
"wide": "Wide",
|
||||||
"wide_title": "チーム開発、いつでも、どこでも",
|
"wide_title": "いつでも、どこでもゴランをプレイする",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"file": "ファイル",
|
"file": "ファイル",
|
||||||
"login": "ログイン",
|
"login": "ログイン",
|
||||||
|
@ -28,7 +28,6 @@
|
||||||
"close_all_files": "全てのファイルを閉じる",
|
"close_all_files": "全てのファイルを閉じる",
|
||||||
"save_all_files": "全てを保存",
|
"save_all_files": "全てを保存",
|
||||||
"format": "フォーマット",
|
"format": "フォーマット",
|
||||||
"goget": "go get",
|
|
||||||
"goinstall": "go install",
|
"goinstall": "go install",
|
||||||
"build": "ビルド",
|
"build": "ビルド",
|
||||||
"build_n_run": "ビルド実行",
|
"build_n_run": "ビルド実行",
|
||||||
|
@ -102,9 +101,6 @@
|
||||||
"start-install": "[go install] 開始",
|
"start-install": "[go install] 開始",
|
||||||
"install-succ": "[go install] 成功",
|
"install-succ": "[go install] 成功",
|
||||||
"install-error": "[go install] 失敗",
|
"install-error": "[go install] 失敗",
|
||||||
"start-get": "[go get] 開始",
|
|
||||||
"get-succ": "[go get] 成功",
|
|
||||||
"get-error": "[go get] 失敗",
|
|
||||||
"check_version": "更新をチェック中",
|
"check_version": "更新をチェック中",
|
||||||
"new_version_available": "新しいバージョンがあります",
|
"new_version_available": "新しいバージョンがあります",
|
||||||
"go_env": "Go",
|
"go_env": "Go",
|
||||||
|
@ -118,18 +114,17 @@
|
||||||
"team": "チーム",
|
"team": "チーム",
|
||||||
"sing_up_error": "登録に失敗しました",
|
"sing_up_error": "登録に失敗しました",
|
||||||
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
|
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
|
||||||
"invalid_email": "無効な電子メール",
|
|
||||||
"password_no_match": "一貫性のないパスワード入力",
|
"password_no_match": "一貫性のないパスワード入力",
|
||||||
"discard": "あきらめる",
|
"discard": "あきらめる",
|
||||||
"close": "クローズ",
|
"close": "クローズ",
|
||||||
"close_other": "閉じるその他",
|
"close_other": "閉じるその他",
|
||||||
"clear": "空の",
|
"clear": "空の",
|
||||||
"perference": "偏好设定",
|
"preference": "環境設定",
|
||||||
"appearence": "エクステリア",
|
"appearence": "エクステリア",
|
||||||
"gotool": "Go ツール",
|
"gotool": "Go ツール",
|
||||||
"user": "ユーザー",
|
"user": "ユーザー",
|
||||||
"font": "フォント",
|
"font": "フォント",
|
||||||
"font_size": "フォント·サイズ",
|
"font_size": "フォントサイズ",
|
||||||
"line_height": "行の高さ",
|
"line_height": "行の高さ",
|
||||||
"go_format": "Go フォーマット",
|
"go_format": "Go フォーマット",
|
||||||
"locale": "ロケール",
|
"locale": "ロケール",
|
||||||
|
@ -137,9 +132,8 @@
|
||||||
"clearOutput": "空の出力",
|
"clearOutput": "空の出力",
|
||||||
"export": "輸出",
|
"export": "輸出",
|
||||||
"refresh": "リフレッシュ",
|
"refresh": "リフレッシュ",
|
||||||
"import": "インポート",
|
|
||||||
"theme": "テーマ",
|
"theme": "テーマ",
|
||||||
"tab_size": "Tab·サイズ",
|
"tab_size": "Tab サイズ",
|
||||||
"copy_file_path": "ファイルパスをコピー",
|
"copy_file_path": "ファイルパスをコピー",
|
||||||
"file_tree": "ファイルツリー",
|
"file_tree": "ファイルツリー",
|
||||||
"select": "選択する",
|
"select": "選択する",
|
||||||
|
@ -148,7 +142,7 @@
|
||||||
"edit": "編集",
|
"edit": "編集",
|
||||||
"undo": "元に戻す",
|
"undo": "元に戻す",
|
||||||
"redo": "やり直し",
|
"redo": "やり直し",
|
||||||
"cut": "Cut",
|
"cut": "切り取り",
|
||||||
"copy": "コピー",
|
"copy": "コピー",
|
||||||
"paste": "貼り付け",
|
"paste": "貼り付け",
|
||||||
"select_all": "すべて選択",
|
"select_all": "すべて選択",
|
||||||
|
@ -156,20 +150,22 @@
|
||||||
"source": "ソース",
|
"source": "ソース",
|
||||||
"toggle_comment": "トグルコメント",
|
"toggle_comment": "トグルコメント",
|
||||||
"find_in_files": "ファイルから検索",
|
"find_in_files": "ファイルから検索",
|
||||||
"email": "Eメール",
|
|
||||||
"no_empty": "空ではありません",
|
"no_empty": "空ではありません",
|
||||||
"change_avatar": "アバターの変更は行く",
|
|
||||||
"open": "オープン",
|
"open": "オープン",
|
||||||
"pricing": "价格",
|
|
||||||
"search_no_match": "一致するファイルが見つかりませんでした。",
|
"search_no_match": "一致するファイルが見つかりませんでした。",
|
||||||
"outline": "アウトライン",
|
"outline": "アウトライン",
|
||||||
"govet": "go vet",
|
"govet": "go vet",
|
||||||
"start-vet": "START [go vet]",
|
"start-vet": "[go vet] 開始",
|
||||||
"vet-succ": "[go vet] SUCCESS",
|
"vet-succ": "[go vet] 成功",
|
||||||
"vet-error": "[go vet] ERROR",
|
"vet-error": "[go vet] 失敗",
|
||||||
"restore_outline": "アウトラインを復元",
|
"restore_outline": "アウトラインを復元",
|
||||||
"share": "シェア",
|
"share": "シェア",
|
||||||
"url": "リンク",
|
"url": "リンク",
|
||||||
"short_url": "ショートリンク",
|
"embeded": "埋め込む",
|
||||||
"embeded": "埋め込む"
|
"terms": "利用規約",
|
||||||
|
"download": "ダウンロード",
|
||||||
|
"decompress": "解凍する",
|
||||||
|
"keymap": "キーマップ",
|
||||||
|
"resize": "サイズ変更",
|
||||||
|
"sponsor": "スポンサー"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
{
|
||||||
|
"colon": ":",
|
||||||
|
"wide": "Wide",
|
||||||
|
"wide_title": "언제 어디서나 골란 놀기",
|
||||||
|
"cancel": "취소",
|
||||||
|
"file": "문서",
|
||||||
|
"login": "로그인",
|
||||||
|
"username": "아이디",
|
||||||
|
"current_user": "현제 접속자 ",
|
||||||
|
"current_session": "현제 세션",
|
||||||
|
"password": "비밀번호",
|
||||||
|
"login_error": "로그인 실패",
|
||||||
|
"run": "실행",
|
||||||
|
"debug": "디버깅",
|
||||||
|
"help": "도움말",
|
||||||
|
"check_update": "업데이트 확인",
|
||||||
|
"issues": "문제",
|
||||||
|
"wide_doc": "Wide 문서",
|
||||||
|
"about": "about",
|
||||||
|
"start_page": "시작 페이지",
|
||||||
|
"create_file": "새문서",
|
||||||
|
"create": "새로만들기",
|
||||||
|
"create_dir": "새폴더",
|
||||||
|
"delete": "삭제",
|
||||||
|
"rename": "이름변경",
|
||||||
|
"save": "저장",
|
||||||
|
"exit": "끝내기",
|
||||||
|
"close_all_files": "모두 닫기",
|
||||||
|
"save_all_files": "일괄저장",
|
||||||
|
"format": "포멧",
|
||||||
|
"goinstall": "go install",
|
||||||
|
"build": "빌드",
|
||||||
|
"build_n_run": "빌드후실행",
|
||||||
|
"editor": "편집기",
|
||||||
|
"max_editor": "최대화",
|
||||||
|
"restore_editor": "복구",
|
||||||
|
"unread_notification": "읽지않은 공지",
|
||||||
|
"notification_2": "[gocode] 를 찾지 못하였습니다. 자동완성기능이 동작하지 않습니다. ",
|
||||||
|
"notification_3": "[ide_stub] 를 찾지 못하였습니다. 찾기 기능이 동작하지 않습니다. ",
|
||||||
|
"notification_4": "서버 오류",
|
||||||
|
"goto_line": "라인이동",
|
||||||
|
"goto_file": "문서오픈",
|
||||||
|
"go": "이동",
|
||||||
|
"tip": "팁",
|
||||||
|
"confirm": "확인",
|
||||||
|
"stop": "정지",
|
||||||
|
"output": "출력",
|
||||||
|
"search": "검색",
|
||||||
|
"notification": "알림",
|
||||||
|
"min": "최소화",
|
||||||
|
"restore_side": "좌측창 복구",
|
||||||
|
"search_text": "문서검색",
|
||||||
|
"find": "검색",
|
||||||
|
"find_next": "다음검색",
|
||||||
|
"find_previous": "이전검색",
|
||||||
|
"replace": "바꾸기",
|
||||||
|
"replace_all": "전부바꾸기",
|
||||||
|
"restore_bottom": "아래창 복구",
|
||||||
|
"file_format": "확장자",
|
||||||
|
"keyword": "키워드",
|
||||||
|
"user_guide": "이용가이드",
|
||||||
|
"dev_guide": "개발 가이드",
|
||||||
|
"keyboard_shortcuts": "단축키",
|
||||||
|
"ver": "버전",
|
||||||
|
"current_ver": "현재버전",
|
||||||
|
"dev_team": "개발단체",
|
||||||
|
"donate": "기부",
|
||||||
|
"confirm_save": "정장했는지 확인해 주세요. ",
|
||||||
|
"workspace": "작업창",
|
||||||
|
"project_address": "항목주소",
|
||||||
|
"community": "커뮤니티",
|
||||||
|
"autocomplete": "자동완성",
|
||||||
|
"jump_to_decl": "알림으로 이동",
|
||||||
|
"show_expr_info": "Expression 보기",
|
||||||
|
"find_usages": "사용법찾기",
|
||||||
|
"delete_line": "현재 줄 삭제",
|
||||||
|
"copy_lines_up": "위로 복사",
|
||||||
|
"copy_lines_down": "아래로 복사",
|
||||||
|
"move_lines_up": "위로이동",
|
||||||
|
"move_lines_down": "아래로 이동",
|
||||||
|
"save_editor_file": "현재 편집하고있던 문서 저장",
|
||||||
|
"save_all_editors_files": "일괄저장",
|
||||||
|
"close_editor": "편집창 닫기",
|
||||||
|
"full_screen": "편집기 전체화면",
|
||||||
|
"auto_indent": "자동 정렬",
|
||||||
|
"indent": "안쪽이동",
|
||||||
|
"unindent": "밖으로 이동",
|
||||||
|
"focus": "포커스",
|
||||||
|
"switch_tab": "편집기편집/창편집 tab",
|
||||||
|
"focus_editor": "편집창으로 포커스",
|
||||||
|
"focus_file_tree": "프로젝트트리로 포커스이동",
|
||||||
|
"focus_output": "output 으로 포커스 이동",
|
||||||
|
"focus_search": "검색창으로 포커스 이동",
|
||||||
|
"focus_notification": "알림창으로 포커스 이동",
|
||||||
|
"start-build": "시작 [go build]",
|
||||||
|
"build-succ": "[go build] 성공",
|
||||||
|
"build-error": "[go build] 실패",
|
||||||
|
"start-test": "시작 [go test]",
|
||||||
|
"test-succ": "[go test] 성공",
|
||||||
|
"test-error": "[go test] 실패",
|
||||||
|
"start-install": "시작 [go install]",
|
||||||
|
"install-succ": "[go install] 성공",
|
||||||
|
"install-error": "[go install] 실패",
|
||||||
|
"check_version": "최신버전검색중",
|
||||||
|
"new_version_available": "최신업데이트 사용 가능",
|
||||||
|
"go_env": "Go 환경",
|
||||||
|
"os": "운영체제",
|
||||||
|
"project": "항목",
|
||||||
|
"license": "라이센스",
|
||||||
|
"credits": "감사합니다.",
|
||||||
|
"uptodate": "최신버전입니다.",
|
||||||
|
"test": "테스트",
|
||||||
|
"sign_up": "회원가입",
|
||||||
|
"team": "단체",
|
||||||
|
"sing_up_error": "가입실패",
|
||||||
|
"user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,",
|
||||||
|
"password_no_match": "비밀번호 오류",
|
||||||
|
"discard": "취소",
|
||||||
|
"close": "닫기",
|
||||||
|
"close_other": "현재창 남기고 닫기",
|
||||||
|
"clear": "청소",
|
||||||
|
"preference": "설정",
|
||||||
|
"appearence": "외관",
|
||||||
|
"gotool": "Go 도구",
|
||||||
|
"user": "유저",
|
||||||
|
"font": "폰트",
|
||||||
|
"font_size": "폰트크기",
|
||||||
|
"line_height": "줄간격",
|
||||||
|
"go_format": "Go 포멧",
|
||||||
|
"locale": "언어설정",
|
||||||
|
"apply": "적용",
|
||||||
|
"clearOutput": "ouput 클리어",
|
||||||
|
"export": "내보내기",
|
||||||
|
"refresh": "새로고침",
|
||||||
|
"theme": "주제",
|
||||||
|
"tab_size": "Tab 크기",
|
||||||
|
"copy_file_path": "경로복사",
|
||||||
|
"file_tree": "트리",
|
||||||
|
"select": "선택",
|
||||||
|
"expand": "확장",
|
||||||
|
"collapse": "축소",
|
||||||
|
"edit": "편집",
|
||||||
|
"undo": "취소",
|
||||||
|
"redo": "복원",
|
||||||
|
"cut": "잘라내기",
|
||||||
|
"copy": "복사",
|
||||||
|
"paste": "붙여넣기",
|
||||||
|
"select_all": "전체선택",
|
||||||
|
"select_identifier": "표식선택",
|
||||||
|
"source": "소스",
|
||||||
|
"toggle_comment": "주석",
|
||||||
|
"find_in_files": "문서에서 찾기",
|
||||||
|
"no_empty": "값을 입력해 주세요.",
|
||||||
|
"open": "열기",
|
||||||
|
"search_no_match": "해당 문서를 찾지 못하였습니다.",
|
||||||
|
"outline": "주제",
|
||||||
|
"govet": "go vet",
|
||||||
|
"start-vet": "시작 [go vet]",
|
||||||
|
"vet-succ": "[go vet] 성공",
|
||||||
|
"vet-error": "[go vet] 실패",
|
||||||
|
"restore_outline": "주제복구",
|
||||||
|
"share": "공유",
|
||||||
|
"url": "하이퍼링크",
|
||||||
|
"embeded": "삽입",
|
||||||
|
"terms": "사용계약",
|
||||||
|
"download": "다운로드",
|
||||||
|
"decompress": "압축풀기",
|
||||||
|
"keymap": "단축키",
|
||||||
|
"resize": "크기조절",
|
||||||
|
"sponsor": "후원사"
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -17,16 +17,15 @@ package i18n
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/gulu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// Locale.
|
// Locale.
|
||||||
type locale struct {
|
type locale struct {
|
||||||
|
@ -44,6 +43,10 @@ func Load() {
|
||||||
names, _ := f.Readdirnames(-1)
|
names, _ := f.Readdirnames(-1)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
|
if len(Locales) == len(names)-1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if !strings.HasSuffix(name, ".json") {
|
if !strings.HasSuffix(name, ".json") {
|
||||||
continue
|
continue
|
||||||
|
@ -55,7 +58,7 @@ func Load() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func load(localeStr string) {
|
func load(localeStr string) {
|
||||||
bytes, err := ioutil.ReadFile("i18n/" + localeStr + ".json")
|
bytes, err := os.ReadFile("i18n/" + localeStr + ".json")
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"colon": ":",
|
"colon": ":",
|
||||||
"wide": "Wide",
|
"wide": "Wide",
|
||||||
"wide_title": "团队开发,随时随地",
|
"wide_title": "随时随地玩 golang",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"file": "文件",
|
"file": "文件",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
|
@ -28,7 +28,6 @@
|
||||||
"close_all_files": "关闭所有文件",
|
"close_all_files": "关闭所有文件",
|
||||||
"save_all_files": "保存所有文件",
|
"save_all_files": "保存所有文件",
|
||||||
"format": "格式化",
|
"format": "格式化",
|
||||||
"goget": "go get",
|
|
||||||
"goinstall": "go install",
|
"goinstall": "go install",
|
||||||
"build": "构建",
|
"build": "构建",
|
||||||
"build_n_run": "构建并运行",
|
"build_n_run": "构建并运行",
|
||||||
|
@ -102,9 +101,6 @@
|
||||||
"start-install": "开始 [go install]",
|
"start-install": "开始 [go install]",
|
||||||
"install-succ": "[go install] 成功",
|
"install-succ": "[go install] 成功",
|
||||||
"install-error": "[go install] 失败",
|
"install-error": "[go install] 失败",
|
||||||
"start-get": "开始 [go get]",
|
|
||||||
"get-succ": "[go get] 成功",
|
|
||||||
"get-error": "[go get] 失败",
|
|
||||||
"check_version": "正在检查更新",
|
"check_version": "正在检查更新",
|
||||||
"new_version_available": "新版本可用",
|
"new_version_available": "新版本可用",
|
||||||
"go_env": "Go 环境",
|
"go_env": "Go 环境",
|
||||||
|
@ -118,13 +114,12 @@
|
||||||
"team": "团队",
|
"team": "团队",
|
||||||
"sing_up_error": "注册失败",
|
"sing_up_error": "注册失败",
|
||||||
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成,长度为16",
|
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成,长度为16",
|
||||||
"invalid_email": "无效的电子邮件",
|
|
||||||
"password_no_match": "密码输入不一致",
|
"password_no_match": "密码输入不一致",
|
||||||
"discard": "放弃",
|
"discard": "放弃",
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"close_other": "关闭其它",
|
"close_other": "关闭其它",
|
||||||
"clear": "清空",
|
"clear": "清空",
|
||||||
"perference": "偏好设定",
|
"preference": "偏好设定",
|
||||||
"appearence": "外观",
|
"appearence": "外观",
|
||||||
"gotool": "Go 工具",
|
"gotool": "Go 工具",
|
||||||
"user": "用户",
|
"user": "用户",
|
||||||
|
@ -137,7 +132,6 @@
|
||||||
"clearOutput": "清空输出",
|
"clearOutput": "清空输出",
|
||||||
"export": "导出",
|
"export": "导出",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"import": "导入",
|
|
||||||
"theme": "主题",
|
"theme": "主题",
|
||||||
"tab_size": "Tab 大小",
|
"tab_size": "Tab 大小",
|
||||||
"copy_file_path": "复制文件路径",
|
"copy_file_path": "复制文件路径",
|
||||||
|
@ -156,20 +150,22 @@
|
||||||
"source": "源码",
|
"source": "源码",
|
||||||
"toggle_comment": "注释",
|
"toggle_comment": "注释",
|
||||||
"find_in_files": "在文件中查找",
|
"find_in_files": "在文件中查找",
|
||||||
"email": "电子邮件",
|
|
||||||
"no_empty": "不能为空",
|
"no_empty": "不能为空",
|
||||||
"change_avatar": "头像修改请到",
|
|
||||||
"open": "打开",
|
"open": "打开",
|
||||||
"pricing": "价格",
|
|
||||||
"search_no_match": "没有发现匹配的文件。",
|
"search_no_match": "没有发现匹配的文件。",
|
||||||
"outline": "大纲",
|
"outline": "大纲",
|
||||||
"govet": "go vet",
|
"govet": "go vet",
|
||||||
"start-vet": "START [go vet]",
|
"start-vet": "开始 [go vet]",
|
||||||
"vet-succ": "[go vet] SUCCESS",
|
"vet-succ": "[go vet] 成功",
|
||||||
"vet-error": "[go vet] ERROR",
|
"vet-error": "[go vet] 失败",
|
||||||
"restore_outline": "恢复大纲",
|
"restore_outline": "恢复大纲",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"url": "链接",
|
"url": "链接",
|
||||||
"short_url": "短链接",
|
"embeded": "嵌入",
|
||||||
"embeded": "嵌入"
|
"terms": "使用条款",
|
||||||
|
"download": "下载",
|
||||||
|
"decompress": "解压缩",
|
||||||
|
"keymap": "快捷键",
|
||||||
|
"resize": "调整大小",
|
||||||
|
"sponsor": "赞助"
|
||||||
}
|
}
|
162
i18n/zh_TW.json
162
i18n/zh_TW.json
|
@ -1,110 +1,106 @@
|
||||||
{
|
{
|
||||||
"colon": ":",
|
"colon": ":",
|
||||||
"wide": "Wide",
|
"wide": "Wide",
|
||||||
"wide_title": "團隊開發,隨時隨地",
|
"wide_title": "隨時隨地玩 golang",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"file": "文件檔",
|
"file": "檔案",
|
||||||
"login": "登錄",
|
"login": "登入",
|
||||||
"username": "用户名字",
|
"username": "使用者",
|
||||||
"current_user": "當前用户名",
|
"current_user": "當前使用者",
|
||||||
"current_session": "當前會話",
|
"current_session": "當前會話",
|
||||||
"password": "密碼",
|
"password": "密碼",
|
||||||
"login_error": "登錄失败",
|
"login_error": "登入失敗",
|
||||||
"run": "執行",
|
"run": "執行",
|
||||||
"debug": "除蟲",
|
"debug": "Debug",
|
||||||
"help": "說明書",
|
"help": "說明書",
|
||||||
"check_update": "程式更新?",
|
"check_update": "檢查更新?",
|
||||||
"issues": "問題",
|
"issues": "問題",
|
||||||
"wide_doc": "Wide 說明文件",
|
"wide_doc": "Wide 說明",
|
||||||
"about": "關於",
|
"about": "關於",
|
||||||
"start_page": "起始頁",
|
"start_page": "開始頁面",
|
||||||
"create_file": "建立文件",
|
"create_file": "開新檔案",
|
||||||
"create": "建立",
|
"create": "新建",
|
||||||
"create_dir": "建立目錄",
|
"create_dir": "新增資料夾",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"rename": "重命名",
|
"rename": "重新命名",
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
"exit": "退出",
|
"exit": "離開",
|
||||||
"close_all_files": "關閉所有文件",
|
"close_all_files": "關閉所有檔案",
|
||||||
"save_all_files": "保存所有文件",
|
"save_all_files": "儲存所有檔案",
|
||||||
"format": "格式化",
|
"format": "格式化",
|
||||||
"goget": "go get",
|
|
||||||
"goinstall": "go install",
|
"goinstall": "go install",
|
||||||
"build": "編譯軟件",
|
"build": "編譯",
|
||||||
"build_n_run": "編譯軟件並執行",
|
"build_n_run": "編譯並執行",
|
||||||
"editor": "編輯器",
|
"editor": "編輯器",
|
||||||
"max_editor": "編輯器窗口最大化",
|
"max_editor": "編輯器最大化",
|
||||||
"restore_editor": "編輯器窗口還原",
|
"restore_editor": "編輯器還原",
|
||||||
"unread_notification": "未讀通知",
|
"unread_notification": "未讀通知",
|
||||||
"notification_2": "没有檢查到 gocode,這將會導致 [自動完成] 失效",
|
"notification_2": "没有檢查到 gocode,這將會導致「自動完成」失效",
|
||||||
"notification_3": "没有檢查到 ide_stub,這將會導致 [跳轉到聲明]、[查找使用] 失效",
|
"notification_3": "没有檢查到 ide_stub,這將會導致「跳轉到聲明」、「查找使用」失效",
|
||||||
"notification_4": "服務器內部錯誤",
|
"notification_4": "伺服器內部錯誤",
|
||||||
"goto_line": "跳轉到行",
|
"goto_line": "跳轉到行",
|
||||||
"goto_file": "打開文件",
|
"goto_file": "開啟舊檔",
|
||||||
"go": "跳到",
|
"go": "跳到",
|
||||||
"tip": "提示",
|
"tip": "提示",
|
||||||
"confirm": "確定",
|
"confirm": "確定",
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
"output": "输出",
|
"output": "輸出",
|
||||||
"search": "搜索",
|
"search": "搜尋",
|
||||||
"notification": "通知",
|
"notification": "通知",
|
||||||
"min": "最小化",
|
"min": "縮到最小",
|
||||||
"restore_side": "左側窗口還原",
|
"restore_side": "左側視窗還原",
|
||||||
"search_text": "尋找文本",
|
"search_text": "尋找",
|
||||||
"find": "尋找",
|
"find": "尋找",
|
||||||
"find_next": "尋找下一個",
|
"find_next": "尋找下一個",
|
||||||
"find_previous": "尋找上一個",
|
"find_previous": "尋找上一個",
|
||||||
"replace": "替換",
|
"replace": "取代",
|
||||||
"replace_all": "替換全部",
|
"replace_all": "取代全部",
|
||||||
"restore_bottom": "底部窗口還原",
|
"restore_bottom": "底部視窗還原",
|
||||||
"file_format": "文件格式",
|
"file_format": "文件格式",
|
||||||
"keyword": "關鍵字",
|
"keyword": "關鍵字",
|
||||||
"user_guide": "用户指南",
|
"user_guide": "使用者說明文件",
|
||||||
"dev_guide": "開發指南",
|
"dev_guide": "開發說明文件",
|
||||||
"keyboard_shortcuts": "鍵盤快捷鍵",
|
"keyboard_shortcuts": "鍵盤快捷鍵",
|
||||||
"ver": "版本",
|
"ver": "版本",
|
||||||
"current_ver": "當前版本",
|
"current_ver": "當前版本",
|
||||||
"dev_team": "開發團隊",
|
"dev_team": "開發團隊",
|
||||||
"donate_us": "愛心捐赠",
|
"donate_us": "愛心捐贈",
|
||||||
"confirm_save": "請確認所有文件已保存",
|
"confirm_save": "請確認所有檔案都已儲存",
|
||||||
"workspace": "工作空間",
|
"workspace": "工作空間",
|
||||||
"project_address": "項目地址",
|
"project_address": "項目地址",
|
||||||
"community": "社區",
|
"community": "社區",
|
||||||
"autocomplete": "自動補全",
|
"autocomplete": "自動完成",
|
||||||
"jump_to_decl": "跳轉到聲明",
|
"jump_to_decl": "跳轉到聲明",
|
||||||
"show_expr_info": "查看表達式信息",
|
"show_expr_info": "查看表達式信息",
|
||||||
"find_usages": "尋找使用",
|
"find_usages": "尋找使用",
|
||||||
"delete_line": "删除當前行",
|
"delete_line": "删除當前行",
|
||||||
"copy_lines_up": "複製到上方",
|
"copy_lines_up": "複製到上一行",
|
||||||
"copy_lines_down": "複製到下方",
|
"copy_lines_down": "複製到下一行",
|
||||||
"move_lines_up": "移動到上方",
|
"move_lines_up": "移動到上一行",
|
||||||
"move_lines_down": "移動到下方",
|
"move_lines_down": "移動到下一行",
|
||||||
"save_editor_file": "保存當前編輯器文件",
|
"save_editor_file": "儲存當前編輯檔案",
|
||||||
"save_all_editors_files": "保存所有編輯器文件",
|
"save_all_editors_files": "儲存所有檔案",
|
||||||
"close_editor": "關閉當前編輯器",
|
"close_editor": "關閉當前編輯器",
|
||||||
"full_screen": "編輯器全螢幕",
|
"full_screen": "全螢幕",
|
||||||
"auto_indent": "自動縮進",
|
"auto_indent": "自動縮進",
|
||||||
"indent": "縮進",
|
"indent": "縮進",
|
||||||
"unindent": "縮進還原",
|
"unindent": "縮進還原",
|
||||||
"focus": "焦點",
|
"focus": "焦點",
|
||||||
"switch_tab": "切換編輯器/窗口组 tab",
|
"switch_tab": "切換編輯器/視窗组 tab",
|
||||||
"focus_editor": "焦點切換到編輯器",
|
"focus_editor": "切換至編輯器",
|
||||||
"focus_file_tree": "焦點切換到文件樹",
|
"focus_file_tree": "切換至檔案樹",
|
||||||
"focus_output": "焦點切換到输出窗口",
|
"focus_output": "切換至输出視窗",
|
||||||
"focus_search": "焦點切換到搜索窗口",
|
"focus_search": "切換至搜索視窗",
|
||||||
"focus_notification": "焦點切換到通知窗口",
|
"focus_notification": "切換至通知視窗",
|
||||||
"start-build": "開始 [go build]",
|
"start-build": "開始 [go build]",
|
||||||
"build-succ": "[go build] 成功",
|
"build-succ": "[go build] 成功",
|
||||||
"build-error": "[go build] 失败",
|
"build-error": "[go build] 失敗",
|
||||||
"start-test": "開始 [go test]",
|
"start-test": "開始 [go test]",
|
||||||
"test-succ": "[go test] 成功",
|
"test-succ": "[go test] 成功",
|
||||||
"test-error": "[go test] 失败",
|
"test-error": "[go test] 失敗",
|
||||||
"start-install": "開始 [go install]",
|
"start-install": "開始 [go install]",
|
||||||
"install-succ": "[go install] 成功",
|
"install-succ": "[go install] 成功",
|
||||||
"install-error": "[go install] 失败",
|
"install-error": "[go install] 失敗",
|
||||||
"start-get": "開始 [go get]",
|
|
||||||
"get-succ": "[go get] 成功",
|
|
||||||
"get-error": "[go get] 失败",
|
|
||||||
"check_version": "正在檢查更新",
|
"check_version": "正在檢查更新",
|
||||||
"new_version_available": "可用新版本",
|
"new_version_available": "可用新版本",
|
||||||
"go_env": "Go 環境",
|
"go_env": "Go 環境",
|
||||||
|
@ -117,18 +113,17 @@
|
||||||
"sign_up": "註冊",
|
"sign_up": "註冊",
|
||||||
"team": "團隊",
|
"team": "團隊",
|
||||||
"sing_up_error": "註冊失敗",
|
"sing_up_error": "註冊失敗",
|
||||||
"user_name_ruler": "用戶名只能由az, AZ, 0-9, _ 組成,長度為16",
|
"user_name_ruler": "帳號只能由 az, AZ, 0-9, _ 組成,長度為16",
|
||||||
"invalid_email": "無效的電子郵件",
|
|
||||||
"password_no_match": "密碼輸入不一致",
|
"password_no_match": "密碼輸入不一致",
|
||||||
"discard": "放棄",
|
"discard": "捨棄",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"close_other": "關閉其它",
|
"close_other": "關閉其它",
|
||||||
"clear": "清空",
|
"clear": "清空",
|
||||||
"perference": "偏好設定",
|
"preference": "偏好設定",
|
||||||
"appearence": "外觀",
|
"appearence": "外觀",
|
||||||
"gotool": "Go 工具",
|
"gotool": "Go 工具",
|
||||||
"user": "用戶",
|
"user": "使用者",
|
||||||
"font": "字形",
|
"font": "字體",
|
||||||
"font_size": "字體大小",
|
"font_size": "字體大小",
|
||||||
"line_height": "行高",
|
"line_height": "行高",
|
||||||
"go_format": "Go 格式化",
|
"go_format": "Go 格式化",
|
||||||
|
@ -137,39 +132,40 @@
|
||||||
"clearOutput": "清空輸出",
|
"clearOutput": "清空輸出",
|
||||||
"export": "導出",
|
"export": "導出",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"import": "導入",
|
|
||||||
"theme": "主題",
|
"theme": "主題",
|
||||||
"tab_size": "Tab 大小",
|
"tab_size": "Tab 大小",
|
||||||
"copy_file_path": "複製文件路徑",
|
"copy_file_path": "複製檔案位置",
|
||||||
"file_tree": "文件樹",
|
"file_tree": "文件樹",
|
||||||
"select": "選擇",
|
"select": "選擇",
|
||||||
"expand": "展開",
|
"expand": "展開",
|
||||||
"collapse": "收起",
|
"collapse": "收起",
|
||||||
"edit": "編輯",
|
"edit": "編輯",
|
||||||
"undo": "撤消",
|
"undo": "復原",
|
||||||
"redo": "重做",
|
"redo": "回復",
|
||||||
"cut": "剪切",
|
"cut": "剪下",
|
||||||
"copy": "複製",
|
"copy": "複製",
|
||||||
"paste": "粘貼",
|
"paste": "天上",
|
||||||
"select_all": "全選",
|
"select_all": "全選",
|
||||||
"select_identifier": "選擇標識符",
|
"select_identifier": "選擇標識符",
|
||||||
"source": "源代碼",
|
"source": "原始碼",
|
||||||
"toggle_comment": "註釋",
|
"toggle_comment": "註解",
|
||||||
"find_in_files": "在文件中查找",
|
"find_in_files": "在文件中尋找",
|
||||||
"email": "電子郵件",
|
|
||||||
"no_empty": "不能為空",
|
"no_empty": "不能為空",
|
||||||
"change_avatar": "頭像修改請到",
|
"open": "開啟",
|
||||||
"open": "打開",
|
|
||||||
"pricing": "價格",
|
|
||||||
"search_no_match": "沒有發現匹配的文件。",
|
"search_no_match": "沒有發現匹配的文件。",
|
||||||
"outline": "大綱",
|
"outline": "大綱",
|
||||||
"govet": "go vet",
|
"govet": "go vet",
|
||||||
"start-vet": "START [go vet]",
|
"start-vet": "開始 [go vet]",
|
||||||
"vet-succ": "[go vet] SUCCESS",
|
"vet-succ": "[go vet] 成功",
|
||||||
"vet-error": "[go vet] ERROR",
|
"vet-error": "[go vet] 失敗",
|
||||||
"restore_outline": "恢復大綱",
|
"restore_outline": "恢復大綱",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"url": "鏈接",
|
"url": "連結",
|
||||||
"short_url": "短鏈接",
|
"embeded": "嵌入",
|
||||||
"embeded": "嵌入"
|
"terms": "使用條款",
|
||||||
|
"download": "下載",
|
||||||
|
"decompress": "解壓縮",
|
||||||
|
"keymap": "快速鍵",
|
||||||
|
"resize": "調整大小",
|
||||||
|
"sponsor": "贊助"
|
||||||
}
|
}
|
217
log/logs.go
217
log/logs.go
|
@ -1,217 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package log includes logging related manipulations.
|
|
||||||
//
|
|
||||||
// log.SetLevel("debug")
|
|
||||||
// logger := log.NewLogger(os.Stdout)
|
|
||||||
//
|
|
||||||
// logger.Trace("trace message)
|
|
||||||
// logger.Debug("debug message")
|
|
||||||
// logger.Info("info message")
|
|
||||||
// logger.Warn("warning message")
|
|
||||||
// logger.Error("error message")
|
|
||||||
//
|
|
||||||
// logger.Errorf("formatted %s message", "error")
|
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
stdlog "log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logging level.
|
|
||||||
const (
|
|
||||||
Off = iota
|
|
||||||
Trace
|
|
||||||
Debug
|
|
||||||
Info
|
|
||||||
Warn
|
|
||||||
Error
|
|
||||||
)
|
|
||||||
|
|
||||||
// all loggers.
|
|
||||||
var loggers []*Logger
|
|
||||||
|
|
||||||
// the global default logging level, it will be used for creating logger.
|
|
||||||
var logLevel = Debug
|
|
||||||
|
|
||||||
// Logger represents a simple logger with level.
|
|
||||||
// The underlying logger is the standard Go logging "log".
|
|
||||||
type Logger struct {
|
|
||||||
level int
|
|
||||||
logger *stdlog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogger creates a logger.
|
|
||||||
func NewLogger(out io.Writer) *Logger {
|
|
||||||
ret := &Logger{level: logLevel, logger: stdlog.New(out, "", stdlog.Ldate|stdlog.Ltime|stdlog.Lshortfile)}
|
|
||||||
|
|
||||||
loggers = append(loggers, ret)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the logging level of all loggers.
|
|
||||||
func SetLevel(level string) {
|
|
||||||
logLevel = getLevel(level)
|
|
||||||
|
|
||||||
for _, l := range loggers {
|
|
||||||
l.SetLevel(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLevel gets logging level int value corresponding to the specified level.
|
|
||||||
func getLevel(level string) int {
|
|
||||||
level = strings.ToLower(level)
|
|
||||||
|
|
||||||
switch level {
|
|
||||||
case "off":
|
|
||||||
return Off
|
|
||||||
case "trace":
|
|
||||||
return Trace
|
|
||||||
case "debug":
|
|
||||||
return Debug
|
|
||||||
case "info":
|
|
||||||
return Info
|
|
||||||
case "warn":
|
|
||||||
return Warn
|
|
||||||
case "error":
|
|
||||||
return Error
|
|
||||||
default:
|
|
||||||
return Info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the logging level of a logger.
|
|
||||||
func (l *Logger) SetLevel(level string) {
|
|
||||||
l.level = getLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTraceEnabled determines whether the trace level is enabled.
|
|
||||||
func (l *Logger) IsTraceEnabled() bool {
|
|
||||||
return l.level <= Trace
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDebugEnabled determines whether the debug level is enabled.
|
|
||||||
func (l *Logger) IsDebugEnabled() bool {
|
|
||||||
return l.level <= Debug
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWarnEnabled determines whether the debug level is enabled.
|
|
||||||
func (l *Logger) IsWarnEnabled() bool {
|
|
||||||
return l.level <= Warn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace prints trace level message.
|
|
||||||
func (l *Logger) Trace(v ...interface{}) {
|
|
||||||
if Trace < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("T ")
|
|
||||||
l.logger.Output(2, fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tracef prints trace level message with format.
|
|
||||||
func (l *Logger) Tracef(format string, v ...interface{}) {
|
|
||||||
if Trace < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("T ")
|
|
||||||
l.logger.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug prints debug level message.
|
|
||||||
func (l *Logger) Debug(v ...interface{}) {
|
|
||||||
if Debug < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("D ")
|
|
||||||
l.logger.Output(2, fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf prints debug level message with format.
|
|
||||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
|
||||||
if Debug < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("D ")
|
|
||||||
l.logger.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info prints info level message.
|
|
||||||
func (l *Logger) Info(v ...interface{}) {
|
|
||||||
if Info < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("I ")
|
|
||||||
l.logger.Output(2, fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof prints info level message with format.
|
|
||||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
|
||||||
if Info < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("I ")
|
|
||||||
l.logger.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn prints warning level message.
|
|
||||||
func (l *Logger) Warn(v ...interface{}) {
|
|
||||||
if Warn < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("W ")
|
|
||||||
l.logger.Output(2, fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn prints warning level message with format.
|
|
||||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
|
||||||
if Warn < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("W ")
|
|
||||||
l.logger.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error prints error level message.
|
|
||||||
func (l *Logger) Error(v ...interface{}) {
|
|
||||||
if Error < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("E ")
|
|
||||||
l.logger.Output(2, fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf prints error level message with format.
|
|
||||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
|
||||||
if Error < l.level {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.logger.SetPrefix("E ")
|
|
||||||
l.logger.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
169
log/logs_test.go
169
log/logs_test.go
|
@ -1,169 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger.
|
|
||||||
var logger = NewLogger(os.Stdout)
|
|
||||||
|
|
||||||
func TestSetLevel(t *testing.T) {
|
|
||||||
SetLevel("trace")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrace(t *testing.T) {
|
|
||||||
logger.SetLevel("trace")
|
|
||||||
logger.Trace("trace")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Trace("trace")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTracef(t *testing.T) {
|
|
||||||
logger.SetLevel("trace")
|
|
||||||
logger.Tracef("tracef")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Tracef("tracef")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebug(t *testing.T) {
|
|
||||||
logger.SetLevel("debug")
|
|
||||||
logger.Debug("debug")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Debug("debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebugf(t *testing.T) {
|
|
||||||
logger.SetLevel("debug")
|
|
||||||
logger.Debugf("debugf")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Debug("debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
|
||||||
logger.SetLevel("info")
|
|
||||||
logger.Info("info")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Info("info")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfof(t *testing.T) {
|
|
||||||
logger.SetLevel("info")
|
|
||||||
logger.Infof("infof")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Infof("infof")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWarn(t *testing.T) {
|
|
||||||
logger.SetLevel("warn")
|
|
||||||
logger.Warn("warn")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Warn("warn")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWarnf(t *testing.T) {
|
|
||||||
logger.SetLevel("warn")
|
|
||||||
logger.Warnf("warnf")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Warnf("warnf")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
|
||||||
logger.SetLevel("error")
|
|
||||||
logger.Error("error")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Error("error")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorf(t *testing.T) {
|
|
||||||
logger.SetLevel("error")
|
|
||||||
logger.Errorf("errorf")
|
|
||||||
logger.SetLevel("off")
|
|
||||||
logger.Errorf("errorf")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLevel(t *testing.T) {
|
|
||||||
if getLevel("trace") != Trace {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if getLevel("debug") != Debug {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if getLevel("info") != Info {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if getLevel("warn") != Warn {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if getLevel("error") != Error {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoggerSetLevel(t *testing.T) {
|
|
||||||
logger.SetLevel("trace")
|
|
||||||
|
|
||||||
if logger.level != Trace {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsTraceEnabled(t *testing.T) {
|
|
||||||
logger.SetLevel("trace")
|
|
||||||
|
|
||||||
if !logger.IsTraceEnabled() {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsDebugEnabled(t *testing.T) {
|
|
||||||
logger.SetLevel("debug")
|
|
||||||
|
|
||||||
if !logger.IsDebugEnabled() {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsWarnEnabled(t *testing.T) {
|
|
||||||
logger.SetLevel("warn")
|
|
||||||
|
|
||||||
if !logger.IsWarnEnabled() {
|
|
||||||
t.FailNow()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
288
main.go
288
main.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -19,165 +19,146 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/editor"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/event"
|
"github.com/88250/wide/editor"
|
||||||
"github.com/b3log/wide/file"
|
"github.com/88250/wide/event"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/file"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/notification"
|
"github.com/88250/wide/notification"
|
||||||
"github.com/b3log/wide/output"
|
"github.com/88250/wide/output"
|
||||||
"github.com/b3log/wide/playground"
|
"github.com/88250/wide/playground"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger
|
// Logger
|
||||||
var logger *log.Logger
|
var logger *gulu.Logger
|
||||||
|
|
||||||
// The only one init function in Wide.
|
// The only one init function in Wide.
|
||||||
func init() {
|
func init() {
|
||||||
confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
|
confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
|
||||||
confIP := flag.String("ip", "", "this will overwrite Wide.IP if specified")
|
confData := flag.String("data", "", "path of data dir")
|
||||||
confPort := flag.String("port", "", "this will overwrite Wide.Port if specified")
|
|
||||||
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
|
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
|
||||||
confLogLevel := flag.String("log_level", "debug", "this will overwrite Wide.LogLevel if specified")
|
confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified")
|
||||||
confStaticServer := flag.String("static_server", "", "this will overwrite Wide.StaticServer if specified")
|
confReadOnly := flag.String("readonly", "", "this will overrite Wide.ReadOnly if specified")
|
||||||
confContext := flag.String("context", "", "this will overwrite Wide.Context if specified")
|
confSiteStatCode := flag.String("site_stat_code", "", "this will overrite Wide.SiteStatCode if specified")
|
||||||
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel if specified")
|
|
||||||
confStat := flag.Bool("stat", false, "whether report statistics periodically")
|
|
||||||
confDocker := flag.Bool("docker", false, "whether run in a docker container")
|
|
||||||
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
|
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
log.SetLevel("warn")
|
gulu.Log.SetLevel("warn")
|
||||||
logger = log.NewLogger(os.Stdout)
|
logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
wd := util.OS.Pwd()
|
//wd := gulu.OS.Pwd()
|
||||||
if strings.HasPrefix(wd, os.TempDir()) {
|
//if strings.HasPrefix(wd, os.TempDir()) {
|
||||||
logger.Error("Don't run wide in OS' temp directory or with `go run`")
|
// logger.Error("Don't run Wide in OS' temp directory or with `go run`")
|
||||||
|
//
|
||||||
os.Exit(-1)
|
// os.Exit(-1)
|
||||||
}
|
//}
|
||||||
|
|
||||||
i18n.Load()
|
i18n.Load()
|
||||||
|
|
||||||
event.Load()
|
event.Load()
|
||||||
|
conf.Load(*confPath, *confData, *confServer, *confLogLevel, *confReadOnly, template.HTML(*confSiteStatCode))
|
||||||
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
|
|
||||||
*confPlayground, *confDocker)
|
|
||||||
|
|
||||||
conf.FixedTimeCheckEnv()
|
conf.FixedTimeCheckEnv()
|
||||||
|
|
||||||
session.FixedTimeSave()
|
session.FixedTimeSave()
|
||||||
session.FixedTimeRelease()
|
session.FixedTimeRelease()
|
||||||
|
|
||||||
if *confStat {
|
|
||||||
session.FixedTimeReport()
|
session.FixedTimeReport()
|
||||||
}
|
|
||||||
|
logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main.
|
// Main.
|
||||||
func main() {
|
func main() {
|
||||||
runtime.GOMAXPROCS(conf.Wide.MaxProcs)
|
|
||||||
|
|
||||||
initMime()
|
initMime()
|
||||||
|
handleSignal()
|
||||||
|
|
||||||
// IDE
|
// IDE
|
||||||
http.HandleFunc(conf.Wide.Context+"/", handlerGzWrapper(indexHandler))
|
http.HandleFunc("/", handlerGzWrapper(indexHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/start", handlerWrapper(startHandler))
|
http.HandleFunc("/start", handlerWrapper(startHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/about", handlerWrapper(aboutHandler))
|
http.HandleFunc("/about", handlerWrapper(aboutHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
|
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
|
||||||
|
|
||||||
// static resources
|
// static resources
|
||||||
http.Handle(conf.Wide.Context+"/static/", http.StripPrefix(conf.Wide.Context+"/static/", http.FileServer(http.Dir("static"))))
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
serveSingle("/favicon.ico", "./static/favicon.ico")
|
http.Handle("/static/users/", http.StripPrefix("/static/", http.FileServer(http.Dir(conf.Wide.Data+"/static"))))
|
||||||
|
serveSingle("/favicon.ico", "./static/images/favicon.png")
|
||||||
|
|
||||||
// workspaces
|
// oauth
|
||||||
for _, user := range conf.Users {
|
http.HandleFunc("/login/redirect", session.LoginRedirectHandler)
|
||||||
http.Handle(conf.Wide.Context+"/workspace/"+user.Name+"/",
|
http.HandleFunc("/login/callback", session.LoginCallbackHandler)
|
||||||
http.StripPrefix(conf.Wide.Context+"/workspace/"+user.Name+"/", http.FileServer(http.Dir(user.GetWorkspace()))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// session
|
// session
|
||||||
http.HandleFunc(conf.Wide.Context+"/session/ws", handlerWrapper(session.WSHandler))
|
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/session/save", handlerWrapper(session.SaveContent))
|
http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
|
||||||
|
|
||||||
// run
|
// run
|
||||||
http.HandleFunc(conf.Wide.Context+"/build", handlerWrapper(output.BuildHandler))
|
http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/run", handlerWrapper(output.RunHandler))
|
http.HandleFunc("/run", handlerWrapper(output.RunHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/stop", handlerWrapper(output.StopHandler))
|
http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/go/test", handlerWrapper(output.GoTestHandler))
|
http.HandleFunc("/go/test", handlerWrapper(output.GoTestHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/go/vet", handlerWrapper(output.GoVetHandler))
|
http.HandleFunc("/go/vet", handlerWrapper(output.GoVetHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/go/get", handlerWrapper(output.GoGetHandler))
|
http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/go/install", handlerWrapper(output.GoInstallHandler))
|
http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/output/ws", handlerWrapper(output.WSHandler))
|
|
||||||
|
// cross-compilation
|
||||||
|
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
|
||||||
|
|
||||||
// file tree
|
// file tree
|
||||||
http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFiles))
|
http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectory))
|
http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFile))
|
http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFile))
|
http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFile))
|
http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFile))
|
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFile))
|
http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchText))
|
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.Find))
|
http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
|
||||||
|
|
||||||
// outline
|
// outline
|
||||||
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutline))
|
http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
|
||||||
|
|
||||||
// file export/import
|
// file export
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/zip/new", handlerWrapper(file.CreateZip))
|
http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZip))
|
http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.Upload))
|
|
||||||
|
|
||||||
// editor
|
// editor
|
||||||
http.HandleFunc(conf.Wide.Context+"/editor/ws", handlerWrapper(editor.WSHandler))
|
http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/go/fmt", handlerWrapper(editor.GoFmtHandler))
|
http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/autocomplete", handlerWrapper(editor.AutocompleteHandler))
|
http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
|
http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/find/decl", handlerWrapper(editor.FindDeclarationHandler))
|
http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
|
|
||||||
|
|
||||||
// shell
|
|
||||||
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
|
|
||||||
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
|
|
||||||
|
|
||||||
// notification
|
// notification
|
||||||
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler))
|
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
|
||||||
|
|
||||||
// user
|
// user
|
||||||
http.HandleFunc(conf.Wide.Context+"/login", handlerWrapper(session.LoginHandler))
|
http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/logout", handlerWrapper(session.LogoutHandler))
|
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUser))
|
http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
|
|
||||||
|
|
||||||
// playground
|
// playground
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler))
|
http.HandleFunc("/playground", handlerWrapper(playground.IndexHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/", handlerWrapper(playground.IndexHandler))
|
http.HandleFunc("/playground/", handlerWrapper(playground.IndexHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler))
|
http.HandleFunc("/playground/ws", handlerWrapper(playground.WSHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler))
|
http.HandleFunc("/playground/save", handlerWrapper(playground.SaveHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/short-url", handlerWrapper(playground.ShortURLHandler))
|
http.HandleFunc("/playground/build", handlerWrapper(playground.BuildHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler))
|
http.HandleFunc("/playground/run", handlerWrapper(playground.RunHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler))
|
http.HandleFunc("/playground/stop", handlerWrapper(playground.StopHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler))
|
http.HandleFunc("/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
|
|
||||||
|
|
||||||
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
|
logger.Infof("Wide is running [%s]", conf.Wide.Server)
|
||||||
|
|
||||||
err := http.ListenAndServe(conf.Wide.Server, nil)
|
err := http.ListenAndServe("0.0.0.0:7070", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -186,61 +167,50 @@ func main() {
|
||||||
// indexHandler handles request of Wide index.
|
// indexHandler handles request of Wide index.
|
||||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if "/" != r.RequestURI {
|
if "/" != r.RequestURI {
|
||||||
http.NotFound(w, r)
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
if "playground" == username { // reserved user for Playground
|
if "playground" == uid { // reserved user for Playground
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
// create a Wide session
|
user := conf.GetUser(uid)
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
sid := strconv.Itoa(rand.Int())
|
|
||||||
wideSession := session.WideSessions.New(httpSession, sid)
|
|
||||||
|
|
||||||
user := conf.GetUser(username)
|
|
||||||
if nil == user {
|
if nil == user {
|
||||||
logger.Warnf("Not found user [%s]", username)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
locale := user.Locale
|
locale := user.Locale
|
||||||
|
|
||||||
wideSessions := session.WideSessions.GetByUsername(username)
|
wideSessions := session.WideSessions.GetByUserId(uid)
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||||
"session": wideSession, "latestSessionContent": user.LatestSessionContent,
|
"uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
|
||||||
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
||||||
"user": user, "editorThemes": conf.GetEditorThemes()}
|
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": []string{"darwin_amd64", "linux_amd64", "windows_amd64"}}
|
||||||
|
|
||||||
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions))
|
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
|
||||||
|
|
||||||
t, err := template.ParseFiles("views/index.html")
|
t, err := template.ParseFiles("views/index.html")
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -248,6 +218,22 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
t.Execute(w, model)
|
t.Execute(w, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleSignal handles system signal for graceful shutdown.
|
||||||
|
func handleSignal() {
|
||||||
|
go func() {
|
||||||
|
c := make(chan os.Signal)
|
||||||
|
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||||
|
s := <-c
|
||||||
|
logger.Tracef("Got signal [%s]", s)
|
||||||
|
|
||||||
|
session.SaveOnlineUsers()
|
||||||
|
logger.Tracef("Saved all online user, exit")
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// serveSingle registers the handler function for the given pattern and filename.
|
// serveSingle registers the handler function for the given pattern and filename.
|
||||||
func serveSingle(pattern string, filename string) {
|
func serveSingle(pattern string, filename string) {
|
||||||
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -257,22 +243,20 @@ func serveSingle(pattern string, filename string) {
|
||||||
|
|
||||||
// startHandler handles request of start page.
|
// startHandler handles request of start page.
|
||||||
func startHandler(w http.ResponseWriter, r *http.Request) {
|
func startHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
http.Redirect(w, r, "/s", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
user := conf.GetUser(uid)
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
locale := user.Locale
|
||||||
|
userWorkspace := conf.GetUserWorkspace(uid)
|
||||||
|
|
||||||
sid := r.URL.Query()["sid"][0]
|
sid := r.URL.Query()["sid"][0]
|
||||||
wSession := session.WideSessions.Get(sid)
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
@ -281,13 +265,13 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||||
"username": username, "workspace": userWorkspace, "ver": conf.WideVersion, "session": wSession}
|
"uid": uid, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid, "username": user.Name}
|
||||||
|
|
||||||
t, err := template.ParseFiles("views/start.html")
|
t, err := template.ParseFiles("views/start.html")
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -297,21 +281,18 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// keyboardShortcutsHandler handles request of keyboard shortcuts page.
|
// keyboardShortcutsHandler handles request of keyboard shortcuts page.
|
||||||
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
|
||||||
|
|
||||||
|
@ -319,7 +300,7 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -329,21 +310,18 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// aboutHandle handles request of about page.
|
// aboutHandle handles request of about page.
|
||||||
func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||||
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
|
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
|
||||||
|
@ -352,7 +330,7 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -431,7 +409,7 @@ func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http
|
||||||
// panicRecover wraps the panic recover process.
|
// panicRecover wraps the panic recover process.
|
||||||
func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer util.Recover()
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
handler(w, r)
|
handler(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -21,12 +21,12 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/event"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/event"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/util"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// Notification represents a notification.
|
// Notification represents a notification.
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
@ -62,9 +62,9 @@ func event2Notification(e *event.Event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, session.CookieName)
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
var notification *Notification
|
var notification *Notification
|
||||||
|
|
||||||
|
|
274
output/build.go
274
output/build.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -17,87 +17,132 @@ package output
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildHandler handles request of building.
|
// BuildHandler handles request of building.
|
||||||
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
user := conf.GetUser(uid)
|
||||||
|
locale := user.Locale
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
sid := args["sid"].(string)
|
||||||
|
|
||||||
filePath := args["file"].(string)
|
filePath := args["file"].(string)
|
||||||
|
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
curDir := filepath.Dir(filePath)
|
curDir := filepath.Dir(filePath)
|
||||||
|
|
||||||
fout, err := os.Create(filePath)
|
fout, err := os.Create(filePath)
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
code := args["code"].(string)
|
code := args["code"].(string)
|
||||||
|
if _, err := fout.WriteString(code); nil != err {
|
||||||
fout.WriteString(code)
|
|
||||||
|
|
||||||
if err := fout.Close(); nil != err {
|
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fout.Close()
|
||||||
|
|
||||||
|
channelRet := map[string]interface{}{}
|
||||||
|
if nil != session.OutputWS[sid] {
|
||||||
|
// display "START [go build]" in front-end browser
|
||||||
|
|
||||||
|
msg := i18n.Get(locale, "start-build").(string)
|
||||||
|
msg = strings.Replace(msg, "build]", "build "+fmt.Sprint(user.BuildArgs(runtime.GOOS))+"]", 1)
|
||||||
|
|
||||||
|
channelRet["output"] = "<span class='start-build'>" + msg + "</span>\n"
|
||||||
|
channelRet["cmd"] = "start-build"
|
||||||
|
|
||||||
|
wsChannel := session.OutputWS[sid]
|
||||||
|
wsChannel.WriteJSON(&channelRet)
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
var goModCmd *exec.Cmd
|
||||||
|
if !gulu.File.IsExist(filepath.Join(curDir, "go.mod")) {
|
||||||
|
curDirName := filepath.Base(curDir)
|
||||||
|
goModCmd = exec.Command("go", "mod", "init", curDirName)
|
||||||
|
} else {
|
||||||
|
goModCmd = exec.Command("go", "mod", "tidy")
|
||||||
|
}
|
||||||
|
goModCmd.Dir = curDir
|
||||||
|
setCmdEnv(goModCmd, uid)
|
||||||
|
outputBytes, err := goModCmd.CombinedOutput()
|
||||||
|
output := string(outputBytes)
|
||||||
|
if nil != err && strings.Contains(output, "go.mod already exists") {
|
||||||
|
logger.Error(err.Error() + ": " + output)
|
||||||
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var goBuildArgs []string
|
||||||
|
goBuildArgs = append(goBuildArgs, "build")
|
||||||
|
goBuildArgs = append(goBuildArgs, user.BuildArgs(runtime.GOOS)...)
|
||||||
|
//if !gulu.Str.Contains("-i", goBuildArgs) {
|
||||||
|
// goBuildArgs = append(goBuildArgs, "-i")
|
||||||
|
//}
|
||||||
|
|
||||||
|
cmd := exec.Command("go", goBuildArgs...)
|
||||||
|
cmd.Dir = curDir
|
||||||
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if util.OS.IsWindows() {
|
if gulu.OS.IsWindows() {
|
||||||
suffix = ".exe"
|
suffix = ".exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", "build")
|
|
||||||
cmd.Dir = curDir
|
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
|
||||||
|
|
||||||
executable := filepath.Base(curDir) + suffix
|
executable := filepath.Base(curDir) + suffix
|
||||||
executable = filepath.Join(curDir, executable)
|
executable = filepath.Join(curDir, executable)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -105,88 +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)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
if 0 != result.Code {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
if err := cmd.Start(); nil != err {
|
||||||
|
|
||||||
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)
|
|
||||||
data["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"
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -194,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,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,
|
||||||
|
@ -237,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.Error(err)
|
logger.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsChannel.Refresh()
|
wsChannel.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
}(rand.Int())
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
// Copyright (c) 2014-present, b3log.org
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/88250/gulu"
|
||||||
|
"github.com/88250/wide/conf"
|
||||||
|
"github.com/88250/wide/i18n"
|
||||||
|
"github.com/88250/wide/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CrossCompilationHandler handles request of cross compilation.
|
||||||
|
func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
result := gulu.Ret.NewResult()
|
||||||
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
result.Code = -1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := args["sid"].(string)
|
||||||
|
filePath := args["path"].(string)
|
||||||
|
|
||||||
|
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
platform := args["platform"].(string)
|
||||||
|
goos := strings.Split(platform, "_")[0]
|
||||||
|
goarch := strings.Split(platform, "_")[1]
|
||||||
|
|
||||||
|
curDir := filepath.Dir(filePath)
|
||||||
|
|
||||||
|
suffix := ""
|
||||||
|
if "windows" == goos {
|
||||||
|
suffix = ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
user := conf.GetUser(uid)
|
||||||
|
goBuildArgs := []string{}
|
||||||
|
goBuildArgs = append(goBuildArgs, "build")
|
||||||
|
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
|
||||||
|
|
||||||
|
cmd := exec.Command("go", goBuildArgs...)
|
||||||
|
cmd.Dir = curDir
|
||||||
|
|
||||||
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
|
for i, env := range cmd.Env {
|
||||||
|
if strings.HasPrefix(env, "GOOS=") {
|
||||||
|
cmd.Env[i] = "GOOS=" + goos
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(env, "GOARCH=") {
|
||||||
|
cmd.Env[i] = "GOARCH=" + goarch
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executable := filepath.Base(curDir) + suffix
|
||||||
|
executable = filepath.Join(curDir, executable)
|
||||||
|
name := filepath.Base(curDir) + "-" + goos + "-" + goarch
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
result.Code = -1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
result.Code = -1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != result.Code {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelRet := map[string]interface{}{}
|
||||||
|
|
||||||
|
if nil != session.OutputWS[sid] {
|
||||||
|
// display "START [go build]" in front-end browser
|
||||||
|
|
||||||
|
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
|
||||||
|
channelRet["cmd"] = "start-build"
|
||||||
|
|
||||||
|
wsChannel := session.OutputWS[sid]
|
||||||
|
|
||||||
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||||
|
|
||||||
|
if err := cmd.Start(); nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
result.Code = -1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(runningId int) {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
// read all
|
||||||
|
buf, _ := ioutil.ReadAll(reader)
|
||||||
|
|
||||||
|
channelRet := map[string]interface{}{}
|
||||||
|
channelRet["cmd"] = "cross-build"
|
||||||
|
channelRet["executable"] = executable
|
||||||
|
channelRet["name"] = name
|
||||||
|
|
||||||
|
if 0 == len(buf) { // build success
|
||||||
|
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
|
||||||
|
} else { // build error
|
||||||
|
// build gutter lint
|
||||||
|
|
||||||
|
errOut := string(buf)
|
||||||
|
lines := strings.Split(errOut, "\n")
|
||||||
|
|
||||||
|
// path process
|
||||||
|
var errOutWithPath string
|
||||||
|
for _, line := range lines {
|
||||||
|
errOutWithPath += parsePath(curDir, line) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" +
|
||||||
|
"<span class='stderr'>" + errOutWithPath + "</span>"
|
||||||
|
|
||||||
|
// lint process
|
||||||
|
|
||||||
|
if lines[0][0] == '#' {
|
||||||
|
lines = lines[1:] // skip the first line
|
||||||
|
}
|
||||||
|
|
||||||
|
lints := []*Lint{}
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if line[0] == '\t' {
|
||||||
|
// append to the last lint
|
||||||
|
last := len(lints)
|
||||||
|
msg := lints[last-1].Msg
|
||||||
|
msg += line
|
||||||
|
|
||||||
|
lints[last-1].Msg = msg
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
file := line[:strings.Index(line, ":")]
|
||||||
|
left := line[strings.Index(line, ":")+1:]
|
||||||
|
index := strings.Index(left, ":")
|
||||||
|
lineNo := 0
|
||||||
|
msg := left
|
||||||
|
if index >= 0 {
|
||||||
|
lineNo, err = strconv.Atoi(left[:index])
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = left[index+2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
lint := &Lint{
|
||||||
|
File: filepath.Join(curDir, file),
|
||||||
|
LineNo: lineNo - 1,
|
||||||
|
Severity: lintSeverityError,
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
lints = append(lints, lint)
|
||||||
|
}
|
||||||
|
|
||||||
|
channelRet["lints"] = lints
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil != session.OutputWS[sid] {
|
||||||
|
wsChannel := session.OutputWS[sid]
|
||||||
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
|
if nil != err {
|
||||||
|
logger.Warn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
}(rand.Int())
|
||||||
|
}
|
148
output/get.go
148
output/get.go
|
@ -1,148 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
|
||||||
"github.com/b3log/wide/i18n"
|
|
||||||
"github.com/b3log/wide/session"
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoGetHandler handles request of go get.
|
|
||||||
func GoGetHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{"succ": true}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if httpSession.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
username := httpSession.Values["username"].(string)
|
|
||||||
locale := conf.GetUser(username).Locale
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
|
||||||
|
|
||||||
filePath := args["file"].(string)
|
|
||||||
curDir := filepath.Dir(filePath)
|
|
||||||
|
|
||||||
cmd := exec.Command("go", "get")
|
|
||||||
cmd.Dir = curDir
|
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
|
||||||
|
|
||||||
if nil != session.OutputWS[sid] {
|
|
||||||
// display "START [go get]" in front-end browser
|
|
||||||
|
|
||||||
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
|
|
||||||
channelRet["cmd"] = "start-get"
|
|
||||||
|
|
||||||
wsChannel := session.OutputWS[sid]
|
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func(runningId int) {
|
|
||||||
defer util.Recover()
|
|
||||||
defer cmd.Wait()
|
|
||||||
|
|
||||||
logger.Debugf("User [%s, %s] is running [go get] [runningId=%d]", username, sid, runningId)
|
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
|
||||||
channelRet["cmd"] = "go get"
|
|
||||||
|
|
||||||
// read all
|
|
||||||
buf, _ := ioutil.ReadAll(reader)
|
|
||||||
|
|
||||||
if 0 != len(buf) {
|
|
||||||
logger.Debugf("User [%s, %s] 's [go get] [runningId=%d] has done (with error)", username, sid, runningId)
|
|
||||||
|
|
||||||
channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf)
|
|
||||||
} else {
|
|
||||||
logger.Debugf("User [%s, %s] 's running [go get] [runningId=%d] has done", username, sid, runningId)
|
|
||||||
|
|
||||||
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if nil != session.OutputWS[sid] {
|
|
||||||
wsChannel := session.OutputWS[sid]
|
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
}(rand.Int())
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -26,31 +26,37 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoInstallHandler handles request of go install.
|
// GoInstallHandler handles request of go install.
|
||||||
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -63,14 +69,14 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd := exec.Command("go", "install")
|
cmd := exec.Command("go", "install")
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
logger.Debugf("go install %s", curDir)
|
logger.Debugf("go install %s", curDir)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -78,12 +84,12 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
stderr, err := cmd.StderrPipe()
|
stderr, err := cmd.StderrPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
if 0 != result.Code {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,16 +116,16 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
if err := cmd.Start(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(runningId int) {
|
go func(runningId int) {
|
||||||
defer util.Recover()
|
defer gulu.Panic.Recover(nil)
|
||||||
defer cmd.Wait()
|
defer cmd.Wait()
|
||||||
|
|
||||||
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir)
|
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
|
||||||
|
|
||||||
// read all
|
// read all
|
||||||
buf, _ := ioutil.ReadAll(reader)
|
buf, _ := ioutil.ReadAll(reader)
|
||||||
|
@ -183,12 +189,12 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if nil != session.OutputWS[sid] {
|
if nil != session.OutputWS[sid] {
|
||||||
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir)
|
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
|
||||||
|
|
||||||
wsChannel := session.OutputWS[sid]
|
wsChannel := session.OutputWS[sid]
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsChannel.Refresh()
|
wsChannel.Refresh()
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetNamespace(cmd *exec.Cmd) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetNamespace(cmd *exec.Cmd) {
|
|
||||||
// XXX: keep move with Go 1.4 and later's
|
|
||||||
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
||||||
//cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET
|
|
||||||
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER /*| syscall.CLONE_NEWNS*/ | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC /*| syscall.CLONE_NEWNET*/
|
|
||||||
cmd.SysProcAttr.Credential = &syscall.Credential{
|
|
||||||
Uid: 0,
|
|
||||||
Gid: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
|
|
||||||
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -25,10 +25,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/util"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// Lint represents a code lint.
|
// Lint represents a code lint.
|
||||||
type Lint struct {
|
type Lint struct {
|
||||||
|
@ -93,7 +93,7 @@ func parsePath(curDir, outputLine string) string {
|
||||||
column = parts[2]
|
column = parts[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
tagStart := `<span class="path" data-path="` + filepath.Join(curDir, file) + `" data-line="` + line +
|
tagStart := `<span class="path" data-path="` + filepath.ToSlash(filepath.Join(curDir, file)) + `" data-line="` + line +
|
||||||
`" data-column="` + column + `">`
|
`" data-column="` + column + `">`
|
||||||
text := file + ":" + line
|
text := file + ":" + line
|
||||||
if hasColumn {
|
if hasColumn {
|
||||||
|
@ -104,18 +104,29 @@ func parsePath(curDir, outputLine string) string {
|
||||||
return tagStart + text + tagEnd + msgPart
|
return tagStart + text + tagEnd + msgPart
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCmdEnv(cmd *exec.Cmd, username string) {
|
func setCmdEnv(cmd *exec.Cmd, uid string) {
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
userWorkspace := conf.GetUserWorkspace(uid)
|
||||||
|
cache, err := os.UserCacheDir()
|
||||||
|
if nil != err {
|
||||||
|
logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
|
||||||
|
cache = os.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Env = append(cmd.Env,
|
cmd.Env = append(cmd.Env,
|
||||||
|
"GOPROXY=https://goproxy.cn",
|
||||||
|
"GO111MODULE=on",
|
||||||
"GOPATH="+userWorkspace,
|
"GOPATH="+userWorkspace,
|
||||||
"GOOS="+runtime.GOOS,
|
"GOOS="+runtime.GOOS,
|
||||||
"GOARCH="+runtime.GOARCH,
|
"GOARCH="+runtime.GOARCH,
|
||||||
"GOROOT="+runtime.GOROOT(),
|
"GOROOT="+runtime.GOROOT(),
|
||||||
|
"GOCACHE="+cache,
|
||||||
"PATH="+os.Getenv("PATH"))
|
"PATH="+os.Getenv("PATH"))
|
||||||
|
|
||||||
if util.OS.IsWindows() {
|
if gulu.OS.IsWindows() {
|
||||||
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized.
|
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized.
|
||||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||||
|
} else {
|
||||||
|
// 编译链接时找不到依赖的动态库 https://github.com/b3log/wide/issues/352
|
||||||
|
cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+os.Getenv("LD_LIBRARY_PATH"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/session"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Type of process set.
|
|
||||||
type procs map[string][]*os.Process
|
|
||||||
|
|
||||||
// Processse of all users.
|
|
||||||
//
|
|
||||||
// <sid, []*os.Process>
|
|
||||||
var Processes = procs{}
|
|
||||||
|
|
||||||
// Exclusive lock.
|
|
||||||
var mutex sync.Mutex
|
|
||||||
|
|
||||||
// add adds the specified process to the user process set.
|
|
||||||
func (procs *procs) Add(wSession *session.WideSession, proc *os.Process) {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
|
|
||||||
sid := wSession.ID
|
|
||||||
userProcesses := (*procs)[sid]
|
|
||||||
|
|
||||||
userProcesses = append(userProcesses, proc)
|
|
||||||
(*procs)[sid] = userProcesses
|
|
||||||
|
|
||||||
// bind process with wide session
|
|
||||||
wSession.SetProcesses(userProcesses)
|
|
||||||
|
|
||||||
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove removes the specified process from the user process set.
|
|
||||||
func (procs *procs) Remove(wSession *session.WideSession, proc *os.Process) {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
|
|
||||||
sid := wSession.ID
|
|
||||||
|
|
||||||
userProcesses := (*procs)[sid]
|
|
||||||
|
|
||||||
var newProcesses []*os.Process
|
|
||||||
for i, p := range userProcesses {
|
|
||||||
if p.Pid == proc.Pid {
|
|
||||||
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
|
|
||||||
(*procs)[sid] = newProcesses
|
|
||||||
|
|
||||||
// bind process with wide session
|
|
||||||
wSession.SetProcesses(newProcesses)
|
|
||||||
|
|
||||||
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// kill kills a process specified by the given pid.
|
|
||||||
func (procs *procs) Kill(wSession *session.WideSession, pid int) {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
|
|
||||||
sid := wSession.ID
|
|
||||||
|
|
||||||
userProcesses := (*procs)[sid]
|
|
||||||
|
|
||||||
for i, p := range userProcesses {
|
|
||||||
if p.Pid == pid {
|
|
||||||
if err := p.Kill(); nil != err {
|
|
||||||
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.Username, sid, err)
|
|
||||||
} else {
|
|
||||||
var newProcesses []*os.Process
|
|
||||||
|
|
||||||
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
|
|
||||||
(*procs)[sid] = newProcesses
|
|
||||||
|
|
||||||
// bind process with wide session
|
|
||||||
wSession.SetProcesses(newProcesses)
|
|
||||||
|
|
||||||
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.Username, sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
239
output/run.go
239
output/run.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -15,243 +15,16 @@
|
||||||
package output
|
package output
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"github.com/88250/wide/session"
|
||||||
"encoding/json"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
|
||||||
"github.com/b3log/wide/session"
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
outputBufMax = 128 // 128 string(rune)
|
|
||||||
outputTimeout = 100 // 100ms
|
|
||||||
)
|
|
||||||
|
|
||||||
type outputBuf struct {
|
|
||||||
content string
|
|
||||||
millisecond int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHandler handles request of executing a binary file.
|
// RunHandler handles request of executing a binary file.
|
||||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
session.RunHandler(w, r, session.OutputWS)
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
// StopHandler handles request of stopping a running process.
|
||||||
wSession := session.WideSessions.Get(sid)
|
|
||||||
if nil == wSession {
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := args["executable"].(string)
|
|
||||||
curDir := filepath.Dir(filePath)
|
|
||||||
|
|
||||||
cmd := exec.Command(filePath)
|
|
||||||
cmd.Dir = curDir
|
|
||||||
|
|
||||||
if conf.Docker {
|
|
||||||
SetNamespace(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
outReader := bufio.NewReader(stdout)
|
|
||||||
errReader := bufio.NewReader(stderr)
|
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel := session.OutputWS[sid]
|
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
|
||||||
if nil != wsChannel {
|
|
||||||
channelRet["cmd"] = "run-done"
|
|
||||||
channelRet["output"] = ""
|
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channelRet["pid"] = cmd.Process.Pid
|
|
||||||
|
|
||||||
// add the process to user's process set
|
|
||||||
Processes.Add(wSession, cmd.Process)
|
|
||||||
|
|
||||||
go func(runningId int) {
|
|
||||||
defer util.Recover()
|
|
||||||
defer cmd.Wait()
|
|
||||||
|
|
||||||
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
|
|
||||||
|
|
||||||
// push once for front-end to get the 'run' state and pid
|
|
||||||
if nil != wsChannel {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = ""
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
buf := outputBuf{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
wsChannel := session.OutputWS[sid]
|
|
||||||
if nil == wsChannel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := outReader.ReadRune()
|
|
||||||
|
|
||||||
oneRuneStr := string(r)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
|
||||||
|
|
||||||
buf.content += oneRuneStr
|
|
||||||
|
|
||||||
if nil != err {
|
|
||||||
// remove the exited process from user process set
|
|
||||||
Processes.Remove(wSession, cmd.Process)
|
|
||||||
|
|
||||||
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
|
|
||||||
|
|
||||||
channelRet["cmd"] = "run-done"
|
|
||||||
channelRet["output"] = buf.content
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
|
|
||||||
if 0 == buf.millisecond {
|
|
||||||
buf.millisecond = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = buf.content
|
|
||||||
|
|
||||||
buf = outputBuf{} // a new buffer
|
|
||||||
|
|
||||||
err = wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf := outputBuf{}
|
|
||||||
for {
|
|
||||||
r, _, err := errReader.ReadRune()
|
|
||||||
|
|
||||||
wsChannel := session.OutputWS[sid]
|
|
||||||
if nil != err || nil == wsChannel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
oneRuneStr := string(r)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
|
||||||
|
|
||||||
buf.content += oneRuneStr
|
|
||||||
|
|
||||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
|
|
||||||
if 0 == buf.millisecond {
|
|
||||||
buf.millisecond = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
|
|
||||||
|
|
||||||
buf = outputBuf{} // a new buffer
|
|
||||||
|
|
||||||
err = wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(rand.Int())
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopHandler handles request of stoping a running process.
|
|
||||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
session.StopHandler(w, r)
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
|
||||||
pid := int(args["pid"].(float64))
|
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
|
||||||
if nil == wSession {
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Processes.Kill(wSession, pid)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -24,31 +24,31 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoTestHandler handles request of go test.
|
// GoTestHandler handles request of go test.
|
||||||
func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -61,12 +61,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd := exec.Command("go", "test", "-v")
|
cmd := exec.Command("go", "test", "-v")
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
stderr, err := cmd.StderrPipe()
|
stderr, err := cmd.StderrPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
if 0 != result.Code {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Warn(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,15 +106,15 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
if err := cmd.Start(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(runningId int) {
|
go func(runningId int) {
|
||||||
defer util.Recover()
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", username, sid, runningId)
|
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
channelRet := map[string]interface{}{}
|
||||||
channelRet["cmd"] = "go test"
|
channelRet["cmd"] = "go test"
|
||||||
|
@ -126,11 +126,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
|
||||||
if !cmd.ProcessState.Success() {
|
if !cmd.ProcessState.Success() {
|
||||||
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", username, sid, runningId)
|
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
|
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
|
||||||
} else {
|
} else {
|
||||||
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", username, sid, runningId)
|
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
|
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsChannel.Refresh()
|
wsChannel.Refresh()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -24,31 +24,31 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoVetHandler handles request of go vet.
|
// GoVetHandler handles request of go vet.
|
||||||
func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
locale := conf.GetUser(username).Locale
|
locale := conf.GetUser(uid).Locale
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -61,12 +61,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd := exec.Command("go", "vet", ".")
|
cmd := exec.Command("go", "vet", ".")
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
setCmdEnv(cmd, username)
|
setCmdEnv(cmd, uid)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
stderr, err := cmd.StderrPipe()
|
stderr, err := cmd.StderrPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
if 0 != result.Code {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Warn(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,15 +106,15 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
if err := cmd.Start(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(runningId int) {
|
go func(runningId int) {
|
||||||
defer util.Recover()
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId)
|
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
channelRet := map[string]interface{}{}
|
||||||
channelRet["cmd"] = "go vet"
|
channelRet["cmd"] = "go vet"
|
||||||
|
@ -126,11 +126,11 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
|
||||||
if !cmd.ProcessState.Success() {
|
if !cmd.ProcessState.Success() {
|
||||||
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId)
|
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
|
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
|
||||||
} else {
|
} else {
|
||||||
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId)
|
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", uid, sid, runningId)
|
||||||
|
|
||||||
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
|
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsChannel.Refresh()
|
wsChannel.Refresh()
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "wide",
|
||||||
|
"version": "1.6.0",
|
||||||
|
"description": "A Web-based Go IDE , do your development anytime, anywhere.",
|
||||||
|
"homepage": "https://wide.b3log.org",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/88250/wide.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/88250/wide/issues"
|
||||||
|
},
|
||||||
|
"license": "Apache License",
|
||||||
|
"private": true,
|
||||||
|
"author": "Daniel <d@b3log.org> (http://88250.b3log.org) & Vanessa <v@b3log.org> (http://vanessa.b3log.org)",
|
||||||
|
"maintainers": [
|
||||||
|
{
|
||||||
|
"name": "Daniel",
|
||||||
|
"email": "d@b3log.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vanessa",
|
||||||
|
"email": "v@b3log.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"gulp-clean-css": "^4.2.0",
|
||||||
|
"gulp-concat": "^2.6.1",
|
||||||
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
|
"gulp-uglify": "^3.0.1"
|
||||||
|
}
|
||||||
|
}
|
58
pkg.sh
58
pkg.sh
|
@ -5,11 +5,11 @@
|
||||||
# Command:
|
# Command:
|
||||||
# ./pkg.sh ${version} ${target}
|
# ./pkg.sh ${version} ${target}
|
||||||
# Example:
|
# Example:
|
||||||
# ./pkg.sh 1.0.1 /home/daniel/1.0.1/
|
# ./pkg.sh 1.0.0 /home/daniel/1.0.0/
|
||||||
|
|
||||||
ver=$1
|
ver=$1
|
||||||
target=$2
|
target=$2
|
||||||
list="conf doc i18n static views README.md LICENSE"
|
list="conf doc i18n static views README.md TERMS.md LICENSE"
|
||||||
|
|
||||||
mkdir -p ${target}
|
mkdir -p ${target}
|
||||||
|
|
||||||
|
@ -20,43 +20,61 @@ echo target=${target}
|
||||||
os=darwin
|
os=darwin
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=386
|
export GOARCH=amd64
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||||
go build
|
go build
|
||||||
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide
|
go build github.com/stamblerre/gocode
|
||||||
|
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
|
||||||
|
rm -f wide gotools gocode
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=amd64
|
export GOARCH=386
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||||
go build
|
go build
|
||||||
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide
|
go build github.com/stamblerre/gocode
|
||||||
|
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
|
||||||
|
rm -f wide gotools gocode
|
||||||
|
|
||||||
## linux
|
## linux
|
||||||
os=linux
|
os=linux
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=386
|
export GOARCH=amd64
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||||
go build
|
go build
|
||||||
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide
|
go build github.com/stamblerre/gocode
|
||||||
|
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
|
||||||
|
rm -f wide gotools gocode
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=amd64
|
export GOARCH=386
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||||
go build
|
go build
|
||||||
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide
|
go build github.com/stamblerre/gocode
|
||||||
|
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
|
||||||
|
rm -f wide gotools gocode
|
||||||
|
|
||||||
## windows
|
## windows
|
||||||
os=windows
|
os=windows
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=386
|
export GOARCH=amd64
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.zip
|
||||||
go build
|
go build
|
||||||
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide.exe
|
go build github.com/stamblerre/gocode
|
||||||
|
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
|
||||||
|
rm -f wide.exe gotools.exe gocode.exe
|
||||||
|
|
||||||
export GOOS=${os}
|
export GOOS=${os}
|
||||||
export GOARCH=amd64
|
export GOARCH=386
|
||||||
|
echo wide-${ver}-${GOOS}-${GOARCH}.zip
|
||||||
go build
|
go build
|
||||||
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go
|
go build github.com/visualfc/gotools
|
||||||
rm -f wide.exe
|
go build github.com/stamblerre/gocode
|
||||||
|
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
|
||||||
|
rm -f wide.exe gotools.exe gocode.exe
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -18,26 +18,32 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/conf"
|
||||||
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AutocompleteHandler handles request of code autocompletion.
|
// AutocompleteHandler handles request of code autocompletion.
|
||||||
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var args map[string]interface{}
|
if conf.Wide.ReadOnly {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
@ -48,20 +54,28 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
line := int(args["cursorLine"].(float64))
|
line := int(args["cursorLine"].(float64))
|
||||||
ch := int(args["cursorCh"].(float64))
|
ch := int(args["cursorCh"].(float64))
|
||||||
|
|
||||||
|
file, err := os.Create("wide_autocomplete_" + gulu.Rand.String(16) + ".go")
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file.WriteString(code)
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
path := file.Name()
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
offset := getCursorOffset(code, line, ch)
|
offset := getCursorOffset(code, line, ch)
|
||||||
|
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
|
||||||
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
|
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
|
||||||
gocode := util.Go.GetExecutableInGOBIN("gocode")
|
|
||||||
cmd := exec.Command(gocode, argv...)
|
cmd := exec.Command(gocode, argv...)
|
||||||
|
|
||||||
stdin, _ := cmd.StdinPipe()
|
|
||||||
stdin.Write([]byte(code))
|
|
||||||
stdin.Close()
|
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -22,17 +22,23 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildHandler handles request of Playground building.
|
// BuildHandler handles request of Playground building.
|
||||||
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
@ -42,28 +48,31 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := args["fileName"].(string)
|
fileName := args["fileName"].(string)
|
||||||
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
|
||||||
|
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if util.OS.IsWindows() {
|
if gulu.OS.IsWindows() {
|
||||||
suffix = ".exe"
|
suffix = ".exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1))
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
|
executable := filepath.Clean(conf.Wide.Data + "/playground/" + strings.Replace(fileName, ".go", suffix, -1))
|
||||||
|
|
||||||
cmd := exec.Command("go", "build", "-o", executable, filePath)
|
cmd := exec.Command("go", "build", "-o", executable, filePath)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
data["output"] = template.HTML(string(out))
|
data["output"] = template.HTML(string(out))
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -23,19 +23,24 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SaveHandler handles request of Playground code save.
|
// SaveHandler handles request of Playground code save.
|
||||||
func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
if conf.Wide.ReadOnly {
|
||||||
|
result.Code = -1
|
||||||
|
result.Msg = "readonly mode"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
@ -45,7 +50,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -58,7 +63,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -72,6 +77,9 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
code = string(output)
|
code = string(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
result.Data = &data
|
||||||
|
|
||||||
data["code"] = code
|
data["code"] = code
|
||||||
|
|
||||||
// Step2. generate file name
|
// Step2. generate file name
|
||||||
|
@ -82,54 +90,13 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data["fileName"] = fileName
|
data["fileName"] = fileName
|
||||||
|
|
||||||
// Step3. write file
|
// Step3. write file
|
||||||
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
|
||||||
fout, err := os.Create(filePath)
|
fout, err := os.Create(filePath)
|
||||||
fout.WriteString(code)
|
fout.WriteString(code)
|
||||||
if err := fout.Close(); nil != err {
|
if err := fout.Close(); nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShortURLHandler handles request of short URL.
|
|
||||||
func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{"succ": true}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if session.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
url := args["url"].(string)
|
|
||||||
|
|
||||||
resp, _ := http.Post("http://dwz.cn/create.php", "application/x-www-form-urlencoded",
|
|
||||||
strings.NewReader("url="+url))
|
|
||||||
|
|
||||||
var response map[string]interface{}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
shortURL := url
|
|
||||||
if 0 == response["status"].(float64) {
|
|
||||||
shortURL = response["tinyurl"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
data["shortURL"] = shortURL
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -26,48 +26,40 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/i18n"
|
||||||
"github.com/b3log/wide/session"
|
"github.com/88250/wide/session"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/util"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
// IndexHandler handles request of Playground index.
|
// IndexHandler handles request of Playground index.
|
||||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// create a HTTP session
|
// create a HTTP session
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||||
if httpSession.IsNew {
|
if httpSession.IsNew {
|
||||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
||||||
httpSession.Values["username"] = "playground"
|
httpSession.Values["uid"] = "playground"
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
|
||||||
// create a wide session
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
sid := strconv.Itoa(rand.Int())
|
|
||||||
wideSession := session.WideSessions.New(httpSession, sid)
|
|
||||||
|
|
||||||
locale := conf.Wide.Locale
|
locale := conf.Wide.Locale
|
||||||
|
|
||||||
// try to load file
|
// try to load file
|
||||||
code := conf.HelloWorld
|
code := conf.HelloWorld
|
||||||
fileName := "8b7cc38b4c12e6fde5c4d15a4f2f32e5.go" // MD5 of HelloWorld.go
|
fileName := "6c5595ec6fbadf4cfce3edbfcfd8c6d0.go" // MD5 of HelloWorld.go
|
||||||
|
|
||||||
if strings.HasSuffix(r.URL.Path, ".go") {
|
if strings.HasSuffix(r.URL.Path, ".go") {
|
||||||
fileNameArg := r.URL.Path[len("/playground/"):]
|
fileNameArg := r.URL.Path[len("/playground/"):]
|
||||||
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileNameArg)
|
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileNameArg)
|
||||||
|
|
||||||
bytes, err := ioutil.ReadFile(filePath)
|
bytes, err := ioutil.ReadFile(filePath)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
@ -92,19 +84,19 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||||
"session": wideSession, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
"sid": session.WideSessions.GenId(), "pathSeparator": conf.PathSeparator,
|
||||||
|
"codeMirrorVer": conf.CodeMirrorVer,
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -15,239 +15,16 @@
|
||||||
package playground
|
package playground
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"github.com/88250/wide/session"
|
||||||
"encoding/json"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
|
||||||
"github.com/b3log/wide/output"
|
|
||||||
"github.com/b3log/wide/session"
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
outputBufMax = 128 // 128 string(rune)
|
|
||||||
outputTimeout = 100 // 100ms
|
|
||||||
)
|
|
||||||
|
|
||||||
type outputBuf struct {
|
|
||||||
content string
|
|
||||||
millisecond int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHandler handles request of executing a binary file.
|
// RunHandler handles request of executing a binary file.
|
||||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
session.RunHandler(w, r, session.PlaygroundWS)
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
// StopHandler handles request of stopping a running process.
|
||||||
wSession := session.WideSessions.Get(sid)
|
|
||||||
if nil == wSession {
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := args["executable"].(string)
|
|
||||||
|
|
||||||
cmd := exec.Command(filePath)
|
|
||||||
|
|
||||||
if conf.Docker {
|
|
||||||
output.SetNamespace(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
outReader := bufio.NewReader(stdout)
|
|
||||||
errReader := bufio.NewReader(stderr)
|
|
||||||
|
|
||||||
if err := cmd.Start(); nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel := session.PlaygroundWS[sid]
|
|
||||||
|
|
||||||
channelRet := map[string]interface{}{}
|
|
||||||
|
|
||||||
if !data["succ"].(bool) {
|
|
||||||
if nil != wsChannel {
|
|
||||||
channelRet["cmd"] = "run-done"
|
|
||||||
channelRet["output"] = ""
|
|
||||||
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channelRet["pid"] = cmd.Process.Pid
|
|
||||||
|
|
||||||
// add the process to user's process set
|
|
||||||
output.Processes.Add(wSession, cmd.Process)
|
|
||||||
|
|
||||||
go func(runningId int) {
|
|
||||||
defer util.Recover()
|
|
||||||
defer cmd.Wait()
|
|
||||||
|
|
||||||
// push once for front-end to get the 'run' state and pid
|
|
||||||
if nil != wsChannel {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = ""
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
buf := outputBuf{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
wsChannel := session.PlaygroundWS[sid]
|
|
||||||
if nil == wsChannel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := outReader.ReadRune()
|
|
||||||
|
|
||||||
oneRuneStr := string(r)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
|
||||||
|
|
||||||
buf.content += oneRuneStr
|
|
||||||
|
|
||||||
if nil != err {
|
|
||||||
// remove the exited process from user process set
|
|
||||||
output.Processes.Remove(wSession, cmd.Process)
|
|
||||||
|
|
||||||
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
|
|
||||||
|
|
||||||
channelRet["cmd"] = "run-done"
|
|
||||||
channelRet["output"] = buf.content
|
|
||||||
err := wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
|
|
||||||
if 0 == buf.millisecond {
|
|
||||||
buf.millisecond = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = buf.content
|
|
||||||
|
|
||||||
buf = outputBuf{} // a new buffer
|
|
||||||
|
|
||||||
err = wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf := outputBuf{}
|
|
||||||
for {
|
|
||||||
r, _, err := errReader.ReadRune()
|
|
||||||
|
|
||||||
wsChannel := session.PlaygroundWS[sid]
|
|
||||||
if nil != err || nil == wsChannel {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
oneRuneStr := string(r)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
|
||||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
|
||||||
|
|
||||||
buf.content += oneRuneStr
|
|
||||||
|
|
||||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
|
|
||||||
if 0 == buf.millisecond {
|
|
||||||
buf.millisecond = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
|
|
||||||
channelRet["cmd"] = "run"
|
|
||||||
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
|
|
||||||
|
|
||||||
buf = outputBuf{} // a new buffer
|
|
||||||
|
|
||||||
err = wsChannel.WriteJSON(&channelRet)
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChannel.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(rand.Int())
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopHandler handles request of stoping a running process.
|
|
||||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
session.StopHandler(w, r)
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := args["sid"].(string)
|
|
||||||
pid := int(args["pid"].(float64))
|
|
||||||
|
|
||||||
wSession := session.WideSessions.Get(sid)
|
|
||||||
if nil == wSession {
|
|
||||||
data["succ"] = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Processes.Kill(wSession, pid)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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, "<", "<", -1)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -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, "<", "<", -1)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -25,17 +25,21 @@ package session
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/event"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/log"
|
"github.com/88250/wide/event"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/util"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
@ -43,10 +47,12 @@ import (
|
||||||
const (
|
const (
|
||||||
sessionStateActive = iota
|
sessionStateActive = iota
|
||||||
sessionStateClosed // (not used so far)
|
sessionStateClosed // (not used so far)
|
||||||
|
|
||||||
|
CookieName = "wide-sess"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
var logger = log.NewLogger(os.Stdout)
|
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// SessionWS holds all session channels. <sid, *util.WSChannel>
|
// SessionWS holds all session channels. <sid, *util.WSChannel>
|
||||||
|
@ -71,12 +77,13 @@ var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
|
||||||
// WideSession represents a session associated with a browser tab.
|
// WideSession represents a session associated with a browser tab.
|
||||||
type WideSession struct {
|
type WideSession struct {
|
||||||
ID string // id
|
ID string // id
|
||||||
Username string // username
|
UserId string // user id
|
||||||
HTTPSession *sessions.Session // HTTP session related
|
HTTPSession *sessions.Session // HTTP session related
|
||||||
Processes []*os.Process // process set
|
Processes []*os.Process // process set
|
||||||
EventQueue *event.UserEventQueue // event queue
|
EventQueue *event.UserEventQueue // event queue
|
||||||
State int // state
|
State int // state
|
||||||
Content *conf.LatestSessionContent // the latest session content
|
Content *conf.LatestSessionContent // the latest session content
|
||||||
|
FileWatcher *fsnotify.Watcher // files change watcher
|
||||||
Created time.Time // create time
|
Created time.Time // create time
|
||||||
Updated time.Time // the latest use time
|
Updated time.Time // the latest use time
|
||||||
}
|
}
|
||||||
|
@ -98,13 +105,15 @@ var mutex sync.Mutex
|
||||||
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
|
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
|
||||||
func FixedTimeRelease() {
|
func FixedTimeRelease() {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
for _ = range time.Tick(time.Hour) {
|
for _ = range time.Tick(time.Hour) {
|
||||||
hour, _ := time.ParseDuration("-30m")
|
hour, _ := time.ParseDuration("-30m")
|
||||||
threshold := time.Now().Add(hour)
|
threshold := time.Now().Add(hour)
|
||||||
|
|
||||||
for _, s := range WideSessions {
|
for _, s := range WideSessions {
|
||||||
if s.Updated.Before(threshold) {
|
if s.Updated.Before(threshold) {
|
||||||
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username)
|
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.UserId)
|
||||||
|
|
||||||
WideSessions.Remove(s.ID)
|
WideSessions.Remove(s.ID)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +124,7 @@ func FixedTimeRelease() {
|
||||||
|
|
||||||
// Online user statistic report.
|
// Online user statistic report.
|
||||||
type userReport struct {
|
type userReport struct {
|
||||||
username string
|
userId string
|
||||||
sessionCnt int
|
sessionCnt int
|
||||||
processCnt int
|
processCnt int
|
||||||
updated time.Time
|
updated time.Time
|
||||||
|
@ -123,13 +132,15 @@ type userReport struct {
|
||||||
|
|
||||||
// report returns a online user statistics in pretty format.
|
// report returns a online user statistics in pretty format.
|
||||||
func (u *userReport) report() string {
|
func (u *userReport) report() string {
|
||||||
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
|
return "[" + u.userId + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
|
||||||
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
|
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
|
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
|
||||||
func FixedTimeReport() {
|
func FixedTimeReport() {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
for _ = range time.Tick(10 * time.Minute) {
|
for _ = range time.Tick(10 * time.Minute) {
|
||||||
users := userReports{}
|
users := userReports{}
|
||||||
processSum := 0
|
processSum := 0
|
||||||
|
@ -138,7 +149,7 @@ func FixedTimeReport() {
|
||||||
processCnt := len(s.Processes)
|
processCnt := len(s.Processes)
|
||||||
processSum += processCnt
|
processSum += processCnt
|
||||||
|
|
||||||
if report, exists := contains(users, s.Username); exists {
|
if report, exists := contains(users, s.UserId); exists {
|
||||||
if s.Updated.After(report.updated) {
|
if s.Updated.After(report.updated) {
|
||||||
report.updated = s.Updated
|
report.updated = s.Updated
|
||||||
}
|
}
|
||||||
|
@ -146,7 +157,7 @@ func FixedTimeReport() {
|
||||||
report.sessionCnt++
|
report.sessionCnt++
|
||||||
report.processCnt += processCnt
|
report.processCnt += processCnt
|
||||||
} else {
|
} else {
|
||||||
users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
|
users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,9 +176,9 @@ func FixedTimeReport() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(reports []*userReport, username string) (*userReport, bool) {
|
func contains(reports []*userReport, userId string) (*userReport, bool) {
|
||||||
for _, ur := range reports {
|
for _, ur := range reports {
|
||||||
if username == ur.username {
|
if userId == ur.userId {
|
||||||
return ur, true
|
return ur, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,26 +192,22 @@ func (f userReports) Len() int { return len(f) }
|
||||||
func (f userReports) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
func (f userReports) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].processCnt }
|
func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].processCnt }
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a message to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the peer.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
)
|
||||||
|
|
||||||
// WSHandler handles request of creating session channel.
|
// WSHandler handles request of creating session channel.
|
||||||
//
|
//
|
||||||
// When a channel closed, releases all resources associated with it.
|
// When a channel closed, releases all resources associated with it.
|
||||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
sid := r.URL.Query()["sid"][0]
|
sid := r.URL.Query()["sid"][0]
|
||||||
wSession := WideSessions.Get(sid)
|
|
||||||
if nil == wSession {
|
|
||||||
httpSession, _ := HTTPSession.Get(r, "wide-session")
|
|
||||||
|
|
||||||
if httpSession.IsNew {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
|
||||||
httpSession.Save(r, w)
|
|
||||||
|
|
||||||
wSession = WideSessions.New(httpSession, sid)
|
|
||||||
|
|
||||||
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||||
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
||||||
|
@ -213,15 +220,52 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
SessionWS[sid] = &wsChan
|
SessionWS[sid] = &wsChan
|
||||||
|
|
||||||
|
wSession := WideSessions.Get(sid)
|
||||||
|
if nil == wSession {
|
||||||
|
httpSession, _ := HTTPSession.Get(r, CookieName)
|
||||||
|
|
||||||
|
if httpSession.IsNew {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
|
httpSession.Save(r, w)
|
||||||
|
|
||||||
|
wSession = WideSessions.new(httpSession, sid)
|
||||||
|
|
||||||
|
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
|
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
|
||||||
|
|
||||||
input := map[string]interface{}{}
|
input := map[string]interface{}{}
|
||||||
|
|
||||||
|
wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
wsChan.Conn.SetPongHandler(func(string) error { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
WideSessions.Remove(sid)
|
||||||
|
ticker.Stop()
|
||||||
|
wsChan.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// send websocket ping message.
|
||||||
|
go func(t *time.Ticker, channel util.WSChannel) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
if err := channel.Conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}(ticker, wsChan)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if err := wsChan.ReadJSON(&input); err != nil {
|
if err := wsChan.ReadJSON(&input); err != nil {
|
||||||
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.Username)
|
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.UserId)
|
||||||
|
|
||||||
WideSessions.Remove(sid)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -238,10 +282,10 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveContent handles request of session content string.
|
// SaveContentHandler handles request of session content string.
|
||||||
func SaveContent(w http.ResponseWriter, r *http.Request) {
|
func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{"succ": true}
|
result := gulu.Ret.NewResult()
|
||||||
defer util.RetJSON(w, r, data)
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
|
|
||||||
args := struct {
|
args := struct {
|
||||||
Sid string
|
Sid string
|
||||||
|
@ -250,14 +294,14 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wSession := WideSessions.Get(args.Sid)
|
wSession := WideSessions.Get(args.Sid)
|
||||||
if nil == wSession {
|
if nil == wSession {
|
||||||
data["succ"] = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -265,7 +309,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||||
wSession.Content = args.LatestSessionContent
|
wSession.Content = args.LatestSessionContent
|
||||||
|
|
||||||
for _, user := range conf.Users {
|
for _, user := range conf.Users {
|
||||||
if user.Name == wSession.Username {
|
if user.Id == wSession.UserId {
|
||||||
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically
|
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically
|
||||||
user.LatestSessionContent = wSession.Content
|
user.LatestSessionContent = wSession.Content
|
||||||
|
|
||||||
|
@ -290,30 +334,11 @@ func (s *WideSession) Refresh() {
|
||||||
s.Updated = time.Now()
|
s.Updated = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a wide session.
|
// GenId generates a wide session id.
|
||||||
func (sessions *wSessions) New(httpSession *sessions.Session, sid string) *WideSession {
|
func (sessions *wSessions) GenId() string {
|
||||||
mutex.Lock()
|
rand.Seed(time.Now().UnixNano())
|
||||||
defer mutex.Unlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
return strconv.Itoa(rand.Int())
|
||||||
|
|
||||||
// create user event queue
|
|
||||||
userEventQueue := event.UserEventQueues.New(sid)
|
|
||||||
|
|
||||||
ret := &WideSession{
|
|
||||||
ID: sid,
|
|
||||||
Username: httpSession.Values["username"].(string),
|
|
||||||
HTTPSession: httpSession,
|
|
||||||
EventQueue: userEventQueue,
|
|
||||||
State: sessionStateActive,
|
|
||||||
Content: &conf.LatestSessionContent{},
|
|
||||||
Created: now,
|
|
||||||
Updated: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
*sessions = append(*sessions, ret)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a wide session with the specified session id.
|
// Get gets a wide session with the specified session id.
|
||||||
|
@ -337,6 +362,7 @@ func (sessions *wSessions) Get(sid string) *WideSession {
|
||||||
// 1. user event queue
|
// 1. user event queue
|
||||||
// 2. process set
|
// 2. process set
|
||||||
// 3. websocket channels
|
// 3. websocket channels
|
||||||
|
// 4. file watcher
|
||||||
func (sessions *wSessions) Remove(sid string) {
|
func (sessions *wSessions) Remove(sid string) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
@ -352,9 +378,9 @@ func (sessions *wSessions) Remove(sid string) {
|
||||||
// kill processes
|
// kill processes
|
||||||
for _, p := range s.Processes {
|
for _, p := range s.Processes {
|
||||||
if err := p.Kill(); nil != err {
|
if err := p.Kill(); nil != err {
|
||||||
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
|
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
|
||||||
} else {
|
} else {
|
||||||
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
|
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,14 +405,19 @@ func (sessions *wSessions) Remove(sid string) {
|
||||||
delete(PlaygroundWS, sid)
|
delete(PlaygroundWS, sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// file watcher
|
||||||
|
if nil != s.FileWatcher {
|
||||||
|
s.FileWatcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
cnt := 0 // count wide sessions associated with HTTP session
|
cnt := 0 // count wide sessions associated with HTTP session
|
||||||
for _, ses := range *sessions {
|
for _, ses := range *sessions {
|
||||||
if ses.Username == s.Username {
|
if ses.UserId == s.UserId {
|
||||||
cnt++
|
cnt++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt)
|
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -394,17 +425,130 @@ func (sessions *wSessions) Remove(sid string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByUsername gets wide sessions.
|
// GetByUsername gets wide sessions.
|
||||||
func (sessions *wSessions) GetByUsername(username string) []*WideSession {
|
func (sessions *wSessions) GetByUserId(userId string) []*WideSession {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
ret := []*WideSession{}
|
ret := []*WideSession{}
|
||||||
|
|
||||||
for _, s := range *sessions {
|
for _, s := range *sessions {
|
||||||
if s.Username == username {
|
if s.UserId == userId {
|
||||||
ret = append(ret, s)
|
ret = append(ret, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new creates a wide session.
|
||||||
|
func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideSession {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
uid := httpSession.Values["uid"].(string)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
ret := &WideSession{
|
||||||
|
ID: sid,
|
||||||
|
UserId: uid,
|
||||||
|
HTTPSession: httpSession,
|
||||||
|
EventQueue: nil,
|
||||||
|
State: sessionStateActive,
|
||||||
|
Content: &conf.LatestSessionContent{},
|
||||||
|
Created: now,
|
||||||
|
Updated: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
*sessions = append(*sessions, ret)
|
||||||
|
|
||||||
|
if "playground" == uid {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// create user event queue
|
||||||
|
ret.EventQueue = event.UserEventQueues.New(sid)
|
||||||
|
|
||||||
|
// add a filesystem watcher to notify front-end after the files changed
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ch := SessionWS[sid]
|
||||||
|
if nil == ch {
|
||||||
|
return // release this goroutine
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
path := filepath.ToSlash(event.Name)
|
||||||
|
dir := filepath.ToSlash(filepath.Dir(path))
|
||||||
|
|
||||||
|
ch = SessionWS[sid]
|
||||||
|
if nil == ch {
|
||||||
|
return // release this goroutine
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Trace(event)
|
||||||
|
|
||||||
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
|
fileType := "f"
|
||||||
|
|
||||||
|
if gulu.File.IsDir(path) {
|
||||||
|
fileType = "d"
|
||||||
|
|
||||||
|
if err = watcher.Add(path); nil != err {
|
||||||
|
logger.Warn(err, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
|
||||||
|
ch.WriteJSON(&cmd)
|
||||||
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||||
|
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
|
||||||
|
ch.WriteJSON(&cmd)
|
||||||
|
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
|
||||||
|
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
|
||||||
|
ch.WriteJSON(&cmd)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
if nil != err {
|
||||||
|
logger.Error("File watcher ERROR: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer gulu.Panic.Recover(nil)
|
||||||
|
|
||||||
|
workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
|
||||||
|
for _, workspace := range workspaces {
|
||||||
|
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
|
||||||
|
if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
if err = watcher.Add(dirPath); nil != err {
|
||||||
|
logger.Error(err, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Tracef("File watcher added a dir [%s]", dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.FileWatcher = watcher
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
390
session/users.go
390
session/users.go
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
// Copyright (c) 2014-present, b3log.org
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
//
|
//
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -15,30 +15,24 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
"github.com/88250/gulu"
|
||||||
"github.com/b3log/wide/i18n"
|
"github.com/88250/wide/conf"
|
||||||
"github.com/b3log/wide/util"
|
"github.com/88250/wide/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TODO: i18n
|
// TODO: i18n
|
||||||
|
|
||||||
userExists = "user exists"
|
userExists = "user exists"
|
||||||
emailExists = "email exists"
|
|
||||||
userCreated = "user created"
|
userCreated = "user created"
|
||||||
userCreateError = "user create error"
|
userCreateError = "user create error"
|
||||||
)
|
)
|
||||||
|
@ -48,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, `"`, `"`, -1)
|
||||||
|
user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `"`, -1)
|
||||||
|
user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `"`, -1)
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
|
||||||
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
|
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
|
||||||
"locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats(),
|
"locales": i18n.GetLocalesNames(), "gofmts": gulu.Go.GetGoFormats(),
|
||||||
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
|
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
|
||||||
|
|
||||||
t, err := template.ParseFiles("views/preference.html")
|
t, err := template.ParseFiles("views/preference.html")
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
user.GoBuildArgsForLinux = tmpLinux
|
||||||
|
user.GoBuildArgsForWindows = tmpWindows
|
||||||
|
user.GoBuildArgsForDarwin = tmpDarwin
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Execute(w, model)
|
t.Execute(w, model)
|
||||||
|
|
||||||
|
user.GoBuildArgsForLinux = tmpLinux
|
||||||
|
user.GoBuildArgsForWindows = tmpWindows
|
||||||
|
user.GoBuildArgsForDarwin = tmpDarwin
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-GET request as save request
|
// non-GET request as save request
|
||||||
|
|
||||||
succ := true
|
result := gulu.Ret.NewResult()
|
||||||
data := map[string]interface{}{"succ": &succ}
|
defer gulu.Ret.RetResult(w, r, result)
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
args := struct {
|
args := struct {
|
||||||
FontFamily string
|
FontFamily string
|
||||||
FontSize string
|
FontSize string
|
||||||
GoFmt string
|
GoFmt string
|
||||||
|
GoBuildArgsForLinux string
|
||||||
|
GoBuildArgsForWindows string
|
||||||
|
GoBuildArgsForDarwin 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
|
||||||
|
@ -110,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)
|
||||||
succ = false
|
result.Code = -1
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -118,16 +125,12 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
user.FontFamily = args.FontFamily
|
user.FontFamily = args.FontFamily
|
||||||
user.FontSize = args.FontSize
|
user.FontSize = args.FontSize
|
||||||
user.GoFormat = args.GoFmt
|
user.GoFormat = args.GoFmt
|
||||||
|
user.GoBuildArgsForLinux = args.GoBuildArgsForLinux
|
||||||
|
user.GoBuildArgsForWindows = args.GoBuildArgsForWindows
|
||||||
|
user.GoBuildArgsForDarwin = args.GoBuildArgsForDarwin
|
||||||
|
user.Keymap = args.Keymap
|
||||||
// XXX: disallow change workspace at present
|
// XXX: disallow change workspace at present
|
||||||
// user.Workspace = args.Workspace
|
// user.Workspace = args.Workspace
|
||||||
if user.Password != args.Password {
|
|
||||||
user.Password = conf.Salt(args.Password, user.Salt)
|
|
||||||
}
|
|
||||||
user.Email = args.Email
|
|
||||||
|
|
||||||
hash := md5.New()
|
|
||||||
hash.Write([]byte(user.Email))
|
|
||||||
user.Gravatar = hex.EncodeToString(hash.Sum(nil))
|
|
||||||
|
|
||||||
user.Locale = args.Locale
|
user.Locale = args.Locale
|
||||||
user.Theme = args.Theme
|
user.Theme = args.Theme
|
||||||
|
@ -137,154 +140,17 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
user.Editor.Theme = args.EditorTheme
|
user.Editor.Theme = args.EditorTheme
|
||||||
user.Editor.TabSize = args.EditorTabSize
|
user.Editor.TabSize = args.EditorTabSize
|
||||||
|
|
||||||
conf.UpdateCustomizedConf(username)
|
conf.UpdateCustomizedConf(uid)
|
||||||
|
|
||||||
now := time.Now().UnixNano()
|
now := time.Now().UnixNano()
|
||||||
user.Lived = now
|
user.Lived = now
|
||||||
user.Updated = now
|
user.Updated = now
|
||||||
|
|
||||||
succ = user.Save()
|
if user.Save() {
|
||||||
|
result.Code = 0
|
||||||
|
} else {
|
||||||
|
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
|
|
||||||
|
|
||||||
succ := true
|
|
||||||
data := map[string]interface{}{"succ": &succ}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
args := struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error("login error: ", err)
|
|
||||||
succ = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
succ = false
|
|
||||||
for _, user := range conf.Users {
|
|
||||||
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
|
|
||||||
succ = true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !succ {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a HTTP session
|
|
||||||
httpSession, _ := HTTPSession.Get(r, "wide-session")
|
|
||||||
httpSession.Values["username"] = args.Username
|
|
||||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
|
||||||
|
|
||||||
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogoutHandler handles request of user logout (exit).
|
|
||||||
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{"succ": true}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
httpSession, _ := HTTPSession.Get(r, "wide-session")
|
|
||||||
|
|
||||||
httpSession.Options.MaxAge = -1
|
|
||||||
httpSession.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignUpUser handles request of registering user.
|
|
||||||
func SignUpUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if "GET" == r.Method {
|
|
||||||
// show the user sign up page
|
|
||||||
|
|
||||||
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
|
|
||||||
dir := filepath.Dir(firstUserWorkspace)
|
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
|
|
||||||
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": dir,
|
|
||||||
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
|
|
||||||
|
|
||||||
t, err := template.ParseFiles("views/sign_up.html")
|
|
||||||
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Execute(w, model)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-GET request as add user request
|
|
||||||
|
|
||||||
succ := true
|
|
||||||
data := map[string]interface{}{"succ": &succ}
|
|
||||||
defer util.RetJSON(w, r, data)
|
|
||||||
|
|
||||||
var args map[string]interface{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
succ = false
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
username := args["username"].(string)
|
|
||||||
password := args["password"].(string)
|
|
||||||
email := args["email"].(string)
|
|
||||||
|
|
||||||
msg := addUser(username, password, email)
|
|
||||||
if userCreated != msg {
|
|
||||||
succ = false
|
|
||||||
data["msg"] = msg
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a HTTP session
|
|
||||||
httpSession, _ := HTTPSession.Get(r, "wide-session")
|
|
||||||
httpSession.Values["username"] = username
|
|
||||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedTimeSave saves online users' configurations periodically (1 minute).
|
// FixedTimeSave saves online users' configurations periodically (1 minute).
|
||||||
|
@ -292,35 +158,57 @@ func SignUpUser(w http.ResponseWriter, r *http.Request) {
|
||||||
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
|
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
|
||||||
func FixedTimeSave() {
|
func FixedTimeSave() {
|
||||||
go func() {
|
go func() {
|
||||||
for _ = range time.Tick(time.Minute) {
|
defer gulu.Panic.Recover(nil)
|
||||||
users := getOnlineUsers()
|
|
||||||
|
|
||||||
|
for _ = range time.Tick(time.Minute) {
|
||||||
|
SaveOnlineUsers()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanAccess determines whether the user specified by the given user id can access the specified path.
|
||||||
|
func CanAccess(userId, path string) bool {
|
||||||
|
path = filepath.FromSlash(path)
|
||||||
|
|
||||||
|
userWorkspace := conf.GetUserWorkspace(userId)
|
||||||
|
workspaces := filepath.SplitList(userWorkspace)
|
||||||
|
|
||||||
|
for _, workspace := range workspaces {
|
||||||
|
if strings.HasPrefix(path, workspace) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveOnlineUsers saves online users' configurations at once.
|
||||||
|
func SaveOnlineUsers() {
|
||||||
|
users := getOnlineUsers()
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
if u.Save() {
|
if u.Save() {
|
||||||
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
|
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOnlineUsers() []*conf.User {
|
func getOnlineUsers() []*conf.User {
|
||||||
ret := []*conf.User{}
|
ret := []*conf.User{}
|
||||||
|
|
||||||
usernames := map[string]string{} // distinct username
|
uids := map[string]string{} // distinct uid
|
||||||
for _, s := range WideSessions {
|
for _, s := range WideSessions {
|
||||||
usernames[s.Username] = s.Username
|
uids[s.UserId] = s.UserId
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, username := range usernames {
|
for _, uid := range uids {
|
||||||
u := conf.GetUser(username)
|
u := conf.GetUser(uid)
|
||||||
|
|
||||||
if "playground" == username { // user [playground] is a reserved mock user
|
if "playground" == uid { // user [playground] is a reserved mock user
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if nil == u {
|
if nil == u {
|
||||||
logger.Warnf("Not found user [%s]", username)
|
logger.Warnf("Not found user [%s]", uid)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -330,133 +218,3 @@ func getOnlineUsers() []*conf.User {
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// addUser add a user with the specified username, password and email.
|
|
||||||
//
|
|
||||||
// 1. create the user's workspace
|
|
||||||
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a http version)
|
|
||||||
// 3. update the user customized configurations, such as style.css
|
|
||||||
// 4. serve files of the user's workspace via HTTP
|
|
||||||
//
|
|
||||||
// Note: user [playground] is a reserved mock user
|
|
||||||
func addUser(username, password, email string) string {
|
|
||||||
if "playground" == username {
|
|
||||||
return userExists
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserMutex.Lock()
|
|
||||||
defer addUserMutex.Unlock()
|
|
||||||
|
|
||||||
for _, user := range conf.Users {
|
|
||||||
if strings.ToLower(user.Name) == strings.ToLower(username) {
|
|
||||||
return userExists
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ToLower(user.Email) == strings.ToLower(email) {
|
|
||||||
return emailExists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
|
|
||||||
dir := filepath.Dir(firstUserWorkspace)
|
|
||||||
workspace := filepath.Join(dir, username)
|
|
||||||
|
|
||||||
newUser := conf.NewUser(username, password, email, workspace)
|
|
||||||
conf.Users = append(conf.Users, newUser)
|
|
||||||
|
|
||||||
if !newUser.Save() {
|
|
||||||
return userCreateError
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.CreateWorkspaceDir(workspace)
|
|
||||||
helloWorld(workspace)
|
|
||||||
conf.UpdateCustomizedConf(username)
|
|
||||||
|
|
||||||
http.Handle("/workspace/"+username+"/",
|
|
||||||
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
|
|
||||||
|
|
||||||
logger.Infof("Created a user [%s]", username)
|
|
||||||
|
|
||||||
return userCreated
|
|
||||||
}
|
|
||||||
|
|
||||||
// helloWorld generates the 'Hello, 世界' source code.
|
|
||||||
// 1. src/hello/main.go
|
|
||||||
// 2. src/web/main.go
|
|
||||||
func helloWorld(workspace string) {
|
|
||||||
consoleHello(workspace)
|
|
||||||
webHello(workspace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func consoleHello(workspace string) {
|
|
||||||
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
|
|
||||||
if err := os.MkdirAll(dir, 0755); nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fout.WriteString(conf.HelloWorld)
|
|
||||||
|
|
||||||
fout.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func webHello(workspace string) {
|
|
||||||
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
|
|
||||||
if err := os.MkdirAll(dir, 0755); nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
code := `package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("Hello, 世界"))
|
|
||||||
})
|
|
||||||
|
|
||||||
port := getPort()
|
|
||||||
|
|
||||||
// you may need to change the address
|
|
||||||
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
|
|
||||||
|
|
||||||
if err := http.ListenAndServe(":"+port, nil); nil != err {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPort() string {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
return strconv.Itoa(7000 + rand.Intn(8000-7000))
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
fout.WriteString(code)
|
|
||||||
|
|
||||||
fout.Close()
|
|
||||||
}
|
|
||||||
|
|
196
shell/shells.go
196
shell/shells.go
|
@ -1,196 +0,0 @@
|
||||||
// Copyright (c) 2014-2015, b3log.org
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package shell include shell related mainipulations.
|
|
||||||
package shell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/b3log/wide/conf"
|
|
||||||
"github.com/b3log/wide/i18n"
|
|
||||||
"github.com/b3log/wide/log"
|
|
||||||
"github.com/b3log/wide/session"
|
|
||||||
"github.com/b3log/wide/util"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Shell channel.
|
|
||||||
//
|
|
||||||
// <sid, *util.WSChannel>>
|
|
||||||
var ShellWS = map[string]*util.WSChannel{}
|
|
||||||
|
|
||||||
// Logger.
|
|
||||||
var logger = log.NewLogger(os.Stdout)
|
|
||||||
|
|
||||||
// IndexHandler handles request of Shell index.
|
|
||||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if httpSession.IsNew {
|
|
||||||
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
|
||||||
if "" != conf.Wide.Context {
|
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
|
||||||
}
|
|
||||||
httpSession.Save(r, w)
|
|
||||||
|
|
||||||
// create a wide session
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
sid := strconv.Itoa(rand.Int())
|
|
||||||
wideSession := session.WideSessions.New(httpSession, sid)
|
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
|
||||||
locale := conf.GetUser(username).Locale
|
|
||||||
|
|
||||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
|
||||||
"session": wideSession}
|
|
||||||
|
|
||||||
wideSessions := session.WideSessions.GetByUsername(username)
|
|
||||||
|
|
||||||
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
|
|
||||||
|
|
||||||
t, err := template.ParseFiles("views/shell.html")
|
|
||||||
|
|
||||||
if nil != err {
|
|
||||||
logger.Error(err)
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Execute(w, model)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WSHandler handles request of creating Shell channel.
|
|
||||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
|
||||||
if httpSession.IsNew {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
username := httpSession.Values["username"].(string)
|
|
||||||
|
|
||||||
sid := r.URL.Query()["sid"][0]
|
|
||||||
|
|
||||||
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
|
||||||
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
|
||||||
|
|
||||||
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
|
|
||||||
err := wsChan.WriteJSON(&ret)
|
|
||||||
if nil != err {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ShellWS[sid] = &wsChan
|
|
||||||
|
|
||||||
logger.Debugf("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
|
|
||||||
|
|
||||||
input := map[string]interface{}{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if err := wsChan.ReadJSON(&input); err != nil {
|
|
||||||
logger.Error("Shell WS ERROR: " + err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inputCmd := input["cmd"].(string)
|
|
||||||
|
|
||||||
cmds := strings.Split(inputCmd, "|")
|
|
||||||
commands := []*exec.Cmd{}
|
|
||||||
for _, cmdWithArgs := range cmds {
|
|
||||||
cmdWithArgs = strings.TrimSpace(cmdWithArgs)
|
|
||||||
cmdWithArgs := strings.Split(cmdWithArgs, " ")
|
|
||||||
args := []string{}
|
|
||||||
if len(cmdWithArgs) > 1 {
|
|
||||||
args = cmdWithArgs[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(cmdWithArgs[0], args...)
|
|
||||||
commands = append(commands, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
if !strings.Contains(inputCmd, "clear") {
|
|
||||||
output = pipeCommands(username, commands...)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = map[string]interface{}{"output": output, "cmd": "shell-output"}
|
|
||||||
|
|
||||||
if err := wsChan.WriteJSON(&ret); err != nil {
|
|
||||||
logger.Error("Shell WS ERROR: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsChan.Refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pipeCommands(username string, commands ...*exec.Cmd) string {
|
|
||||||
for i, command := range commands[:len(commands)-1] {
|
|
||||||
setCmdEnv(command, username)
|
|
||||||
|
|
||||||
stdout, err := command.StdoutPipe()
|
|
||||||
if nil != err {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
command.Start()
|
|
||||||
|
|
||||||
commands[i+1].Stdin = stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
last := commands[len(commands)-1]
|
|
||||||
setCmdEnv(last, username)
|
|
||||||
|
|
||||||
out, err := last.CombinedOutput()
|
|
||||||
|
|
||||||
// release resources
|
|
||||||
for _, command := range commands[:len(commands)-1] {
|
|
||||||
command.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCmdEnv(cmd *exec.Cmd, username string) {
|
|
||||||
userWorkspace := conf.GetUserWorkspace(username)
|
|
||||||
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
"TERM="+os.Getenv("TERM"),
|
|
||||||
"GOPATH="+userWorkspace,
|
|
||||||
"GOOS="+runtime.GOOS,
|
|
||||||
"GOARCH="+runtime.GOARCH,
|
|
||||||
"GOROOT="+runtime.GOROOT(),
|
|
||||||
"PATH="+os.Getenv("PATH"))
|
|
||||||
|
|
||||||
cmd.Dir = userWorkspace
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialogAbout .thx a {
|
#dialogAbout .thx a {
|
||||||
width: 100px;
|
width: 80px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,6 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* themes for base.
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 0.2.0.0, Oct 5, 2018
|
||||||
|
*/
|
||||||
/* start reset & function */
|
/* start reset & function */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -134,17 +140,24 @@ button {
|
||||||
/* start icon */
|
/* start icon */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
src:url('fonts/icomoon.eot?35cb2z');
|
src: url('fonts/icomoon.eot?lqk80d');
|
||||||
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'),
|
src: url('fonts/icomoon.eot?lqk80d#iefix') format('embedded-opentype'),
|
||||||
url('fonts/icomoon.woff?35cb2z') format('woff'),
|
url('fonts/icomoon.ttf?lqk80d') format('truetype'),
|
||||||
url('fonts/icomoon.ttf?35cb2z') format('truetype'),
|
url('fonts/icomoon.woff?lqk80d') format('woff'),
|
||||||
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg');
|
url('fonts/icomoon.svg?lqk80d#icomoon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico {
|
[class^="ico-"], [class*=" ico-"] {
|
||||||
font-family: 'icomoon';
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
|
font-family: 'icomoon' !important;
|
||||||
|
speak: none;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
text-transform: none;
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
/* Better Font Rendering =========== */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
@ -153,173 +166,140 @@ button {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-book:before {
|
.ico-qqz:before {
|
||||||
content: "\e623";
|
content: "\e900";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-price:before {
|
|
||||||
content: "\e616";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-start:before {
|
|
||||||
content: "\e9d7";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-share:before {
|
|
||||||
content: "\e61f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-github:before {
|
|
||||||
content: "\f00a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-tencent:before {
|
|
||||||
content: "\e622";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-weibo:before {
|
|
||||||
content: "\e621";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-googleplus:before {
|
|
||||||
content: "\e61a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-twitter:before {
|
|
||||||
content: "\e61c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-email:before {
|
|
||||||
content: "\e619";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-facebook:before {
|
|
||||||
content: "\e61b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-moveup:before {
|
|
||||||
content: "\f148";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-movedown:before {
|
|
||||||
content: "\f149";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-keyboard:before {
|
|
||||||
content: "\f11c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-findfiles:before {
|
|
||||||
content: "\e603";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-find:before {
|
.ico-find:before {
|
||||||
content: "\e602";
|
content: "\e602";
|
||||||
}
|
}
|
||||||
|
.ico-findfiles:before {
|
||||||
|
content: "\e603";
|
||||||
|
}
|
||||||
.ico-editor:before {
|
.ico-editor:before {
|
||||||
content: "\e604";
|
content: "\e604";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-tree:before {
|
|
||||||
content: "\e600";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-build:before {
|
|
||||||
content: "\e601";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-notification:before {
|
.ico-notification:before {
|
||||||
content: "\e607";
|
content: "\e607";
|
||||||
}
|
}
|
||||||
|
.ico-price:before {
|
||||||
|
content: "\e616";
|
||||||
|
}
|
||||||
.ico-report:before {
|
.ico-report:before {
|
||||||
content: "\e605";
|
content: "\e605";
|
||||||
}
|
}
|
||||||
|
.ico-git:before {
|
||||||
.ico-comment:before {
|
content: "\e624";
|
||||||
content: "\e620";
|
|
||||||
}
|
}
|
||||||
|
.ico-book:before {
|
||||||
.ico-goline:before {
|
content: "\e623";
|
||||||
content: "\e61e";
|
|
||||||
}
|
}
|
||||||
|
.ico-start:before {
|
||||||
.ico-info:before {
|
content: "\e9d7";
|
||||||
content: "\e61d";
|
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
.ico-tree:before {
|
||||||
.ico-signup:before {
|
content: "\e600";
|
||||||
content: "\e606";
|
|
||||||
}
|
}
|
||||||
|
.ico-build:before {
|
||||||
.ico-signout:before {
|
content: "\e601";
|
||||||
content: "\e618";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-redo:before {
|
|
||||||
content: "\e615";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-undo:before {
|
|
||||||
content: "\e60e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-about:before {
|
|
||||||
content: "\e60d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-import:before {
|
|
||||||
content: "\f0ee";
|
|
||||||
}
|
|
||||||
|
|
||||||
.ico-export:before {
|
.ico-export:before {
|
||||||
content: "\f0ed";
|
content: "\f0ed";
|
||||||
}
|
}
|
||||||
|
.ico-import:before {
|
||||||
.ico-refresh:before {
|
content: "\f0ee";
|
||||||
content: "\f021";
|
|
||||||
}
|
}
|
||||||
|
.ico-keyboard:before {
|
||||||
.ico-remove:before {
|
content: "\f11c";
|
||||||
content: "\e60b";
|
|
||||||
}
|
}
|
||||||
|
.ico-moveup:before {
|
||||||
.ico-save:before {
|
content: "\f148";
|
||||||
content: "\f0c7";
|
}
|
||||||
|
.ico-movedown:before {
|
||||||
|
content: "\f149";
|
||||||
|
}
|
||||||
|
.ico-weibo:before {
|
||||||
|
content: "\e621";
|
||||||
|
}
|
||||||
|
.ico-uniE608:before {
|
||||||
|
content: "\e608";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-max:before {
|
.ico-max:before {
|
||||||
content: "\e609";
|
content: "\e609";
|
||||||
}
|
}
|
||||||
|
.ico-remove:before {
|
||||||
.ico-format:before {
|
content: "\e60b";
|
||||||
content: "\e612";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-buildrun:before {
|
.ico-buildrun:before {
|
||||||
content: "\e60c";
|
content: "\e60c";
|
||||||
}
|
}
|
||||||
|
.ico-about:before {
|
||||||
|
content: "\e60d";
|
||||||
|
}
|
||||||
|
.ico-undo:before {
|
||||||
|
content: "\e60e";
|
||||||
|
}
|
||||||
.ico-stop:before {
|
.ico-stop:before {
|
||||||
content: "\e60f";
|
content: "\e60f";
|
||||||
}
|
}
|
||||||
|
.ico-close:before {
|
||||||
|
content: "\e611";
|
||||||
|
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
.ico-format:before {
|
||||||
|
content: "\e612";
|
||||||
|
}
|
||||||
.ico-restore:before {
|
.ico-restore:before {
|
||||||
content: "\e613";
|
content: "\e613";
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbars .ico-restore:before {
|
.toolbars .ico-restore:before {
|
||||||
content: "\e60a";
|
content: "\e60a";
|
||||||
}
|
}
|
||||||
|
|
||||||
.ico-min:before {
|
.ico-min:before {
|
||||||
content: "\e614";
|
content: "\e614";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
|
.ico-redo:before {
|
||||||
.ico-close:before {
|
content: "\e615";
|
||||||
content: "\e611";
|
}
|
||||||
|
.ico-uniE617:before {
|
||||||
|
content: "\e617";
|
||||||
|
}
|
||||||
|
.ico-signout:before {
|
||||||
|
content: "\e618";
|
||||||
|
}
|
||||||
|
.ico-email:before {
|
||||||
|
content: "\e619";
|
||||||
|
}
|
||||||
|
.ico-googleplus:before {
|
||||||
|
content: "\e61a";
|
||||||
|
}
|
||||||
|
.ico-facebook:before {
|
||||||
|
content: "\e61b";
|
||||||
|
}
|
||||||
|
.ico-twitter:before {
|
||||||
|
content: "\e61c";
|
||||||
|
}
|
||||||
|
.ico-info:before {
|
||||||
|
content: "\e61d";
|
||||||
|
}
|
||||||
|
.ico-goline:before {
|
||||||
|
content: "\e61e";
|
||||||
|
}
|
||||||
|
.ico-share:before {
|
||||||
|
content: "\e61f";
|
||||||
|
}
|
||||||
|
.ico-comment:before {
|
||||||
|
content: "\e620";
|
||||||
|
}
|
||||||
|
.ico-github:before {
|
||||||
|
content: "\f00a";
|
||||||
|
}
|
||||||
|
.ico-refresh:before {
|
||||||
|
content: "\f021";
|
||||||
|
}
|
||||||
|
.ico-save:before {
|
||||||
|
content: "\f0c7";
|
||||||
}
|
}
|
||||||
/* end ico */
|
/* end ico */
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
/**
|
/**
|
||||||
* dialig style
|
* dialig style
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:LLY219@gmail.com">Liyuan Li</a>
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
* @version 0.0.0.6, Jun 3, 2012
|
* @version 0.0.0.6, Jun 3, 2012
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
.dialog-header-bg {
|
.dialog-header-bg {
|
||||||
height: 23px;
|
height: 23px;
|
||||||
background-color: #CCD5E5;
|
background-color: #bbb;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -6,51 +6,50 @@
|
||||||
<font id="icomoon" horiz-adv-x="1024">
|
<font id="icomoon" horiz-adv-x="1024">
|
||||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||||
<missing-glyph horiz-adv-x="1024" />
|
<missing-glyph horiz-adv-x="1024" />
|
||||||
<glyph unicode=" " d="" horiz-adv-x="512" />
|
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M622.858 778.475l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" />
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M192 832l640-384-640-384z" />
|
<glyph unicode="" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
|
||||||
<glyph unicode="" 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="" 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="" 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="" 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="" d="M128 832h768v-768h-768z" />
|
<glyph unicode="" glyph-name="stop" d="M128 832h768v-768h-768z" />
|
||||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" glyph-name="uniE617" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
|
||||||
<glyph unicode="" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
|
<glyph unicode="" glyph-name="signout" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
|
||||||
<glyph unicode="" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
|
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M18.286 804.571h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" horiz-adv-x="585" />
|
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
body {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin: 0;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header li {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .gravatar {
|
|
||||||
width: 26px;
|
|
||||||
border-radius: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .logo {
|
|
||||||
height: 21px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-panel {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 20;
|
|
||||||
width: 226px;
|
|
||||||
padding: 5px 0px;
|
|
||||||
right: 0px;
|
|
||||||
line-height: normal;
|
|
||||||
top: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-panel .font-ico {
|
|
||||||
transition: all .2s ease-out 0s;
|
|
||||||
margin: 0 5px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-panel .font-ico:hover {
|
|
||||||
transform:rotate(360deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
height: 30px;
|
|
||||||
text-shadow: 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editorDiv {
|
|
||||||
width: 100%;
|
|
||||||
height: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#output {
|
|
||||||
height: 30%;
|
|
||||||
width: 100%;
|
|
||||||
border-width: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border-top: 1px solid #919191;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dialogShare {
|
|
||||||
margin: 10px 15px 0;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dialogShare a {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 1px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#disqus_thread {
|
|
||||||
border-top: 1px solid #919191;
|
|
||||||
padding: 0 30px;
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -13,17 +13,27 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 1px;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico {
|
.header li {
|
||||||
font-size: 26px;
|
margin-left: 0;
|
||||||
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .gravatar {
|
.header .gravatar {
|
||||||
|
@ -31,23 +41,25 @@ body {
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header > .fn-right {
|
.header .logo {
|
||||||
margin-top: 10px;
|
height: 28px;
|
||||||
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .logo {
|
.header .font-ico {
|
||||||
height: 36px;
|
font-size: 18px;
|
||||||
margin-top: -4px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.share-panel {
|
.share-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
width: 258px;
|
width: 190px;
|
||||||
padding: 10px 10px;
|
padding: 5px 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
top: 57px;
|
top: 38px;
|
||||||
|
border-color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.share-panel .font-ico {
|
.share-panel .font-ico {
|
||||||
|
@ -66,17 +78,21 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#editorDiv {
|
#editorDiv {
|
||||||
width: 60%;
|
width: 100%;
|
||||||
float: left;
|
height: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-window-group {
|
||||||
|
height: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#output {
|
#output {
|
||||||
width: 40%;
|
height: 100%;
|
||||||
float: right;
|
width: 100%;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 2px 5px;
|
||||||
border-left: 1px solid #919191;
|
border-top: 1px solid #919191;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dialogShare {
|
#dialogShare {
|
||||||
|
@ -89,7 +105,54 @@ body {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#disqus_thread {
|
.wrapper {
|
||||||
border-top: 1px solid #919191;
|
width: auto;
|
||||||
padding: 0 30px;
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editorDivWrap {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews {
|
||||||
|
width: 30%;
|
||||||
|
overflow: auto;
|
||||||
|
border-left: 1px solid #919191;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews li a {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews li a.fn-right {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
color: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews li a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goNews li.title {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
background-color: #f9fafb;
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,43 +14,37 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* themes for side
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 0.1.0.0, Dec 6, 2015
|
||||||
|
*/
|
||||||
/* start side */
|
/* start side */
|
||||||
.side {
|
.side {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 8;
|
z-index: 8;
|
||||||
|
flex-flow: column;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-max {
|
.side-max {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side .tabs-panel {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
/* end side */
|
/* end side */
|
||||||
|
|
||||||
/* start side right */
|
/* start side right */
|
||||||
.side-right {
|
|
||||||
height: 70%;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
width: 20%;
|
|
||||||
z-index: 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-right-max {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-right .tabs-panel > div {
|
.side-right .tabs-panel > div {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-right {
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
#outline .ico {
|
#outline .ico {
|
||||||
margin: 1px 5px 0 5px;
|
margin: 1px 5px 0 5px;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +82,8 @@
|
||||||
.ztree {
|
.ztree {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
outline: 0px;
|
||||||
|
border: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ztree li a.curSelectedNode {
|
.ztree li a.curSelectedNode {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,9 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 980px;
|
width: 980px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .logo {
|
.header .logo {
|
||||||
|
@ -44,13 +52,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.header a:hover {
|
.header a:hover {
|
||||||
color: #4183C4;
|
color: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fn-left {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
border-top: 1px solid #A4A4A4;
|
border-top: 1px solid #A4A4A4;
|
||||||
border-bottom: 1px solid #919191;
|
border-bottom: 1px solid #919191;
|
||||||
background-color: #202021;
|
background-color: #3b3e43;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content h2 {
|
.content h2 {
|
||||||
|
@ -60,94 +75,104 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content h3 {
|
.content h3 {
|
||||||
color: #4183c4;
|
color: #4285f4;
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content .form {
|
.content .form {
|
||||||
|
padding: 24px 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
margin-top: -18px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content .form input {
|
.content a {
|
||||||
border: 1px solid #ccc;
|
color: #4285f4;
|
||||||
border-radius: 3px;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fafafa;
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset;
|
|
||||||
color: #333;
|
|
||||||
min-height: 34px;
|
|
||||||
outline: medium none;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 16px;
|
|
||||||
border: 1px solid #FFF;
|
|
||||||
padding: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content .form input:focus {
|
|
||||||
background-color: #FFF;
|
.login__icon {
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
|
width: 200px;
|
||||||
|
transition: all .15s ease-in-out;
|
||||||
|
padding-right: 24px;
|
||||||
|
color: #3b3e43;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login__icon:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.25);
|
background-color: #2ebc4f;
|
||||||
background-color: #60b044;
|
|
||||||
background-image: linear-gradient(#8add6d, #60b044);
|
|
||||||
border: 1px solid #5ca941;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
background-color: #569e3d;
|
text-decoration: none;
|
||||||
background-image: linear-gradient(#79d858, #569e3d);
|
background-color: #28a745;
|
||||||
border-color: #4a993e;
|
}
|
||||||
|
|
||||||
|
.btn:focus {
|
||||||
|
box-shadow: 0 0 0 0.2em rgba(40, 167, 69, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue {
|
||||||
|
background-color: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-blue:hover {
|
||||||
|
background-color: #2a75f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-white,
|
.btn.btn-white,
|
||||||
.btn.btn-red {
|
.btn.btn-red {
|
||||||
color: #333;
|
color: #333;
|
||||||
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
|
background-color: #fff;
|
||||||
background-color: #eee;
|
|
||||||
background-image: linear-gradient(#fcfcfc, #eee);
|
|
||||||
border-color: #d5d5d5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-red {
|
.btn.btn-red {
|
||||||
color: #9d0000;
|
color: #d23f31;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-white:hover {
|
.btn.btn-white:hover {
|
||||||
color: #333;
|
|
||||||
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
|
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
background-image: linear-gradient(#eee, #ddd);
|
|
||||||
border-color: #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-red:hover {
|
.btn.btn-red:hover {
|
||||||
background-color: #b33630;
|
|
||||||
background-image: linear-gradient(#dc5f59, #b33630);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #cd504a;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
|
background-color: #d23f31;
|
||||||
}
|
}
|
||||||
|
|
||||||
#msg {
|
.desc {
|
||||||
background-color: #fcdede;
|
color: #6a737d;
|
||||||
border: 1px solid #d2b2b2;
|
font-size: 12px;
|
||||||
padding: 15px;
|
}
|
||||||
font-size: 14px;
|
|
||||||
color: #911;
|
.more-detail {
|
||||||
position: absolute;
|
display: none;
|
||||||
width: 100%;
|
margin: 0 0 8px 60px;
|
||||||
top: -48px;
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-more {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
color: #4285f4;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
@ -155,20 +180,28 @@
|
||||||
color: #777;
|
color: #777;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer a {
|
.footer a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #4183c4;
|
color: #4285f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer a:hover {
|
.footer a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer .github-btns {
|
||||||
|
height: 25px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* start sign up */
|
/* start sign up */
|
||||||
.dir {
|
.dir {
|
||||||
color: #4183c4;
|
color: #4285f4;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
@ -188,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 */
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -32,14 +32,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#startPage .title {
|
#startPage .title {
|
||||||
background-color: #9F9F9F;
|
background-color: #BBB;
|
||||||
border-bottom-width: 0 !important;
|
border-bottom-width: 0 !important;
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#startPage .details {
|
#startPage .details {
|
||||||
|
@ -71,6 +70,8 @@
|
||||||
border-left: 1px solid #f1f1f1;
|
border-left: 1px solid #f1f1f1;
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
padding-left: 10%;
|
padding-left: 10%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#startPage .news li {
|
#startPage .news li {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,14 +14,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* themes for dark.
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 0.1.0.0, Dec 6, 2015
|
||||||
|
*/
|
||||||
.side {
|
.side {
|
||||||
background-color: #303130;
|
background-color: #303130;
|
||||||
border-right: 1px solid #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-right {
|
.side-right {
|
||||||
background-color: #303130;
|
background-color: #303130;
|
||||||
border-left: 1px solid #000;
|
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,11 +40,13 @@
|
||||||
color: #EBEBEB;
|
color: #EBEBEB;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico {
|
.font-ico,
|
||||||
|
.ico-restore {
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico:hover,
|
.font-ico:hover,
|
||||||
|
.ico-restore:hover,
|
||||||
.frame li:hover .font-ico {
|
.frame li:hover .font-ico {
|
||||||
color: #e1e1e1;
|
color: #e1e1e1;
|
||||||
}
|
}
|
||||||
|
@ -141,11 +147,7 @@
|
||||||
color: #e1e1e1;
|
color: #e1e1e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group {
|
.bottom-window-group .tabs-panel {
|
||||||
border-top: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-window-group .tabs-panel > div{
|
|
||||||
background-color: #181818;
|
background-color: #181818;
|
||||||
color: #ebebeb;
|
color: #ebebeb;
|
||||||
}
|
}
|
||||||
|
@ -153,3 +155,25 @@
|
||||||
#dialogPreference .preference {
|
#dialogPreference .preference {
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
}
|
}
|
||||||
|
/* start layout resize */
|
||||||
|
.ui-layout-resizer,
|
||||||
|
.ui-layout-resizer-dragging,
|
||||||
|
.ui-layout-resizer-open-hover,
|
||||||
|
.ui-layout-resizer-dragging {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-closed {
|
||||||
|
background-color: #555555;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-west-closed {
|
||||||
|
border-right: 1px solid #000;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-south-closed {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-east-closed {
|
||||||
|
border-left: 1px solid #000;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
|
||||||
|
background: #000;
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,14 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* themes for default.
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 0.1.0.0, Dec 6, 2015
|
||||||
|
*/
|
||||||
|
|
||||||
.side {
|
.side {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
border-right: 1px solid #919191;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-right {
|
.side-right {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
border-left: 1px solid #919191;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
@ -34,11 +39,13 @@
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico {
|
.font-ico,
|
||||||
|
.ico-restore {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-ico:hover,
|
.font-ico:hover,
|
||||||
|
.ico-restore:hover,
|
||||||
.frame li:hover .font-ico {
|
.frame li:hover .font-ico {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
@ -122,10 +129,27 @@
|
||||||
background-color: #F0F0F0;
|
background-color: #F0F0F0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group {
|
|
||||||
border-top: 1px solid #919191;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dialogPreference .preference {
|
#dialogPreference .preference {
|
||||||
border: 1px solid #A4A4A4;
|
border: 1px solid #A4A4A4;
|
||||||
}
|
}
|
||||||
|
/* start layout resize */
|
||||||
|
.ui-layout-resizer,
|
||||||
|
.ui-layout-resizer-dragging {
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-open-hover , /* hover-color to 'resize' */
|
||||||
|
.ui-layout-resizer-dragging { /* resizer beging 'dragging' */
|
||||||
|
background: #919191;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-west-closed {
|
||||||
|
border-right: 1px solid #919191;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-south-closed {
|
||||||
|
border-top: 1px solid #919191;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-east-closed {
|
||||||
|
border-left: 1px solid #919191;
|
||||||
|
}
|
||||||
|
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
|
||||||
|
background: #919191;
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,6 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* themes for wide.
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 0.1.0.1, Dec 15, 2015
|
||||||
|
*/
|
||||||
/* start frame */
|
/* start frame */
|
||||||
.frame {
|
.frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -77,9 +83,20 @@
|
||||||
.tabs > div > span.changed {
|
.tabs > div > span.changed {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tabs-panel {
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
/* end tabs */
|
/* end tabs */
|
||||||
|
|
||||||
/* start menu */
|
/* start menu */
|
||||||
|
.menu {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
.menu > ul > li {
|
.menu > ul > li {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -119,9 +136,10 @@
|
||||||
.share-panel {
|
.share-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
width: 226px;
|
width: 190px;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
top: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.share-panel .font-ico {
|
.share-panel .font-ico {
|
||||||
|
@ -143,6 +161,8 @@
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex-flow: column;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbars {
|
.toolbars {
|
||||||
|
@ -184,6 +204,7 @@
|
||||||
|
|
||||||
.CodeMirror,
|
.CodeMirror,
|
||||||
.CodeMirror-hints {
|
.CodeMirror-hints {
|
||||||
|
font-family: Consolas, 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-hints .ico {
|
.CodeMirror-hints .ico {
|
||||||
|
@ -218,27 +239,8 @@
|
||||||
|
|
||||||
/* start bottom-window-group */
|
/* start bottom-window-group */
|
||||||
.bottom-window-group {
|
.bottom-window-group {
|
||||||
width: 80%;
|
|
||||||
position: absolute;
|
|
||||||
left: 20%;
|
|
||||||
width: 80%;
|
|
||||||
height: 30%;
|
|
||||||
top: 70%;
|
|
||||||
z-index: 7;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
flex-flow: column;
|
||||||
|
|
||||||
.bottom-window-group-max {
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 11;
|
|
||||||
border-top-width: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-window-group > div > div {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group .output {
|
.bottom-window-group .output {
|
||||||
|
@ -247,6 +249,7 @@
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group .output pre {
|
.bottom-window-group .output pre {
|
||||||
|
@ -256,22 +259,19 @@
|
||||||
|
|
||||||
.bottom-window-group .output .start-build,
|
.bottom-window-group .output .start-build,
|
||||||
.bottom-window-group .output .start-test, .start-vet,
|
.bottom-window-group .output .start-test, .start-vet,
|
||||||
.bottom-window-group .output .start-install,
|
.bottom-window-group .output .start-install {
|
||||||
.bottom-window-group .output .start-get {
|
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group .output .build-succ,
|
.bottom-window-group .output .build-succ,
|
||||||
.bottom-window-group .output .test-succ, .vet-succ,
|
.bottom-window-group .output .test-succ, .vet-succ,
|
||||||
.bottom-window-group .output .install-succ,
|
.bottom-window-group .output .install-succ {
|
||||||
.bottom-window-group .output .get-succ {
|
|
||||||
color: rgb(0,153,0);
|
color: rgb(0,153,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-window-group .output .build-error,
|
.bottom-window-group .output .build-error,
|
||||||
.bottom-window-group .output .test-error, .vet-error,
|
.bottom-window-group .output .test-error, .vet-error,
|
||||||
.bottom-window-group .output .install-error,
|
.bottom-window-group .output .install-error {
|
||||||
.bottom-window-group .output .get-error {
|
|
||||||
color: #9d0000;
|
color: #9d0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,23 +295,29 @@
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-window-group .notification {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-window-group .notification .type,
|
.bottom-window-group .notification .type,
|
||||||
.bottom-window-group .notification .severity {
|
.bottom-window-group .notification .severity {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-window-group .search {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
/* end bottom-window-group */
|
/* end bottom-window-group */
|
||||||
|
|
||||||
/* start footer */
|
/* start footer */
|
||||||
.footer {
|
.footer {
|
||||||
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
|
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
|
||||||
text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.57);
|
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
height: 19px;
|
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
bottom: 0;
|
display: block !important;
|
||||||
z-index: 11;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .cursor {
|
.footer .cursor {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px;text-decoration:none}.dialog-close-icon:hover{text-decoration:none}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#fff;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px}
|
||||||
|
::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:solid 0 transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;margin:0;color:#000;overflow:hidden;font-family:Helvetica}ul{padding:0;margin:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}button,input{font-family:Helvetica}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#fff}.list li.selected .ft-small,.list li:hover .ft-small{color:#fff}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?lqk80d);src:url(fonts/icomoon.eot?lqk80d#iefix) format('embedded-opentype'),url(fonts/icomoon.ttf?lqk80d) format('truetype'),url(fonts/icomoon.woff?lqk80d) format('woff'),url(fonts/icomoon.svg?lqk80d#icomoon) format('svg');font-weight:400;font-style:normal}[class*=" ico-"],[class^=ico-]{font-family:icomoon!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-qqz:before{content:"\e900"}.ico-find:before{content:"\e602"}.ico-findfiles:before{content:"\e603"}.ico-editor:before{content:"\e604"}.ico-notification:before{content:"\e607"}.ico-price:before{content:"\e616"}.ico-report:before{content:"\e605"}.ico-git:before{content:"\e624"}.ico-book:before{content:"\e623"}.ico-start:before{content:"\e9d7";text-shadow:0 0 rgba(0,0,0,.4)}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-export:before{content:"\f0ed"}.ico-import:before{content:"\f0ee"}.ico-keyboard:before{content:"\f11c"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-weibo:before{content:"\e621"}.ico-uniE608:before{content:"\e608"}.ico-max:before{content:"\e609"}.ico-remove:before{content:"\e60b"}.ico-buildrun:before{content:"\e60c"}.ico-about:before{content:"\e60d"}.ico-undo:before{content:"\e60e"}.ico-stop:before{content:"\e60f"}.ico-close:before{content:"\e611";text-shadow:0 0 rgba(0,0,0,.4)}.ico-format:before{content:"\e612"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-redo:before{content:"\e615"}.ico-uniE617:before{content:"\e617"}.ico-signout:before{content:"\e618"}.ico-email:before{content:"\e619"}.ico-googleplus:before{content:"\e61a"}.ico-facebook:before{content:"\e61b"}.ico-twitter:before{content:"\e61c"}.ico-info:before{content:"\e61d"}.ico-goline:before{content:"\e61e"}.ico-share:before{content:"\e61f"}.ico-comment:before{content:"\e620"}.ico-github:before{content:"\f00a"}.ico-refresh:before{content:"\f021"}.ico-save:before{content:"\f0c7"}
|
||||||
|
.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{padding:0 5px;line-height:25px;cursor:pointer}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#fff}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{float:left;line-height:20px;height:20px;padding:0 5px;cursor:pointer}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.menu{display:block!important}.menu>ul>li{float:left}.menu>ul>li>span{font-size:12px;line-height:21px;cursor:pointer;padding:4px 7px}.menu .split{float:left;border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6db14c;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:190px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{position:absolute;left:20%;width:60%;height:70%;overflow:hidden;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);float:left;height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.footer .cursor{cursor:pointer}.notification-count{float:right;display:none;cursor:pointer;background-color:#9d0000;color:#fff;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px}
|
||||||
|
.side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0 5px}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px}
|
||||||
|
#startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#bbb;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#fff}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px;border-bottom:1px solid #919191}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .news li{border-bottom:1px solid #919191}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap}
|
||||||
|
#dialogAboutDialog .dialog-main{background-color:#fff}#dialogAbout{margin:35px 20px;line-height:28px}#dialogAbout .item{margin:0 10px}#dialogAbout a{color:#4183c4;text-decoration:none}#dialogAbout a:hover{text-decoration:underline}#dialogAbout label{color:#666}#dialogAbout img{width:100px;float:left;margin-right:60px}#dialogAbout .space{margin-bottom:6px;border-bottom:1px solid #919191;padding-bottom:6px}#dialogAbout .thx ul{margin-left:50px}#dialogAbout .thx a{width:80px;display:inline-block}#dialogAbout .license{color:#999;font-size:12px;line-height:normal;height:85px;overflow-x:hidden;word-wrap:break-word}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
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 |
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,6 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @file bottomGroup.js
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @author <a href="http://88250.b3log.org">Liang Ding</a>
|
||||||
|
* @version 1.1.1.1, Mar 15, 2017
|
||||||
|
*/
|
||||||
var bottomGroup = {
|
var bottomGroup = {
|
||||||
tabs: undefined,
|
tabs: undefined,
|
||||||
searchTab: undefined,
|
searchTab: undefined,
|
||||||
|
@ -75,6 +82,7 @@ var bottomGroup = {
|
||||||
fillOutput: function (data) {
|
fillOutput: function (data) {
|
||||||
var $output = $('.bottom-window-group .output');
|
var $output = $('.bottom-window-group .output');
|
||||||
|
|
||||||
|
data = data.replace(/\r/g, '');
|
||||||
data = data.replace(/\n/g, '<br/>');
|
data = data.replace(/\n/g, '<br/>');
|
||||||
|
|
||||||
if (-1 !== data.indexOf("<br/>")) {
|
if (-1 !== data.indexOf("<br/>")) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,11 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @file dialog.js
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @version 1.0.0.1, Dec 8, 2015
|
||||||
|
*/
|
||||||
(function ($) {
|
(function ($) {
|
||||||
$.fn.extend({
|
$.fn.extend({
|
||||||
dialog: {
|
dialog: {
|
||||||
version: "0.0.1.7",
|
version: "0.0.1.7",
|
||||||
author: "lly219@gmail.com"
|
author: "v@b3log.org"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @file editor.js
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @author <a href="http://88250.b3log.org">Liang Ding</a>
|
||||||
|
* @version 1.1.1.0, Jan 12, 2016
|
||||||
|
*/
|
||||||
var editors = {
|
var editors = {
|
||||||
|
autocompleteMutex: false,
|
||||||
data: [],
|
data: [],
|
||||||
tabs: {},
|
tabs: {},
|
||||||
getEditorByPath: function (path) {
|
getEditorByPath: function (path) {
|
||||||
|
@ -25,7 +33,7 @@ var editors = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
$(".edit-panel .tabs > div[data-index=" + $(".edit-panel .frame").data("index") + "]").find(".ico-close").click();
|
$('.edit-panel .tabs > div[data-index="' + $('.edit-panel .frame').data('index') + ']').find('.ico-close').click();
|
||||||
},
|
},
|
||||||
closeOther: function () {
|
closeOther: function () {
|
||||||
var currentIndex = $(".edit-panel .frame").data("index");
|
var currentIndex = $(".edit-panel .frame").data("index");
|
||||||
|
@ -43,14 +51,14 @@ var editors = {
|
||||||
var firstIndex = removeData.splice(0, 1);
|
var firstIndex = removeData.splice(0, 1);
|
||||||
$("#dialogCloseEditor").data("removeData", removeData);
|
$("#dialogCloseEditor").data("removeData", removeData);
|
||||||
// 开始关闭
|
// 开始关闭
|
||||||
$(".edit-panel .tabs > div[data-index=" + firstIndex + "]").find(".ico-close").click();
|
$('.edit-panel .tabs > div[data-index="' + firstIndex + '"]').find(".ico-close").click();
|
||||||
},
|
},
|
||||||
_removeAllMarker: function () {
|
_removeAllMarker: function () {
|
||||||
var removeData = $("#dialogCloseEditor").data("removeData");
|
var removeData = $("#dialogCloseEditor").data("removeData");
|
||||||
if (removeData && removeData.length > 0) {
|
if (removeData && removeData.length > 0) {
|
||||||
var removeIndex = removeData.splice(0, 1);
|
var removeIndex = removeData.splice(0, 1);
|
||||||
$("#dialogCloseEditor").data("removeData", removeData);
|
$("#dialogCloseEditor").data("removeData", removeData);
|
||||||
$(".edit-panel .tabs > div[data-index=" + removeIndex + "] .ico-close").click();
|
$('.edit-panel .tabs > div[data-index="' + removeIndex + '"] .ico-close').click();
|
||||||
}
|
}
|
||||||
if (wide.curEditor) {
|
if (wide.curEditor) {
|
||||||
wide.curEditor.focus();
|
wide.curEditor.focus();
|
||||||
|
@ -97,7 +105,7 @@ var editors = {
|
||||||
"afterInit": function () {
|
"afterInit": function () {
|
||||||
$("#dialogCloseEditor button.save").click(function () {
|
$("#dialogCloseEditor button.save").click(function () {
|
||||||
var i = $("#dialogCloseEditor").data("index");
|
var i = $("#dialogCloseEditor").data("index");
|
||||||
wide.fmt(tree.fileTree.getNodeByTId(editors.data[i].id).path, editors.data[i].editor);
|
wide.fmt(editors.data[i].id, editors.data[i].editor);
|
||||||
editors.tabs.del(editors.data[i].id);
|
editors.tabs.del(editors.data[i].id);
|
||||||
$("#dialogCloseEditor").dialog("close");
|
$("#dialogCloseEditor").dialog("close");
|
||||||
editors._removeAllMarker();
|
editors._removeAllMarker();
|
||||||
|
@ -129,27 +137,9 @@ var editors = {
|
||||||
wide.curEditor = undefined;
|
wide.curEditor = undefined;
|
||||||
$(".footer .cursor").text('');
|
$(".footer .cursor").text('');
|
||||||
wide.refreshOutline();
|
wide.refreshOutline();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set tree node selected
|
|
||||||
var node = tree.fileTree.getNodeByTId(id);
|
|
||||||
tree.fileTree.selectNode(node);
|
|
||||||
wide.curNode = node;
|
|
||||||
|
|
||||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
|
||||||
if (editors.data[i].id === id) {
|
|
||||||
wide.curEditor = editors.data[i].editor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cursor = wide.curEditor.getCursor();
|
|
||||||
wide.curEditor.setCursor(cursor);
|
|
||||||
wide.curEditor.focus();
|
|
||||||
wide.refreshOutline();
|
|
||||||
|
|
||||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
|
||||||
},
|
},
|
||||||
removeBefore: function (id) {
|
removeBefore: function (id) {
|
||||||
if (id === 'startPage') { // 当前关闭的 tab 是起始页
|
if (id === 'startPage') { // 当前关闭的 tab 是起始页
|
||||||
|
@ -163,8 +153,8 @@ var editors = {
|
||||||
editors._removeAllMarker();
|
editors._removeAllMarker();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$("#dialogCloseEditor").dialog("open", $(".edit-panel .tabs > div[data-index="
|
$("#dialogCloseEditor").dialog("open", $('.edit-panel .tabs > div[data-index="'
|
||||||
+ editors.data[i].id + "] > span:eq(0)").text());
|
+ editors.data[i].id + '"] > span:eq(0)').text());
|
||||||
$("#dialogCloseEditor").data("index", i);
|
$("#dialogCloseEditor").data("index", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -188,14 +178,22 @@ var editors = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
|
if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
|
||||||
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install',
|
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
|
||||||
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
|
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
|
||||||
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
|
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
|
||||||
'edit']);
|
'edit']);
|
||||||
|
|
||||||
|
// remove selected tree node
|
||||||
|
tree.fileTree.cancelSelectedNode();
|
||||||
|
wide.curNode = undefined;
|
||||||
|
wide.curEditor = undefined;
|
||||||
|
wide.refreshOutline();
|
||||||
|
$(".footer .cursor").text('');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextId) {
|
if (!nextId) {
|
||||||
// 不存在打开的编辑器
|
// 编辑器区域不存在打开的 Tab
|
||||||
// remove selected tree node
|
// remove selected tree node
|
||||||
tree.fileTree.cancelSelectedNode();
|
tree.fileTree.cancelSelectedNode();
|
||||||
wide.curNode = undefined;
|
wide.curNode = undefined;
|
||||||
|
@ -209,30 +207,6 @@ var editors = {
|
||||||
// 关闭的不是当前编辑器
|
// 关闭的不是当前编辑器
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set tree node selected
|
|
||||||
var node = tree.fileTree.getNodeByTId(nextId);
|
|
||||||
tree.fileTree.selectNode(node);
|
|
||||||
wide.curNode = node;
|
|
||||||
|
|
||||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
|
||||||
if (editors.data[i].id === nextId) {
|
|
||||||
wide.curEditor = editors.data[i].editor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wide.refreshOutline();
|
|
||||||
var cursor = wide.curEditor.getCursor();
|
|
||||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".edit-panel .tabs").on("dblclick", function () {
|
|
||||||
if ($(".toolbars .ico-max").length === 1) {
|
|
||||||
windows.maxEditor();
|
|
||||||
} else {
|
|
||||||
windows.restoreEditor();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -272,10 +246,9 @@ var editors = {
|
||||||
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
|
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
|
||||||
content: '<div id="startPage"></div>',
|
content: '<div id="startPage"></div>',
|
||||||
after: function () {
|
after: function () {
|
||||||
$("#startPage").height($('.side-right').height() - $(".bottom-window-group").children(".tabs").height() - 100);
|
$("#startPage").load('/start?sid=' + config.wideSessionId);
|
||||||
$("#startPage").load(config.context + '/start?sid=' + config.wideSessionId);
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "https://symphony.b3log.org/apis/articles?tags=wide,golang&p=1&size=30",
|
url: "https://ld246.com/apis/articles?tags=wide,golang&p=1&size=20",
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
jsonp: "callback",
|
jsonp: "callback",
|
||||||
|
@ -285,20 +258,21 @@ var editors = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 size = 30 取,但只保留最多 10 篇
|
// 按 size = 20 取,但只保留最多 9 篇
|
||||||
var length = articles.length;
|
var length = articles.length;
|
||||||
if (length > 10) {
|
if (length > 9) {
|
||||||
length = 10;
|
length = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
var listHTML = "<ul><li class='title'>" + config.label.community + "</li>";
|
var listHTML = "<ul><li class='title'>" + config.label.community +
|
||||||
|
"<a href='https://ld246.com/article/1437497122181' target='_blank' class='fn-right'>边看边练</li>";
|
||||||
for (var i = 0; i < length; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
var article = articles[i];
|
var article = articles[i];
|
||||||
listHTML += "<li>"
|
listHTML += "<li>"
|
||||||
+ "<a target='_blank' href='http://symphony.b3log.org"
|
+ "<a target='_blank' href='"
|
||||||
+ article.articlePermalink + "'>"
|
+ article.articlePermalink + "'>"
|
||||||
+ article.articleTitle + "</a> <span class='date'>"
|
+ article.articleTitle + "</a> <span class='date'>"
|
||||||
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd hh:mm');
|
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd');
|
||||||
+"</span></li>";
|
+"</span></li>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,11 +283,12 @@ var editors = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCurrentId: function () {
|
getCurrentId: function () {
|
||||||
var currentId = editors.tabs.getCurrentId();
|
var ret = editors.tabs.getCurrentId();
|
||||||
if (currentId === 'startPage') {
|
if (ret === 'startPage') {
|
||||||
currentId = null;
|
ret = null;
|
||||||
}
|
}
|
||||||
return currentId;
|
|
||||||
|
return ret;
|
||||||
},
|
},
|
||||||
getCurrentPath: function () {
|
getCurrentPath: function () {
|
||||||
var currentPath = $(".edit-panel .tabs .current span:eq(0)").attr("title");
|
var currentPath = $(".edit-panel .tabs .current span:eq(0)").attr("title");
|
||||||
|
@ -324,6 +299,7 @@ var editors = {
|
||||||
},
|
},
|
||||||
_initCodeMirrorHotKeys: function () {
|
_initCodeMirrorHotKeys: function () {
|
||||||
CodeMirror.registerHelper("hint", "go", function (editor) {
|
CodeMirror.registerHelper("hint", "go", function (editor) {
|
||||||
|
editor = wide.curEditor; // 使用当前编辑器覆盖实参,因为异步调用的原因,实参不一定正确
|
||||||
var word = /[\w$]+/;
|
var word = /[\w$]+/;
|
||||||
|
|
||||||
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
|
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
|
||||||
|
@ -344,10 +320,16 @@ var editors = {
|
||||||
|
|
||||||
var autocompleteHints = [];
|
var autocompleteHints = [];
|
||||||
|
|
||||||
|
if (editors.autocompleteMutex && editor.state.completionActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editors.autocompleteMutex = true;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: false, // 同步执行
|
async: false, // 同步执行
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: config.context + '/autocomplete',
|
url: '/autocomplete',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
@ -397,12 +379,15 @@ var editors = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除未保存状态
|
|
||||||
editor.doc.markClean();
|
editor.doc.markClean();
|
||||||
$(".edit-panel .tabs > div.current > span").removeClass("changed");
|
$(".edit-panel .tabs .current > span:eq(0)").removeClass("changed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
editors.autocompleteMutex = false;
|
||||||
|
}, 20);
|
||||||
|
|
||||||
return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
|
return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -450,17 +435,18 @@ var editors = {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: config.context + '/exprinfo',
|
url: '/exprinfo',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (result) {
|
||||||
if (!data.succ) {
|
if (0 != result.code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var position = wide.curEditor.cursorCoords();
|
var position = wide.curEditor.cursorCoords();
|
||||||
$("body").append('<div style="top:'
|
$("body").append('<div style="top:'
|
||||||
+ (position.top + 15) + 'px;left:' + position.left
|
+ (position.top + 15) + 'px;left:' + position.left
|
||||||
+ 'px" class="edit-exprinfo">' + data.info + '</div>');
|
+ 'px" class="edit-exprinfo">' + result.data + '</div>');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -597,14 +583,16 @@ var editors = {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: config.context + '/find/decl',
|
url: '/find/decl',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (result) {
|
||||||
if (!data.succ) {
|
if (0 != result.code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var data = result.data;
|
||||||
|
|
||||||
var tId = tree.getTIdByPath(data.path);
|
var tId = tree.getTIdByPath(data.path);
|
||||||
wide.curNode = tree.fileTree.getNodeByTId(tId);
|
wide.curNode = tree.fileTree.getNodeByTId(tId);
|
||||||
tree.fileTree.selectNode(wide.curNode);
|
tree.fileTree.selectNode(wide.curNode);
|
||||||
|
@ -625,15 +613,15 @@ var editors = {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: config.context + '/find/usages',
|
url: '/find/usages',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (result) {
|
||||||
if (!data.succ) {
|
if (0 != result.code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editors.appendSearch(data.founds, 'usages', '');
|
editors.appendSearch(result.data, 'usages', '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -735,7 +723,7 @@ var editors = {
|
||||||
},
|
},
|
||||||
// 新建一个编辑器 Tab,如果已经存在 Tab 则切换到该 Tab.
|
// 新建一个编辑器 Tab,如果已经存在 Tab 则切换到该 Tab.
|
||||||
newEditor: function (data, cursor) {
|
newEditor: function (data, cursor) {
|
||||||
var id = wide.curNode.tId;
|
var id = wide.curNode.id;
|
||||||
|
|
||||||
editors.tabs.add({
|
editors.tabs.add({
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -744,7 +732,7 @@ var editors = {
|
||||||
content: '<textarea id="editor' + id + '"></textarea>'
|
content: '<textarea id="editor' + id + '"></textarea>'
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install',
|
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
|
||||||
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
|
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
|
||||||
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
|
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
|
||||||
'edit']);
|
'edit']);
|
||||||
|
@ -763,9 +751,12 @@ var editors = {
|
||||||
theme: config.editorTheme,
|
theme: config.editorTheme,
|
||||||
tabSize: config.editorTabSize,
|
tabSize: config.editorTabSize,
|
||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
|
indentWithTabs: true,
|
||||||
foldGutter: true,
|
foldGutter: true,
|
||||||
cursorHeight: 1,
|
cursorHeight: 1,
|
||||||
path: data.path,
|
path: data.path,
|
||||||
|
readOnly: wide.curNode.isGOAPI,
|
||||||
|
profile: 'xhtml', // define Emmet output profile
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
"Ctrl-\\": "autocompleteAnyWord",
|
"Ctrl-\\": "autocompleteAnyWord",
|
||||||
".": "autocompleteAfterDot",
|
".": "autocompleteAfterDot",
|
||||||
|
@ -804,6 +795,10 @@ var editors = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ("text/html" === data.mode) {
|
||||||
|
emmetCodeMirror(editor);
|
||||||
|
}
|
||||||
|
|
||||||
editor.on('cursorActivity', function (cm) {
|
editor.on('cursorActivity', function (cm) {
|
||||||
$(".edit-exprinfo").remove();
|
$(".edit-exprinfo").remove();
|
||||||
var cursor = cm.getCursor();
|
var cursor = cm.getCursor();
|
||||||
|
@ -811,31 +806,59 @@ var editors = {
|
||||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.on('focus', function (cm) {
|
|
||||||
windows.clearFloat();
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.on('blur', function (cm) {
|
editor.on('blur', function (cm) {
|
||||||
$(".edit-exprinfo").remove();
|
$(".edit-exprinfo").remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.on('changes', function (cm) {
|
editor.on('changes', function (cm) {
|
||||||
if (cm.doc.isClean()) {
|
if (cm.doc.isClean()) { // no modification
|
||||||
// 没有修改过
|
|
||||||
$(".edit-panel .tabs > div").each(function () {
|
$(".edit-panel .tabs > div").each(function () {
|
||||||
var $span = $(this).find("span:eq(0)");
|
var $span = $(this).find("span:eq(0)");
|
||||||
if ($span.attr("title") === cm.options.path) {
|
if ($span.attr("title") === cm.options.path) {
|
||||||
$span.removeClass("changed");
|
$span.removeClass("changed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// 修改过
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// changed
|
||||||
|
|
||||||
$(".edit-panel .tabs > div").each(function () {
|
$(".edit-panel .tabs > div").each(function () {
|
||||||
var $span = $(this).find("span:eq(0)");
|
var $span = $(this).find("span:eq(0)");
|
||||||
if ($span.attr("title") === cm.options.path) {
|
if ($span.attr("title") === cm.options.path) {
|
||||||
$span.addClass("changed");
|
$span.addClass("changed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('keydown', function (cm, evt) {
|
||||||
|
if (evt.altKey || evt.ctrlKey || evt.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var k = evt.which;
|
||||||
|
|
||||||
|
if (k < 48) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hit [0-9]
|
||||||
|
|
||||||
|
if (k > 57 && k < 65) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hit [a-z]
|
||||||
|
|
||||||
|
if (k > 90) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.autocomplete) {
|
||||||
|
if (0.5 <= Math.random()) {
|
||||||
|
CodeMirror.commands.autocompleteAfterDot(cm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -843,6 +866,10 @@ var editors = {
|
||||||
editor.setOption("mode", data.mode);
|
editor.setOption("mode", data.mode);
|
||||||
editor.setOption("gutters", ["CodeMirror-lint-markers", "CodeMirror-foldgutter"]);
|
editor.setOption("gutters", ["CodeMirror-lint-markers", "CodeMirror-foldgutter"]);
|
||||||
|
|
||||||
|
if ("wide" !== config.keymap) {
|
||||||
|
editor.setOption("keyMap", config.keymap);
|
||||||
|
}
|
||||||
|
|
||||||
if ("text/x-go" === data.mode || "application/json" === data.mode) {
|
if ("text/x-go" === data.mode || "application/json" === data.mode) {
|
||||||
editor.setOption("lint", true);
|
editor.setOption("lint", true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014-2015, b3log.org
|
* Copyright (c) 2014-present, b3log.org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,6 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @file hotkeys.js
|
||||||
|
*
|
||||||
|
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||||
|
* @author <a href="http://88250.b3log.org">Liang Ding</a>
|
||||||
|
* @version 1.0.0.2, Dec 15, 2015
|
||||||
|
*/
|
||||||
var hotkeys = {
|
var hotkeys = {
|
||||||
defaultKeyMap: {
|
defaultKeyMap: {
|
||||||
// Ctrl-0
|
// Ctrl-0
|
||||||
|
@ -36,13 +43,9 @@ var hotkeys = {
|
||||||
which: 49,
|
which: 49,
|
||||||
fun: function () {
|
fun: function () {
|
||||||
// 有些元素需设置 tabindex 为 -1 时才可以 focus
|
// 有些元素需设置 tabindex 为 -1 时才可以 focus
|
||||||
if ($(".footer .ico-restore:eq(0)").css("display") === "inline") {
|
if (windows.outerLayout.west.state.isClosed) {
|
||||||
// 当文件树最小化时
|
windows.outerLayout.slideOpen('west');
|
||||||
$(".side").css({
|
|
||||||
"left": "0"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#files").focus();
|
$("#files").focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,11 +56,8 @@ var hotkeys = {
|
||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
which: 50,
|
which: 50,
|
||||||
fun: function () {
|
fun: function () {
|
||||||
if ($(".footer .ico-restore:eq(2)").css("display") === "inline") {
|
if (windows.innerLayout.east.state.isClosed) {
|
||||||
// 当文件树最小化时
|
windows.innerLayout.slideOpen('east');
|
||||||
$(".side-right").css({
|
|
||||||
"right": "0"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#outline").focus();
|
$("#outline").focus();
|
||||||
|
@ -426,19 +426,19 @@ var hotkeys = {
|
||||||
|| document.activeElement.className === "search") {
|
|| document.activeElement.className === "search") {
|
||||||
// 焦点在底部窗口组时,对底部进行切换
|
// 焦点在底部窗口组时,对底部进行切换
|
||||||
var tabs = ["output", "search", "notification"],
|
var tabs = ["output", "search", "notification"],
|
||||||
nextId = "";
|
nextPath = "";
|
||||||
for (var i = 0, ii = tabs.length; i < ii; i++) {
|
for (var i = 0, ii = tabs.length; i < ii; i++) {
|
||||||
if (document.activeElement.className === tabs[i]) {
|
if (bottomGroup.tabs.getCurrentId() === tabs[i]) {
|
||||||
if (i < ii - 1) {
|
if (i < ii - 1) {
|
||||||
nextId = tabs[i + 1];
|
nextPath = tabs[i + 1];
|
||||||
} else {
|
} else {
|
||||||
nextId = tabs[0];
|
nextPath = tabs[0];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bottomGroup.tabs.setCurrent(nextId);
|
bottomGroup.tabs.setCurrent(nextPath);
|
||||||
$(".bottom-window-group ." + nextId).focus();
|
$(".bottom-window-group ." + nextPath).focus();
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -446,16 +446,16 @@ var hotkeys = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editors.data.length > 1) {
|
if (editors.data.length > 1) {
|
||||||
var nextId = "";
|
var nextPath = "";
|
||||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
||||||
var currentId = editors.getCurrentId();
|
var currentId = editors.getCurrentId();
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
if (currentId === editors.data[i].id) {
|
if (currentId === editors.data[i].id) {
|
||||||
if (i < ii - 1) {
|
if (i < ii - 1) {
|
||||||
nextId = editors.data[i + 1].id;
|
nextPath = editors.data[i + 1].id;
|
||||||
wide.curEditor = editors.data[i + 1].editor;
|
wide.curEditor = editors.data[i + 1].editor;
|
||||||
} else {
|
} else {
|
||||||
nextId = editors.data[0].id;
|
nextPath = editors.data[0].id;
|
||||||
wide.curEditor = editors.data[0].editor;
|
wide.curEditor = editors.data[0].editor;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -463,8 +463,10 @@ var hotkeys = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editors.tabs.setCurrent(nextId);
|
editors.tabs.setCurrent(nextPath);
|
||||||
wide.curNode = tree.fileTree.getNodeByTId(nextId);
|
var nextTId = tree.getTIdByPath(nextPath);
|
||||||
|
wide.curNode = tree.fileTree.getNodeByTId(nextTId);
|
||||||
|
|
||||||
tree.fileTree.selectNode(wide.curNode);
|
tree.fileTree.selectNode(wide.curNode);
|
||||||
wide.refreshOutline();
|
wide.refreshOutline();
|
||||||
var cursor = wide.curEditor.getCursor();
|
var cursor = wide.curEditor.getCursor();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,159 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
var DEFAULT_BRACKETS = "()[]{}''\"\"";
|
|
||||||
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
|
|
||||||
var SPACE_CHAR_REGEX = /\s/;
|
|
||||||
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
|
||||||
if (old != CodeMirror.Init && old)
|
|
||||||
cm.removeKeyMap("autoCloseBrackets");
|
|
||||||
if (!val) return;
|
|
||||||
var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
|
|
||||||
if (typeof val == "string") pairs = val;
|
|
||||||
else if (typeof val == "object") {
|
|
||||||
if (val.pairs != null) pairs = val.pairs;
|
|
||||||
if (val.explode != null) explode = val.explode;
|
|
||||||
}
|
|
||||||
var map = buildKeymap(pairs);
|
|
||||||
if (explode) map.Enter = buildExplodeHandler(explode);
|
|
||||||
cm.addKeyMap(map);
|
|
||||||
});
|
|
||||||
|
|
||||||
function charsAround(cm, pos) {
|
|
||||||
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
|
||||||
Pos(pos.line, pos.ch + 1));
|
|
||||||
return str.length == 2 ? str : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Project the token type that will exists after the given char is
|
|
||||||
// typed, and use it to determine whether it would cause the start
|
|
||||||
// of a string token.
|
|
||||||
function enteringString(cm, pos, ch) {
|
|
||||||
var line = cm.getLine(pos.line);
|
|
||||||
var token = cm.getTokenAt(pos);
|
|
||||||
if (/\bstring2?\b/.test(token.type)) return false;
|
|
||||||
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
|
|
||||||
stream.pos = stream.start = token.start;
|
|
||||||
for (;;) {
|
|
||||||
var type1 = cm.getMode().token(stream, token.state);
|
|
||||||
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
|
|
||||||
stream.start = stream.pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildKeymap(pairs) {
|
|
||||||
var map = {
|
|
||||||
name : "autoCloseBrackets",
|
|
||||||
Backspace: function(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var around = charsAround(cm, ranges[i].head);
|
|
||||||
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
|
||||||
var cur = ranges[i].head;
|
|
||||||
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var closingBrackets = "";
|
|
||||||
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
|
|
||||||
closingBrackets += right;
|
|
||||||
map["'" + left + "'"] = function(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections(), type, next;
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var range = ranges[i], cur = range.head, curType;
|
|
||||||
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
|
||||||
if (!range.empty()) {
|
|
||||||
curType = "surround";
|
|
||||||
} else if (left == right && next == right) {
|
|
||||||
if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
|
|
||||||
curType = "skipThree";
|
|
||||||
else
|
|
||||||
curType = "skip";
|
|
||||||
} else if (left == right && cur.ch > 1 &&
|
|
||||||
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
|
|
||||||
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) {
|
|
||||||
curType = "addFour";
|
|
||||||
} else if (left == '"' || left == "'") {
|
|
||||||
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
|
|
||||||
else return CodeMirror.Pass;
|
|
||||||
} else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) {
|
|
||||||
curType = "both";
|
|
||||||
} else {
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
if (!type) type = curType;
|
|
||||||
else if (type != curType) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.operation(function() {
|
|
||||||
if (type == "skip") {
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
} else if (type == "skipThree") {
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
} else if (type == "surround") {
|
|
||||||
var sels = cm.getSelections();
|
|
||||||
for (var i = 0; i < sels.length; i++)
|
|
||||||
sels[i] = left + sels[i] + right;
|
|
||||||
cm.replaceSelections(sels, "around");
|
|
||||||
} else if (type == "both") {
|
|
||||||
cm.replaceSelection(left + right, null);
|
|
||||||
cm.execCommand("goCharLeft");
|
|
||||||
} else if (type == "addFour") {
|
|
||||||
cm.replaceSelection(left + left + left + left, "before");
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (left != right) map["'" + right + "'"] = function(cm) {
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var range = ranges[i];
|
|
||||||
if (!range.empty() ||
|
|
||||||
cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
};
|
|
||||||
})(pairs.charAt(i), pairs.charAt(i + 1));
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildExplodeHandler(pairs) {
|
|
||||||
return function(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var around = charsAround(cm, ranges[i].head);
|
|
||||||
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
cm.operation(function() {
|
|
||||||
cm.replaceSelection("\n\n", null);
|
|
||||||
cm.execCommand("goCharLeft");
|
|
||||||
ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var line = ranges[i].head.line;
|
|
||||||
cm.indentLine(line, null, true);
|
|
||||||
cm.indentLine(line + 1, null, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,86 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
|
|
||||||
|
|
||||||
//config settings
|
|
||||||
var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
|
|
||||||
scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
|
|
||||||
|
|
||||||
//inner modes
|
|
||||||
var scriptingMode, htmlMixedMode;
|
|
||||||
|
|
||||||
//tokenizer when in html mode
|
|
||||||
function htmlDispatch(stream, state) {
|
|
||||||
if (stream.match(scriptStartRegex, false)) {
|
|
||||||
state.token=scriptingDispatch;
|
|
||||||
return scriptingMode.token(stream, state.scriptState);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return htmlMixedMode.token(stream, state.htmlState);
|
|
||||||
}
|
|
||||||
|
|
||||||
//tokenizer when in scripting mode
|
|
||||||
function scriptingDispatch(stream, state) {
|
|
||||||
if (stream.match(scriptEndRegex, false)) {
|
|
||||||
state.token=htmlDispatch;
|
|
||||||
return htmlMixedMode.token(stream, state.htmlState);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return scriptingMode.token(stream, state.scriptState);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
startState: function() {
|
|
||||||
scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
|
|
||||||
htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
|
|
||||||
return {
|
|
||||||
token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
|
|
||||||
htmlState : CodeMirror.startState(htmlMixedMode),
|
|
||||||
scriptState : CodeMirror.startState(scriptingMode)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
token: function(stream, state) {
|
|
||||||
return state.token(stream, state);
|
|
||||||
},
|
|
||||||
|
|
||||||
indent: function(state, textAfter) {
|
|
||||||
if (state.token == htmlDispatch)
|
|
||||||
return htmlMixedMode.indent(state.htmlState, textAfter);
|
|
||||||
else if (scriptingMode.indent)
|
|
||||||
return scriptingMode.indent(state.scriptState, textAfter);
|
|
||||||
},
|
|
||||||
|
|
||||||
copyState: function(state) {
|
|
||||||
return {
|
|
||||||
token : state.token,
|
|
||||||
htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
|
|
||||||
scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
innerMode: function(state) {
|
|
||||||
if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
|
|
||||||
else return {state: state.htmlState, mode: htmlMixedMode};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, "htmlmixed");
|
|
||||||
|
|
||||||
CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
|
|
||||||
CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
|
|
||||||
CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
|
|
||||||
CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"});
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,221 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Smarty 2 and 3 mode.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineMode("smarty", function(config) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// our default settings; check to see if they're overridden
|
|
||||||
var settings = {
|
|
||||||
rightDelimiter: '}',
|
|
||||||
leftDelimiter: '{',
|
|
||||||
smartyVersion: 2 // for backward compatibility
|
|
||||||
};
|
|
||||||
if (config.hasOwnProperty("leftDelimiter")) {
|
|
||||||
settings.leftDelimiter = config.leftDelimiter;
|
|
||||||
}
|
|
||||||
if (config.hasOwnProperty("rightDelimiter")) {
|
|
||||||
settings.rightDelimiter = config.rightDelimiter;
|
|
||||||
}
|
|
||||||
if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) {
|
|
||||||
settings.smartyVersion = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyFunctions = ["debug", "extends", "function", "include", "literal"];
|
|
||||||
var last;
|
|
||||||
var regs = {
|
|
||||||
operatorChars: /[+\-*&%=<>!?]/,
|
|
||||||
validIdentifier: /[a-zA-Z0-9_]/,
|
|
||||||
stringChar: /['"]/
|
|
||||||
};
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
cont: function(style, lastType) {
|
|
||||||
last = lastType;
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
chain: function(stream, state, parser) {
|
|
||||||
state.tokenize = parser;
|
|
||||||
return parser(stream, state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// our various parsers
|
|
||||||
var parsers = {
|
|
||||||
|
|
||||||
// the main tokenizer
|
|
||||||
tokenizer: function(stream, state) {
|
|
||||||
if (stream.match(settings.leftDelimiter, true)) {
|
|
||||||
if (stream.eat("*")) {
|
|
||||||
return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
|
|
||||||
} else {
|
|
||||||
// Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
|
|
||||||
state.depth++;
|
|
||||||
var isEol = stream.eol();
|
|
||||||
var isFollowedByWhitespace = /\s/.test(stream.peek());
|
|
||||||
if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) {
|
|
||||||
state.depth--;
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
state.tokenize = parsers.smarty;
|
|
||||||
last = "startTag";
|
|
||||||
return "tag";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stream.next();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// parsing Smarty content
|
|
||||||
smarty: function(stream, state) {
|
|
||||||
if (stream.match(settings.rightDelimiter, true)) {
|
|
||||||
if (settings.smartyVersion === 3) {
|
|
||||||
state.depth--;
|
|
||||||
if (state.depth <= 0) {
|
|
||||||
state.tokenize = parsers.tokenizer;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.tokenize = parsers.tokenizer;
|
|
||||||
}
|
|
||||||
return helpers.cont("tag", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream.match(settings.leftDelimiter, true)) {
|
|
||||||
state.depth++;
|
|
||||||
return helpers.cont("tag", "startTag");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ch = stream.next();
|
|
||||||
if (ch == "$") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("variable-2", "variable");
|
|
||||||
} else if (ch == "|") {
|
|
||||||
return helpers.cont("operator", "pipe");
|
|
||||||
} else if (ch == ".") {
|
|
||||||
return helpers.cont("operator", "property");
|
|
||||||
} else if (regs.stringChar.test(ch)) {
|
|
||||||
state.tokenize = parsers.inAttribute(ch);
|
|
||||||
return helpers.cont("string", "string");
|
|
||||||
} else if (regs.operatorChars.test(ch)) {
|
|
||||||
stream.eatWhile(regs.operatorChars);
|
|
||||||
return helpers.cont("operator", "operator");
|
|
||||||
} else if (ch == "[" || ch == "]") {
|
|
||||||
return helpers.cont("bracket", "bracket");
|
|
||||||
} else if (ch == "(" || ch == ")") {
|
|
||||||
return helpers.cont("bracket", "operator");
|
|
||||||
} else if (/\d/.test(ch)) {
|
|
||||||
stream.eatWhile(/\d/);
|
|
||||||
return helpers.cont("number", "number");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (state.last == "variable") {
|
|
||||||
if (ch == "@") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("property", "property");
|
|
||||||
} else if (ch == "|") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("qualifier", "modifier");
|
|
||||||
}
|
|
||||||
} else if (state.last == "pipe") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("qualifier", "modifier");
|
|
||||||
} else if (state.last == "whitespace") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("attribute", "modifier");
|
|
||||||
} if (state.last == "property") {
|
|
||||||
stream.eatWhile(regs.validIdentifier);
|
|
||||||
return helpers.cont("property", null);
|
|
||||||
} else if (/\s/.test(ch)) {
|
|
||||||
last = "whitespace";
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var str = "";
|
|
||||||
if (ch != "/") {
|
|
||||||
str += ch;
|
|
||||||
}
|
|
||||||
var c = null;
|
|
||||||
while (c = stream.eat(regs.validIdentifier)) {
|
|
||||||
str += c;
|
|
||||||
}
|
|
||||||
for (var i=0, j=keyFunctions.length; i<j; i++) {
|
|
||||||
if (keyFunctions[i] == str) {
|
|
||||||
return helpers.cont("keyword", "keyword");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (/\s/.test(ch)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return helpers.cont("tag", "tag");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
inAttribute: function(quote) {
|
|
||||||
return function(stream, state) {
|
|
||||||
var prevChar = null;
|
|
||||||
var currChar = null;
|
|
||||||
while (!stream.eol()) {
|
|
||||||
currChar = stream.peek();
|
|
||||||
if (stream.next() == quote && prevChar !== '\\') {
|
|
||||||
state.tokenize = parsers.smarty;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prevChar = currChar;
|
|
||||||
}
|
|
||||||
return "string";
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
inBlock: function(style, terminator) {
|
|
||||||
return function(stream, state) {
|
|
||||||
while (!stream.eol()) {
|
|
||||||
if (stream.match(terminator)) {
|
|
||||||
state.tokenize = parsers.tokenizer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stream.next();
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// the public API for CodeMirror
|
|
||||||
return {
|
|
||||||
startState: function() {
|
|
||||||
return {
|
|
||||||
tokenize: parsers.tokenizer,
|
|
||||||
mode: "smarty",
|
|
||||||
last: null,
|
|
||||||
depth: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
token: function(stream, state) {
|
|
||||||
var style = state.tokenize(stream, state);
|
|
||||||
state.last = last;
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
electricChars: ""
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineMIME("text/x-smarty", "smarty");
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,114 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
|
|
||||||
<title>CodeMirror: Smarty mixed mode</title>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<link rel=stylesheet href="../../doc/docs.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="../../lib/codemirror.css">
|
|
||||||
<script src="../../lib/codemirror.js"></script>
|
|
||||||
<script src="../../mode/xml/xml.js"></script>
|
|
||||||
<script src="../../mode/javascript/javascript.js"></script>
|
|
||||||
<script src="../../mode/css/css.js"></script>
|
|
||||||
<script src="../../mode/htmlmixed/htmlmixed.js"></script>
|
|
||||||
<script src="../../mode/smarty/smarty.js"></script>
|
|
||||||
<script src="../../mode/smartymixed/smartymixed.js"></script>
|
|
||||||
<div id=nav>
|
|
||||||
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="../../index.html">Home</a>
|
|
||||||
<li><a href="../../doc/manual.html">Manual</a>
|
|
||||||
<li><a href="https://github.com/codemirror/codemirror">Code</a>
|
|
||||||
</ul>
|
|
||||||
<ul>
|
|
||||||
<li><a href="../index.html">Language modes</a>
|
|
||||||
<li><a class=active href="#">Smarty mixed</a>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<h2>Smarty mixed mode</h2>
|
|
||||||
<form><textarea id="code" name="code">
|
|
||||||
{**
|
|
||||||
* @brief Smarty mixed mode
|
|
||||||
* @author Ruslan Osmanov
|
|
||||||
* @date 29.06.2013
|
|
||||||
*}
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>{$title|htmlspecialchars|truncate:30}</title>
|
|
||||||
</head>
|
|
||||||
<body class="{$bodyclass}">
|
|
||||||
{* Multiline smarty
|
|
||||||
* comment, no {$variables} here
|
|
||||||
*}
|
|
||||||
{literal}
|
|
||||||
{literal} is just an HTML text.
|
|
||||||
<script type="text/javascript">//<![CDATA[
|
|
||||||
var a = {$just_a_normal_js_object : "value"};
|
|
||||||
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("code"), {
|
|
||||||
mode : "smartymixed",
|
|
||||||
tabSize : 2,
|
|
||||||
indentUnit : 2,
|
|
||||||
indentWithTabs : false,
|
|
||||||
lineNumbers : true,
|
|
||||||
smartyVersion : 3
|
|
||||||
});
|
|
||||||
// ]]>
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
/* CSS content
|
|
||||||
{$no_smarty} */
|
|
||||||
.some-class { font-weight: bolder; color: "orange"; }
|
|
||||||
</style>
|
|
||||||
{/literal}
|
|
||||||
|
|
||||||
{extends file="parent.tpl"}
|
|
||||||
{include file="template.tpl"}
|
|
||||||
|
|
||||||
{* some example Smarty content *}
|
|
||||||
{if isset($name) && $name == 'Blog'}
|
|
||||||
This is a {$var}.
|
|
||||||
{$integer = 4511}, {$array[] = "a"}, {$stringvar = "string"}
|
|
||||||
{$integer = 4512} {$array[] = "a"} {$stringvar = "string"}
|
|
||||||
{assign var='bob' value=$var.prop}
|
|
||||||
{elseif $name == $foo}
|
|
||||||
{function name=menu level=0}
|
|
||||||
{foreach $data as $entry}
|
|
||||||
{if is_array($entry)}
|
|
||||||
- {$entry@key}
|
|
||||||
{menu data=$entry level=$level+1}
|
|
||||||
{else}
|
|
||||||
{$entry}
|
|
||||||
{* One
|
|
||||||
* Two
|
|
||||||
* Three
|
|
||||||
*}
|
|
||||||
{/if}
|
|
||||||
{/foreach}
|
|
||||||
{/function}
|
|
||||||
{/if}
|
|
||||||
</body>
|
|
||||||
<!-- R.O. -->
|
|
||||||
</html>
|
|
||||||
</textarea></form>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("code"), {
|
|
||||||
mode : "smartymixed",
|
|
||||||
tabSize : 2,
|
|
||||||
indentUnit : 2,
|
|
||||||
indentWithTabs : false,
|
|
||||||
lineNumbers : true,
|
|
||||||
smartyVersion : 3,
|
|
||||||
matchBrackets : true,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<p>The Smarty mixed mode depends on the Smarty and HTML mixed modes. HTML
|
|
||||||
mixed mode itself depends on XML, JavaScript, and CSS modes.</p>
|
|
||||||
|
|
||||||
<p>It takes the same options, as Smarty and HTML mixed modes.</p>
|
|
||||||
|
|
||||||
<p><strong>MIME types defined:</strong> <code>text/x-smarty</code>.</p>
|
|
||||||
</article>
|
|
|
@ -1,197 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file smartymixed.js
|
|
||||||
* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML)
|
|
||||||
* @author Ruslan Osmanov <rrosmanov at gmail dot com>
|
|
||||||
* @version 3.0
|
|
||||||
* @date 05.07.2013
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Warning: Don't base other modes on this one. This here is a
|
|
||||||
// terrible way to write a mixed mode.
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../smarty/smarty"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineMode("smartymixed", function(config) {
|
|
||||||
var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
|
|
||||||
var smartyMode = CodeMirror.getMode(config, "smarty");
|
|
||||||
|
|
||||||
var settings = {
|
|
||||||
rightDelimiter: '}',
|
|
||||||
leftDelimiter: '{'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.hasOwnProperty("leftDelimiter")) {
|
|
||||||
settings.leftDelimiter = config.leftDelimiter;
|
|
||||||
}
|
|
||||||
if (config.hasOwnProperty("rightDelimiter")) {
|
|
||||||
settings.rightDelimiter = config.rightDelimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }
|
|
||||||
|
|
||||||
var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter);
|
|
||||||
var regs = {
|
|
||||||
smartyComment: new RegExp("^" + reRight + "\\*"),
|
|
||||||
literalOpen: new RegExp(reLeft + "literal" + reRight),
|
|
||||||
literalClose: new RegExp(reLeft + "\/literal" + reRight),
|
|
||||||
hasLeftDelimeter: new RegExp(".*" + reLeft),
|
|
||||||
htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft)
|
|
||||||
};
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
chain: function(stream, state, parser) {
|
|
||||||
state.tokenize = parser;
|
|
||||||
return parser(stream, state);
|
|
||||||
},
|
|
||||||
|
|
||||||
cleanChain: function(stream, state, parser) {
|
|
||||||
state.tokenize = null;
|
|
||||||
state.localState = null;
|
|
||||||
state.localMode = null;
|
|
||||||
return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
|
|
||||||
},
|
|
||||||
|
|
||||||
maybeBackup: function(stream, pat, style) {
|
|
||||||
var cur = stream.current();
|
|
||||||
var close = cur.search(pat),
|
|
||||||
m;
|
|
||||||
if (close > - 1) stream.backUp(cur.length - close);
|
|
||||||
else if (m = cur.match(/<\/?$/)) {
|
|
||||||
stream.backUp(cur.length);
|
|
||||||
if (!stream.match(pat, false)) stream.match(cur[0]);
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var parsers = {
|
|
||||||
html: function(stream, state) {
|
|
||||||
var htmlTagName = state.htmlMixedState.htmlState.context && state.htmlMixedState.htmlState.context.tagName
|
|
||||||
? state.htmlMixedState.htmlState.context.tagName
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && htmlTagName === null) {
|
|
||||||
state.tokenize = parsers.smarty;
|
|
||||||
state.localMode = smartyMode;
|
|
||||||
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
|
|
||||||
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
|
|
||||||
} else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
|
|
||||||
state.tokenize = parsers.smarty;
|
|
||||||
state.localMode = smartyMode;
|
|
||||||
state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
|
|
||||||
return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
|
|
||||||
}
|
|
||||||
return htmlMixedMode.token(stream, state.htmlMixedState);
|
|
||||||
},
|
|
||||||
|
|
||||||
smarty: function(stream, state) {
|
|
||||||
if (stream.match(settings.leftDelimiter, false)) {
|
|
||||||
if (stream.match(regs.smartyComment, false)) {
|
|
||||||
return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
|
|
||||||
}
|
|
||||||
} else if (stream.match(settings.rightDelimiter, false)) {
|
|
||||||
stream.eat(settings.rightDelimiter);
|
|
||||||
state.tokenize = parsers.html;
|
|
||||||
state.localMode = htmlMixedMode;
|
|
||||||
state.localState = state.htmlMixedState;
|
|
||||||
return "tag";
|
|
||||||
}
|
|
||||||
|
|
||||||
return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
|
|
||||||
},
|
|
||||||
|
|
||||||
inBlock: function(style, terminator) {
|
|
||||||
return function(stream, state) {
|
|
||||||
while (!stream.eol()) {
|
|
||||||
if (stream.match(terminator)) {
|
|
||||||
helpers.cleanChain(stream, state, "");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stream.next();
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
startState: function() {
|
|
||||||
var state = htmlMixedMode.startState();
|
|
||||||
return {
|
|
||||||
token: parsers.html,
|
|
||||||
localMode: null,
|
|
||||||
localState: null,
|
|
||||||
htmlMixedState: state,
|
|
||||||
tokenize: null,
|
|
||||||
inLiteral: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
copyState: function(state) {
|
|
||||||
var local = null, tok = (state.tokenize || state.token);
|
|
||||||
if (state.localState) {
|
|
||||||
local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
token: state.token,
|
|
||||||
tokenize: state.tokenize,
|
|
||||||
localMode: state.localMode,
|
|
||||||
localState: local,
|
|
||||||
htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
|
|
||||||
inLiteral: state.inLiteral
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
token: function(stream, state) {
|
|
||||||
if (stream.match(settings.leftDelimiter, false)) {
|
|
||||||
if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
|
|
||||||
state.inLiteral = true;
|
|
||||||
return "keyword";
|
|
||||||
} else if (state.inLiteral && stream.match(regs.literalClose, true)) {
|
|
||||||
state.inLiteral = false;
|
|
||||||
return "keyword";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state.inLiteral && state.localState != state.htmlMixedState) {
|
|
||||||
state.tokenize = parsers.html;
|
|
||||||
state.localMode = htmlMixedMode;
|
|
||||||
state.localState = state.htmlMixedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
var style = (state.tokenize || state.token)(stream, state);
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
|
|
||||||
indent: function(state, textAfter) {
|
|
||||||
if (state.localMode == smartyMode
|
|
||||||
|| (state.inLiteral && !state.localMode)
|
|
||||||
|| regs.hasLeftDelimeter.test(textAfter)) {
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
return htmlMixedMode.indent(state.htmlMixedState, textAfter);
|
|
||||||
},
|
|
||||||
|
|
||||||
innerMode: function(state) {
|
|
||||||
return {
|
|
||||||
state: state.localState || state.htmlMixedState,
|
|
||||||
mode: state.localMode || htmlMixedMode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, "htmlmixed", "smarty");
|
|
||||||
|
|
||||||
CodeMirror.defineMIME("text/x-smarty", "smartymixed");
|
|
||||||
// vim: et ts=2 sts=2 sw=2
|
|
||||||
|
|
||||||
});
|
|
|
@ -58,8 +58,10 @@
|
||||||
if (inp) {
|
if (inp) {
|
||||||
if (options.value) {
|
if (options.value) {
|
||||||
inp.value = options.value;
|
inp.value = options.value;
|
||||||
|
if (options.selectValueOnOpen !== false) {
|
||||||
inp.select();
|
inp.select();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.onInput)
|
if (options.onInput)
|
||||||
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue