Merge pull request #1 from b3log/master

merged with newer
This commit is contained in:
Qiao 2014-11-26 23:00:58 +08:00
commit 3602d8a987
77 changed files with 3110 additions and 1578 deletions

11
.gitignore vendored
View File

@ -1,12 +1,7 @@
/wide.exe /wide.exe
/wide /wide
/main
/packer
/data/workspace/bin/ /static/user/admin/style.css
/data/workspace/pkg/
/data/workspace/src/
/data/user_workspaces/*/bin/ /header
/data/user_workspaces/*/pkg/ /header.exe
/data/user_workspaces/*/src/**/*.exe

View File

@ -1,11 +1,10 @@
author: DL88250@gmail.com author: DL88250@gmail.com
description: A Web IDE for Teams using Golang. description: A Web-based IDE for Teams using Golang.
filesets: filesets:
depth: 10 depth: 10
includes: includes:
- conf - conf
- data
- doc - doc
- i18n - i18n
- static - static

20
.header.json Normal file
View File

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

13
.header.txt Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) {{.Year}}, {{.Owner}}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
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.

17
Dockerfile Normal file
View File

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

View File

@ -1,5 +1,7 @@
# Wide [![Build Status](https://drone.io/github.com/b3log/wide/status.png)](https://drone.io/github.com/b3log/wide/latest) # Wide [![Build Status](https://drone.io/github.com/b3log/wide/status.png)](https://drone.io/github.com/b3log/wide/latest)
_Have a [**try**](http://121.41.106.121:7070/signup) first!_
## Intro ## Intro
A <b>W</b>eb-based <b>IDE</b> for Teams using Golang. A <b>W</b>eb-based <b>IDE</b> for Teams using Golang.
@ -98,17 +100,22 @@ We have provided OS-specific executable binary as follows:
* windows-amd64/386 * windows-amd64/386
* darwin-amd64/386 * darwin-amd64/386
Download [here](http://pan.baidu.com/s/1dD3XwOT)! Download [HERE](http://pan.baidu.com/s/1dD3XwOT)!
### Build Wide for yourself ### Build Wide for yourself
1. Download source or by `git clone` 1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone`
2. Get dependencies with 2. Get dependencies with
* `go get -u` * `go get`
* `go get -u github.com/88250/ide_stub` * `go get github.com/88250/ide_stub`
* `go get -u github.com/nsf/gocode` * `go get github.com/nsf/gocode`
3. Compile wide with `go build` 3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -u wide -p {ip}:{port}:7070 88250/wide:latest ./wide -docker=true -channel=ws://{ip}:{port}`
## Known Issues ## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32) * [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
@ -127,8 +134,9 @@ Licensed under the [Apache License 2.0](https://github.com/b3log/wide/blob/maste
* [LiteIDE](https://github.com/visualfc/liteide) * [LiteIDE](https://github.com/visualfc/liteide)
* [gocode](https://github.com/nsf/gocode) * [gocode](https://github.com/nsf/gocode)
* [Gorilla](https://github.com/gorilla) * [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" /> <img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />
</center>

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Configurations manipulations, all configurations (including user configurations) are stored in wide.json. // Configurations manipulations, all configurations (including user configurations) are stored in wide.json.
package conf package conf
@ -7,7 +21,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "strconv"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@ -51,23 +65,24 @@ type User struct {
type Editor struct { type Editor struct {
FontFamily string FontFamily string
FontSize string FontSize string
LineHeight string
} }
// Configuration. // Configuration.
type conf struct { type conf struct {
IP string // server ip, ${ip} IP string // server ip, ${ip}
Server string // server host and port ({IP}:7070) Port string // server port
StaticServer string // static resources server scheme, host and port (http://{IP}:7070) Server string // server host and port ({IP}:{Port})
EditorChannel string // editor channel (ws://{IP}:7070) StaticServer string // static resources server scheme, host and port (http://{IP}:{Port})
OutputChannel string // output channel (ws://{IP}:7070) EditorChannel string // editor channel (ws://{IP}:{Port})
ShellChannel string // shell channel(ws://{IP}:7070) OutputChannel string // output channel (ws://{IP}:{Port})
SessionChannel string // wide session channel (ws://{IP}:7070) ShellChannel string // shell channel(ws://{IP}:{Port})
SessionChannel string // wide session channel (ws://{IP}:{Port})
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 MaxProcs int // Go max procs
RuntimeMode string // runtime mode (dev/prod) RuntimeMode string // runtime mode (dev/prod)
WD string // current working direcitory, ${pwd} WD string // current working direcitory, ${pwd}
Workspace string // path of master workspace
Locale string // default locale Locale string // default locale
Users []*User // configurations of users Users []*User // configurations of users
} }
@ -95,28 +110,31 @@ func FixedTimeCheckEnv() {
} }
func checkEnv() { func checkEnv() {
cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput()
if nil != err {
glog.Fatal("Not found 'go' command, please make sure Go has been installed correctly")
os.Exit(-1)
}
glog.V(5).Info(string(buf))
if "" == os.Getenv("GOPATH") { if "" == os.Getenv("GOPATH") {
glog.Fatal("Not found $GOPATH, please configure it before running Wide") glog.Fatal("Not found $GOPATH, please configure it before running Wide")
os.Exit(-1) os.Exit(-1)
} }
if "" == os.Getenv("GOROOT") { gocode := util.Go.GetExecutableInGOBIN("gocode")
glog.Fatal("Not found $GOROOT, please configure it before running Wide") cmd = exec.Command(gocode, "close")
_, err = cmd.Output()
os.Exit(-1)
}
gocode := Wide.GetExecutableInGOBIN("gocode")
cmd := exec.Command(gocode, "close")
_, err := cmd.Output()
if nil != err { if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound} event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
glog.Warningf("Not found gocode [%s]", gocode) glog.Warningf("Not found gocode [%s]", gocode)
} }
ide_stub := Wide.GetExecutableInGOBIN("ide_stub") ide_stub := util.Go.GetExecutableInGOBIN("ide_stub")
cmd = exec.Command(ide_stub, "version") cmd = exec.Command(ide_stub, "version")
_, err = cmd.Output() _, err = cmd.Output()
if nil != err { if nil != err {
@ -141,25 +159,14 @@ func FixedTimeSave() {
func (c *conf) GetUserWorkspace(username string) string { func (c *conf) GetUserWorkspace(username string) string {
for _, user := range c.Users { for _, user := range c.Users {
if user.Name == username { if user.Name == username {
ret := strings.Replace(user.Workspace, "{WD}", c.WD, 1) return user.GetWorkspace()
return filepath.FromSlash(ret)
} }
} }
return "" return ""
} }
// GetWorkspace gets the master workspace path. // GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
//
// Compared to the use of Wide.Workspace, this function will be processed as follows:
// 1. Replace {WD} variable with the actual directory path
// 2. Replace "/" with "\\" (Windows)
func (c *conf) GetWorkspace() string {
return filepath.FromSlash(strings.Replace(c.Workspace, "{WD}", c.WD, 1))
}
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found.
func (c *conf) GetGoFmt(username string) string { func (c *conf) GetGoFmt(username string) string {
for _, user := range c.Users { for _, user := range c.Users {
if user.Name == username { if user.Name == username {
@ -167,7 +174,7 @@ func (c *conf) GetGoFmt(username string) string {
case "gofmt": case "gofmt":
return "gofmt" return "gofmt"
case "goimports": case "goimports":
return c.GetExecutableInGOBIN("goimports") return util.Go.GetExecutableInGOBIN("goimports")
default: default:
glog.Errorf("Unsupported Go Format tool [%s]", user.GoFormat) glog.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
return "gofmt" return "gofmt"
@ -182,9 +189,13 @@ func (c *conf) GetGoFmt(username string) string {
// //
// 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 "/" with "\\" (Windows) // 2. Replace ${GOPATH} with enviorment variable GOPATH
// 3. Replace "/" with "\\" (Windows)
func (u *User) GetWorkspace() string { func (u *User) GetWorkspace() string {
return filepath.FromSlash(strings.Replace(u.Workspace, "{WD}", Wide.WD, 1)) w := strings.Replace(u.Workspace, "{WD}", Wide.WD, 1)
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
return filepath.FromSlash(w)
} }
// 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 username, returns nil if not found.
@ -198,42 +209,6 @@ func (*conf) GetUser(username string) *User {
return nil return nil
} }
// GetExecutableInGOBIN gets executable file under GOBIN path.
//
// The specified executable should not with extension, this function will append .exe if on Windows.
func (*conf) GetExecutableInGOBIN(executable string) string {
if util.OS.IsWindows() {
executable += ".exe"
}
gopaths := filepath.SplitList(os.Getenv("GOPATH"))
for _, gopath := range gopaths {
// $GOPATH/bin/$GOOS_$GOARCH/executable
ret := gopath + PathSeparator + "bin" + PathSeparator +
os.Getenv("GOOS") + "_" + os.Getenv("GOARCH") + PathSeparator + executable
if isExist(ret) {
return ret
}
// $GOPATH/bin/{runtime.GOOS}_{runtime.GOARCH}/executable
ret = gopath + PathSeparator + "bin" + PathSeparator +
runtime.GOOS + "_" + runtime.GOARCH + PathSeparator + executable
if isExist(ret) {
return ret
}
// $GOPATH/bin/executable
ret = gopath + PathSeparator + "bin" + PathSeparator + executable
if isExist(ret) {
return ret
}
}
// $GOBIN/executable
return os.Getenv("GOBIN") + PathSeparator + executable
}
// Save saves Wide configurations. // Save saves Wide configurations.
func Save() bool { func Save() bool {
// just the Users field are volatile // just the Users field are volatile
@ -258,12 +233,12 @@ func Save() bool {
} }
// Load loads the configurations from wide.json. // Load loads the configurations from wide.json.
func Load() { func Load(confPath, confIP, confPort, confServer, confChannel string, confDocker bool) {
bytes, _ := ioutil.ReadFile("conf/wide.json") bytes, _ := ioutil.ReadFile(confPath)
err := json.Unmarshal(bytes, &Wide) err := json.Unmarshal(bytes, &Wide)
if err != nil { if err != nil {
glog.Error(err) glog.Error("Parses wide.json error: ", err)
os.Exit(-1) os.Exit(-1)
} }
@ -271,6 +246,11 @@ func Load() {
// keep the raw content // keep the raw content
json.Unmarshal(bytes, &rawWide) json.Unmarshal(bytes, &rawWide)
// Working Driectory
Wide.WD = util.OS.Pwd()
glog.V(5).Infof("${pwd} [%s]", Wide.WD)
// IP
ip, err := util.Net.LocalIP() ip, err := util.Net.LocalIP()
if err != nil { if err != nil {
glog.Error(err) glog.Error(err)
@ -280,17 +260,54 @@ func Load() {
glog.V(5).Infof("${ip} [%s]", ip) glog.V(5).Infof("${ip} [%s]", ip)
Wide.WD = util.OS.Pwd() if confDocker {
glog.V(5).Infof("${pwd} [%s]", Wide.WD) // TODO: may be we need to do something here
}
if "" != confIP {
ip = confIP
}
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1) Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
if "" != confPort {
Wide.Port = confPort
}
// Server
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1) Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
if "" != confServer {
Wide.Server = confServer
}
// Static Server
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1) Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", strconv.FormatInt(time.Now().UnixNano(), 10), 1)
// Channels
Wide.EditorChannel = strings.Replace(Wide.EditorChannel, "{IP}", Wide.IP, 1) Wide.EditorChannel = strings.Replace(Wide.EditorChannel, "{IP}", Wide.IP, 1)
if "" != confChannel {
Wide.EditorChannel = confChannel
}
Wide.OutputChannel = strings.Replace(Wide.OutputChannel, "{IP}", Wide.IP, 1) Wide.OutputChannel = strings.Replace(Wide.OutputChannel, "{IP}", Wide.IP, 1)
if "" != confChannel {
Wide.OutputChannel = confChannel
}
Wide.ShellChannel = strings.Replace(Wide.ShellChannel, "{IP}", Wide.IP, 1) Wide.ShellChannel = strings.Replace(Wide.ShellChannel, "{IP}", Wide.IP, 1)
if "" != confChannel {
Wide.ShellChannel = confChannel
}
Wide.SessionChannel = strings.Replace(Wide.SessionChannel, "{IP}", Wide.IP, 1) Wide.SessionChannel = strings.Replace(Wide.SessionChannel, "{IP}", Wide.IP, 1)
if "" != confChannel {
Wide.SessionChannel = confChannel
}
Wide.Server = strings.Replace(Wide.Server, "{Port}", Wide.Port, 1)
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{Port}", Wide.Port, 1)
Wide.EditorChannel = strings.Replace(Wide.EditorChannel, "{Port}", Wide.Port, 1)
Wide.OutputChannel = strings.Replace(Wide.OutputChannel, "{Port}", Wide.Port, 1)
Wide.ShellChannel = strings.Replace(Wide.ShellChannel, "{Port}", Wide.Port, 1)
Wide.SessionChannel = strings.Replace(Wide.SessionChannel, "{Port}", Wide.Port, 1)
glog.V(5).Info("Conf: \n" + string(bytes)) glog.V(5).Info("Conf: \n" + string(bytes))
@ -331,7 +348,7 @@ func UpdateCustomizedConf(username string) {
wd := util.OS.Pwd() wd := util.OS.Pwd()
dir := filepath.Clean(wd + "/static/user/" + u.Name) dir := filepath.Clean(wd + "/static/user/" + u.Name)
if err := os.MkdirAll(dir, 0664); nil != err { if err := os.MkdirAll(dir, 0755); nil != err {
glog.Error(err) glog.Error(err)
os.Exit(-1) os.Exit(-1)
@ -353,11 +370,11 @@ func UpdateCustomizedConf(username string) {
} }
} }
// initWorkspaceDirs initializes the directories of master workspace, users' workspaces. // initWorkspaceDirs initializes the directories of users' workspaces.
// //
// Creates directories if not found on path of workspace. // Creates directories if not found on path of workspace.
func initWorkspaceDirs() { func initWorkspaceDirs() {
paths := filepath.SplitList(Wide.GetWorkspace()) paths := []string{}
for _, user := range Wide.Users { for _, user := range Wide.Users {
paths = append(paths, filepath.SplitList(user.GetWorkspace())...) paths = append(paths, filepath.SplitList(user.GetWorkspace())...)
@ -383,7 +400,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 !isExist(path) { if !util.File.IsExist(path) {
if err := os.MkdirAll(path, 0775); nil != err { if err := os.MkdirAll(path, 0775); nil != err {
glog.Error(err) glog.Error(err)
@ -393,10 +410,3 @@ func createDir(path string) {
glog.V(7).Infof("Created a directory [%s]", path) glog.V(7).Infof("Created a directory [%s]", path)
} }
} }
// isExist determines whether the file spcified by the given filename is exists.
func isExist(filename string) bool {
_, err := os.Stat(filename)
return err == nil || os.IsExist(err)
}

View File

@ -1,40 +1,36 @@
{ {
"IP": "${ip}", "IP": "${ip}",
"Server": "{IP}:7070", "Port": "7070",
"StaticServer": "http://{IP}:7070", "Server": "{IP}:{Port}",
"EditorChannel": "ws://{IP}:7070", "StaticServer": "",
"OutputChannel": "ws://{IP}:7070", "EditorChannel": "ws://{IP}:{Port}",
"ShellChannel": "ws://{IP}:7070", "OutputChannel": "ws://{IP}:{Port}",
"SessionChannel": "ws://{IP}:7070", "ShellChannel": "ws://{IP}:{Port}",
"SessionChannel": "ws://{IP}:{Port}",
"HTTPSessionMaxAge": 86400, "HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "201411062300", "StaticResourceVersion": "${time}",
"MaxProcs": 4, "MaxProcs": 4,
"RuntimeMode": "dev", "RuntimeMode": "dev",
"WD": "${pwd}", "WD": "${pwd}",
"Workspace": "{WD}/data/workspace",
"Locale": "en_US", "Locale": "en_US",
"Users": [ "Users": [
{ {
"Name": "admin", "Name": "admin",
"Password": "admin", "Password": "admin",
"Workspace": "{WD}/data/user_workspaces/admin", "Workspace": "${GOPATH}",
"Locale": "en_US", "Locale": "en_US",
"GoFormat": "gofmt", "GoFormat": "gofmt",
"FontFamily": "Helvetica", "FontFamily": "Helvetica",
"FontSize": "13px", "FontSize": "13px",
"Editor": { "Editor": {
"FontFamily": "Consolas, 'Courier New', monospace", "FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "inherit" "FontSize": "13px",
"LineHeight": "17px"
}, },
"LatestSessionContent": { "LatestSessionContent": {
"FileTree": [ "FileTree": [],
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src", "Files": [],
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\time" "CurrentFile": ""
],
"Files": [
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\time\\main.go"
],
"CurrentFile": "D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\time\\main.go"
} }
} }
] ]

View File

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

View File

@ -1,319 +0,0 @@
/* start icon */
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?35cb2z');
src: url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'), url('fonts/icomoon.woff?35cb2z') format('woff'), url('fonts/icomoon.ttf?35cb2z') format('truetype'), url('fonts/icomoon.svg?35cb2z#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
.font-ico {
font-family: 'icomoon';
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #666;
cursor: pointer;
font-size: 13px;
line-height: 18px;
}
.font-ico:hover {
color: #333;
}
.ico-play:before {
content: "\e605";
}
.ico-save:before {
content: "\f0c7";
}
.ico-max:before {
content: "\f096";
}
.ico-format:before {
content: "\e60b";
}
.ico-buildrun:before {
content: "\e607";
}
.ico-stop:before {
content: "\e608";
}
.ico-restore:before {
content: "\e60c";
}
.ico-min:before {
content: "\e60d";
position: absolute;
right: 5px;
}
.ico-close:before {
content: "\e60a";
}
/* end ico */
/* start frame */
.frame {
position: absolute;
border: 1px solid #5F5F5F;
background-color: #F8F8F8;
width: 320px;
z-index: 21;
display: none;
}
.frame li {
padding: 0 5px;
line-height: 25px;
cursor: pointer;
}
.frame li.disabled {
color: #999;
}
.frame li:hover {
background-color: #3875D7;
color: #FFF;
}
.frame li.disabled:hover {
background-color: #F8F8F8;
color: #999;
}
.frame a {
color: #000;
text-decoration: none;
}
.frame li:hover a,
.frame a:hover {
color: #FFF;
}
.frame .hr {
background-color: #bdbdbd;
height: 1px;
margin: 0 1px;
}
/* end frame */
/* start tabs */
.tabs {
height: 20px;
overflow: hidden;
width: 100%;
background-color: #E6E6E6;
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #9D9D9D;
}
.tabs > div {
float: left;
line-height: 18px;
height: 18px;
padding: 0 5px;
cursor: pointer;
background-color: #DDD;
color: #8B8B8B;
border-right: 1px solid #ADADAD;
}
.tabs > div.current {
background-color: #9F9F9F;
color: #FFF;
}
/* end tabs */
/* start framework */
.content {
position: relative;
overflow: hidden;
}
/* end framework */
/* start menu */
.menu {
background-color: #F0F0F0;
height: 24px;
}
.menu > ul > li {
float: left;
}
.menu > ul > li > span {
color: #000;
font-size: 12px;
line-height: 24px;
padding: 5px;
text-decoration: none;
cursor: pointer;
}
/* end menu */
/* start editor */
.edit-panel {
width: 80%;
position: absolute;
left: 20%;
width: 80%;
height: 70%;
overflow: hidden;
}
.edit-panel .tabs > div {
background-color: #d1d1d1;
border-right-color: #9b9b9b;
color: #333;
cursor: auto;
}
.edit-panel .tabs > div.current {
background-color: #F7F7F7;
}
.toolbars {
position: absolute;
right: 5px;
top: 1px;
}
.edit-panel .tabs .ico {
background-image: url("../images/ico-file.png");
float: left;
height: 16px;
margin: 2px 0 0 -2px;
width: 16px;
}
/* 统一为 static/js/lib/codemirror-4.5/addon/hint/show-hint.css 中的.CodeMirror-hints */
.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: white;
font-size: 90%;
font-family: Consolas, Courier New, monospace;
max-height: 20em;
overflow-y: auto;
}
.CodeMirror,
.CodeMirror-hints {
font-family: Consolas, Courier New, monospace;
}
.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: white;
}
/* end editor */
/* start bottom-window-group */
.bottom-window-group {
width: 80%;
position: absolute;
left: 20%;
width: 80%;
height: 30%;
top: 70%;
z-index: 7;
background-color: #fff;
}
.bottom-window-group-max {
height: 100%;
left: 0;
top: 0;
width: 100%;
z-index: 11;
}
.bottom-window-group > div > div {
overflow: auto;
}
.bottom-window-group .output {
font-family: Consolas, Courier New, monospace;
padding: 0 5px;
line-height: 16px;
font-size: 12px;
}
.bottom-window-group .output pre {
margin: 0;
}
.bottom-window-group .output .start-build,
.bottom-window-group .output .start-test,
.bottom-window-group .output .start-install,
.bottom-window-group .output .start-get {
color: #999;
}
.bottom-window-group .output .build-succ,
.bottom-window-group .output .test-succ,
.bottom-window-group .output .install-succ,
.bottom-window-group .output .get-succ {
color: rgb(0, 153, 0);
}
.bottom-window-group .output .build-error,
.bottom-window-group .output .test-error,
.bottom-window-group .output .install-error,
.bottom-window-group .output .get-error {
color: red;
}
.bottom-window-group table {
width: 100%;
}
.bottom-window-group td {
border-bottom: 1px solid #DDD;
line-height: 20px;
}
.bottom-window-group .notification .type,
.bottom-window-group .notification .severity {
width: 50px;
padding: 0 5px;
}
.bottom-window-group .search li {
cursor: pointer;
line-height: 20px;
padding: 0 3px;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bottom-window-group .search li.selected {
background-color: #3875d7;
color: #FFF;
}
.bottom-window-group .search .path {
color: #999;
font-size: 12px;
}
.bottom-window-group .search li.selected .path {
color: #FFF;
}
/* end bottom-window-group */
/* start footer */
.footer {
border-top: 1px solid #919191;
background-color: #F0F0F0;
padding-left: 5px;
height: 19px;
line-height: 18px;
}
.footer .cursor {
cursor: pointer;
}
.notification-count {
float: right;
display: none;
cursor: pointer;
background-color: red;
color: #FFF;
margin: 1px 5px;
padding: 0 2px;
border-radius: 3px;
line-height: 16px;
}
/* end footer */

View File

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

View File

@ -1,36 +0,0 @@
{
"Server": "{IP}:7070",
"StaticServer": "http://{IP}:7070",
"EditorChannel": "ws://{IP}:7070",
"OutputChannel": "ws://{IP}:7070",
"ShellChannel": "ws://{IP}:7070",
"SessionChannel": "ws://{IP}:7070",
"HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "201410271700",
"MaxProcs": 4,
"RuntimeMode": "dev",
"Pwd": "{pwd}",
"Workspace": "{pwd}/data/workspace",
"Locale": "en_US",
"Users": [{
"Name": "admin",
"Password": "admin",
"Workspace": "{pwd}/data/user_workspaces/admin",
"Locale": "en_US",
"GoFormat": "gofmt",
"LatestSessionContent": {
"FileTree": [
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src",
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\format",
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\gotest",
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\hello",
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\time"
],
"Files": [
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\time\\main.go",
"D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\format\\index.html"
],
"CurrentFile": "D:\\GoGoGo\\src\\github.com\\b3log\\wide\\data\\user_workspaces\\admin\\src\\format\\index.html"
}
}]
}

View File

@ -1,16 +0,0 @@
package test
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
func Test_Division_2(t *testing.T) {
}

View File

@ -1,17 +0,0 @@
package test
import (
"testing"
)
func Test_Division_3(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
func Test_Division_4(t *testing.T) {
t.Error("就是不通过")
}

View File

@ -1,13 +0,0 @@
package test
import (
"errors"
)
func Division(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Editor manipulations. // Editor manipulations.
package editor package editor
@ -24,30 +38,30 @@ import (
// WSHandler handles request of creating editor channel. // WSHandler handles request of creating editor channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := httpSession.Values["id"].(string) sid := httpSession.Values["id"].(string)
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
session.EditorWS[sid] = &editorChan
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"} ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
editorChan.Conn.WriteJSON(&ret) err := editorChan.WriteJSON(&ret)
if nil != err {
return
}
session.EditorWS[sid] = &editorChan
glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS)) glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS))
args := map[string]interface{}{} args := map[string]interface{}{}
for { for {
if err := session.EditorWS[sid].Conn.ReadJSON(&args); err != nil { if err := session.EditorWS[sid].ReadJSON(&args); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Editor WS ERROR: " + err.Error())
return return
} }
@ -59,7 +73,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset: %d", offset) // glog.Infof("offset: %d", offset)
gocode := conf.Wide.GetExecutableInGOBIN("gocode") gocode := util.Go.GetExecutableInGOBIN("gocode")
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)} argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
var output bytes.Buffer var output bytes.Buffer
@ -75,7 +89,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"} ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"}
if err := session.EditorWS[sid].Conn.WriteJSON(&ret); err != nil { if err := session.EditorWS[sid].WriteJSON(&ret); err != nil {
glog.Error("Editor WS ERROR: " + err.Error()) glog.Error("Editor WS ERROR: " + err.Error())
return return
} }
@ -94,6 +108,11 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string) username := session.Values["username"].(string)
path := args["path"].(string) path := args["path"].(string)
@ -136,7 +155,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
glog.V(5).Infof("gocode set lib-path %s", libPath) glog.V(5).Infof("gocode set lib-path %s", libPath)
// 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 := conf.Wide.GetExecutableInGOBIN("gocode") gocode := util.Go.GetExecutableInGOBIN("gocode")
argv := []string{"set", "lib-path", libPath} argv := []string{"set", "lib-path", libPath}
exec.Command(gocode, argv...).Run() exec.Command(gocode, argv...).Run()
@ -205,7 +224,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
@ -236,6 +255,11 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string) username := session.Values["username"].(string)
var args map[string]interface{} var args map[string]interface{}
@ -276,7 +300,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
@ -315,6 +339,11 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string) username := session.Values["username"].(string)
var args map[string]interface{} var args map[string]interface{}
@ -355,7 +384,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir cmd.Dir = curDir

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 editor package editor
import ( import (
@ -5,8 +19,6 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/session" "github.com/b3log/wide/session"
@ -24,6 +36,11 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string) username := session.Values["username"].(string)
var args map[string]interface{} var args map[string]interface{}
@ -37,8 +54,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg" if util.Go.IsAPI(filePath) {
if strings.HasPrefix(filePath, apiPath) { // if it is Go API source code
// ignore it // ignore it
return return
} }
@ -70,7 +86,9 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
bytes, _ := cmd.Output() bytes, _ := cmd.Output()
output := string(bytes) output := string(bytes)
if "" == output { if "" == output {
data["succ"] = false // format error, returns the original content
data["succ"] = true
data["code"] = code
return return
} }

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Event manipulations. // Event manipulations.
package event package event

91
file/exporter.go Normal file
View File

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

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// File tree manipulations. // File tree manipulations.
package file package file
@ -7,7 +21,6 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strings" "strings"
@ -21,7 +34,6 @@ import (
// File node, used to construct the file tree. // File node, used to construct the file tree.
type FileNode struct { type FileNode struct {
Name string `json:"name"` Name string `json:"name"`
Title string `json:"title"`
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
Type string `json:"type"` // "f": file, "d": directory Type string `json:"type"` // "f": file, "d": directory
@ -39,28 +51,49 @@ type Snippet struct {
Contents []string `json:"contents"` // lines nearby Contents []string `json:"contents"` // lines nearby
} }
var apiNode *FileNode
// initAPINode builds the Go API file node.
func initAPINode() {
apiPath := util.Go.GetAPIPath()
apiNode = &FileNode{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
Creatable: false, Removable: false, FileNodes: []*FileNode{}}
walk(apiPath, apiNode, false, false)
}
// GetFiles handles request of constructing user workspace file tree. // GetFiles handles request of constructing user workspace file tree.
// //
// The Go API source code package ($GOROOT/src/pkg) 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. // so that users can easily view the Go API source code in file tree.
func GetFiles(w http.ResponseWriter, r *http.Request) { func GetFiles(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string) username := session.Values["username"].(string)
userWorkspace := conf.Wide.GetUserWorkspace(username) userWorkspace := conf.Wide.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
root := FileNode{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}} root := FileNode{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}}
if nil == apiNode { // lazy init
initAPINode()
}
// workspace node process // workspace node process
for _, workspace := range workspaces { for _, workspace := range workspaces {
workspacePath := workspace + conf.PathSeparator + "src" workspacePath := workspace + conf.PathSeparator + "src"
workspaceNode := FileNode{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:], workspaceNode := FileNode{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
Title: workspace, Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d", Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d",
Creatable: true, Removable: false, FileNodes: []*FileNode{}} Creatable: true, Removable: false, FileNodes: []*FileNode{}}
walk(workspacePath, &workspaceNode, true, true) walk(workspacePath, &workspaceNode, true, true)
@ -69,28 +102,31 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
root.FileNodes = append(root.FileNodes, &workspaceNode) root.FileNodes = append(root.FileNodes, &workspaceNode)
} }
// construct Go API node
apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg"
apiNode := FileNode{Name: "Go API", Title: apiPath, Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
Creatable: false, Removable: false, FileNodes: []*FileNode{}}
goapiBuildOKSignal := make(chan bool)
go func() {
walk(apiPath, &apiNode, false, false)
// go-ahead
close(goapiBuildOKSignal)
}()
// waiting
<-goapiBuildOKSignal
// add Go API node // add Go API node
root.FileNodes = append(root.FileNodes, &apiNode) root.FileNodes = append(root.FileNodes, apiNode)
data["root"] = root data["root"] = root
} }
// RefreshDirectory handles request of refresh a directory of file tree.
func RefreshDirectory(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
path := r.FormValue("path")
node := FileNode{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}}
walk(path, &node, true, true)
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(node.FileNodes)
if err != nil {
glog.Error(err)
return
}
w.Write(data)
}
// GetFile handles request of opening file by editor. // GetFile handles request of opening file by editor.
func GetFile(w http.ResponseWriter, r *http.Request) { func GetFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
@ -106,25 +142,40 @@ func GetFile(w http.ResponseWriter, r *http.Request) {
} }
path := args["path"].(string) path := args["path"].(string)
size := util.File.GetFileSize(path)
if size > 5242880 { // 5M
data["succ"] = false
data["msg"] = "This file is too large to open :("
return
}
buf, _ := ioutil.ReadFile(path) buf, _ := ioutil.ReadFile(path)
extension := filepath.Ext(path) extension := filepath.Ext(path)
if isImg(extension) { if util.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"
path2 := strings.Replace(path, "\\", "/", -1) user := GetUsre(path)
idx := strings.Index(path2, "/data/user_workspaces") if nil == user {
data["path"] = path2[idx:] glog.Warningf("The path [%s] has no owner")
data["path"] = ""
return
}
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1)
return return
} }
content := string(buf) content := string(buf)
if isBinary(content) { if util.File.IsBinary(content) {
data["succ"] = false data["succ"] = false
data["msg"] = "Can't open a binary file :(" data["msg"] = "Can't open a binary file :("
} else { } else {
@ -262,10 +313,70 @@ func RenameFile(w http.ResponseWriter, r *http.Request) {
data["succ"] = false data["succ"] = false
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 " + path} Data: "can't rename file " + oldPath}
} }
} }
// Use to find results sorting.
type foundPath struct {
Path string `json:"path"`
score int
}
type foundPaths []*foundPath
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) 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.
func Find(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
path := args["path"].(string) // path of selected file in file tree
name := args["name"].(string)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
userWorkspace := conf.Wide.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
if "" != path && !util.File.IsDir(path) {
path = filepath.Dir(path)
}
founds := foundPaths{}
for _, workspace := range workspaces {
rs := find(workspace+conf.PathSeparator+"src", name, []*string{})
for _, r := range rs {
substr := util.Str.LCS(path, *r)
founds = append(founds, &foundPath{Path: *r, score: len(substr)})
}
}
sort.Sort(founds)
data["founds"] = founds
}
// SearchText handles request of searching files under the specified directory with the specified keyword. // SearchText handles request of searching files under the specified directory with the specified keyword.
func SearchText(w http.ResponseWriter, r *http.Request) { func SearchText(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
@ -298,7 +409,7 @@ func walk(path string, node *FileNode, creatable, removable bool) {
fio, _ := os.Lstat(fpath) fio, _ := os.Lstat(fpath)
child := FileNode{Name: filename, Title: fpath, Path: fpath, Removable: removable, FileNodes: []*FileNode{}} child := FileNode{Name: filename, Path: fpath, Removable: removable, FileNodes: []*FileNode{}}
node.FileNodes = append(node.FileNodes, &child) node.FileNodes = append(node.FileNodes, &child)
if nil == fio { if nil == fio {
@ -340,7 +451,14 @@ func listFiles(dirname string) []string {
// sort: directories in front of files // sort: directories in front of files
for _, name := range names { for _, name := range names {
fio, _ := os.Lstat(filepath.Join(dirname, name)) path := filepath.Join(dirname, name)
fio, err := os.Lstat(path)
if nil != err {
glog.Warningf("Can't read file info [%s]", path)
continue
}
if fio.IsDir() { if fio.IsDir() {
// exclude the .git direcitory // exclude the .git direcitory
@ -361,7 +479,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 isImg(filenameExtension) { if util.File.IsImg(filenameExtension) {
return "ico-ztree-img " return "ico-ztree-img "
} }
@ -426,7 +544,7 @@ func getEditorMode(filenameExtension string) string {
func createFile(path, fileType string) bool { func createFile(path, fileType string) bool {
switch fileType { switch fileType {
case "f": case "f":
file, err := os.OpenFile(path, os.O_CREATE, 0664) file, err := os.OpenFile(path, os.O_CREATE, 0775)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
@ -465,7 +583,7 @@ func removeFile(path string) bool {
return false return false
} }
glog.Infof("Removed [%s]", path) glog.V(5).Infof("Removed [%s]", path)
return true return true
} }
@ -473,17 +591,69 @@ func removeFile(path string) bool {
// renameFile renames (moves) a file from the specified old path to the specified new path. // renameFile renames (moves) a file from the specified old path to the specified new path.
func renameFile(oldPath, newPath string) bool { func renameFile(oldPath, newPath string) bool {
if err := os.Rename(oldPath, newPath); nil != err { if err := os.Rename(oldPath, newPath); nil != err {
glog.Errorf("Renames [%s] failed: [%s]", path, err.Error()) glog.Errorf("Renames [%s] failed: [%s]", oldPath, err.Error())
return false return false
} }
glog.Infof("Renamed [%s] to [%s]", oldPath, newPath) glog.V(5).Infof("Renamed [%s] to [%s]", oldPath, newPath)
return true return true
} }
// search finds file under the specified dir and its sub-directories with the specified text, likes the command grep/findstr. // Default exclude file name patterns when find.
var defaultExcludesFind = []string{".git", ".svn", ".repository", "CVS", "RCS", "SCCS", ".bzr", ".metadata", ".hg"}
// find finds files under the specified dir and its sub-directoryies with the specified name,
// likes the command 'find dir -name name'.
func find(dir, name string, results []*string) []*string {
if !strings.HasSuffix(dir, conf.PathSeparator) {
dir += conf.PathSeparator
}
f, _ := os.Open(dir)
fileInfos, err := f.Readdir(-1)
f.Close()
if nil != err {
glog.Errorf("Read dir [%s] failed: [%s]", dir, err.Error())
return results
}
for _, fileInfo := range fileInfos {
fname := fileInfo.Name()
path := dir + fname
if fileInfo.IsDir() {
if util.Str.Contains(fname, defaultExcludesFind) {
continue
}
// enter the directory recursively
results = find(path, name, results)
} else {
// match filename
pattern := filepath.Dir(path) + conf.PathSeparator + name
match, err := filepath.Match(pattern, path)
if nil != err {
glog.Errorf("Find match filename failed: [%s]", err.Error)
continue
}
if match {
results = append(results, &path)
}
}
}
return results
}
// search finds file under the specified dir and its sub-directories with the specified text, likes the command 'grep'
// or 'findstr'.
func search(dir, extension, text string, snippets []*Snippet) []*Snippet { func search(dir, extension, text string, snippets []*Snippet) []*Snippet {
if !strings.HasSuffix(dir, conf.PathSeparator) { if !strings.HasSuffix(dir, conf.PathSeparator) {
dir += conf.PathSeparator dir += conf.PathSeparator
@ -528,7 +698,7 @@ func searchInFile(path string, text string) []*Snippet {
} }
content := string(bytes) content := string(bytes)
if isBinary(content) { if util.File.IsBinary(content) {
return ret return ret
} }
@ -547,25 +717,13 @@ func searchInFile(path string, text string) []*Snippet {
return ret return ret
} }
// isBinary determines whether the specified content is a binary file content. // GetUsre gets the user the specified path belongs to. Returns nil if not found.
func isBinary(content string) bool { func GetUsre(path string) *conf.User {
for _, b := range content { for _, user := range conf.Wide.Users {
if 0 == b { if strings.HasPrefix(path, user.GetWorkspace()) {
return true return user
} }
} }
return false return nil
}
// isImg determines whether the specified extension is a image.
func isImg(extension string) bool {
ext := strings.ToLower(extension)
switch ext {
case ".jpg", ".jpeg", ".bmp", ".gif", ".png", ".svg", ".ico":
return true
default:
return false
}
} }

View File

@ -22,6 +22,7 @@
"create": "Create", "create": "Create",
"create_dir": "Create Dir", "create_dir": "Create Dir",
"delete": "Delete", "delete": "Delete",
"rename": "Rename",
"save": "Save", "save": "Save",
"exit": "Exit", "exit": "Exit",
"close_all_files": "Close All", "close_all_files": "Close All",
@ -39,6 +40,7 @@
"notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work", "notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work",
"notification_4": "Server Internal Error", "notification_4": "Server Internal Error",
"goto_line": "Goto Line", "goto_line": "Goto Line",
"goto_file": "Goto File",
"go": "Go", "go": "Go",
"tip": "Tip", "tip": "Tip",
"confirm": "Confirm", "confirm": "Confirm",
@ -73,8 +75,10 @@
"show_expr_info": "Show Expression Info", "show_expr_info": "Show Expression Info",
"find_usages": "Find Usages", "find_usages": "Find Usages",
"delete_line": "Delete Line", "delete_line": "Delete Line",
"copy_line_up": "Copy Line Up", "copy_lines_up": "Copy Lines Up",
"copy_line_down": "Copy Line Down", "copy_lines_down": "Copy Lines Down",
"move_lines_up": "Move Lines Up",
"move_lines_down": "Move Lines Down",
"save_editor_file": "Save File", "save_editor_file": "Save File",
"save_all_editors_files": "Save All", "save_all_editors_files": "Save All",
"close_editor": "Close File", "close_editor": "Close File",
@ -118,5 +122,18 @@
"discard": "Discard", "discard": "Discard",
"close": "Close", "close": "Close",
"close_other": "Close Other", "close_other": "Close Other",
"clear": "Clear" "clear": "Clear",
"perference": "Perference",
"appearence": "Appearence",
"gotool": "Go Tool",
"user": "User",
"font": "Font",
"font_size": "Font Size",
"line_height": "Line Height",
"go_format": "Go Format",
"locale": "Locale",
"apply": "Apply",
"clearOutput": "Clear Output",
"export": "Export",
"refresh": "Refresh"
} }

View File

@ -22,6 +22,7 @@
"create": "作成", "create": "作成",
"create_dir": "新規ディレクトリ", "create_dir": "新規ディレクトリ",
"delete": "削除", "delete": "削除",
"rename": "名前の変更",
"save": "保存", "save": "保存",
"exit": "終了", "exit": "終了",
"close_all_files": "全てのファイルを閉じる", "close_all_files": "全てのファイルを閉じる",
@ -39,6 +40,7 @@
"notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。", "notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。",
"notification_4": "内部サーバーエラー", "notification_4": "内部サーバーエラー",
"goto_line": "指定行にジャンプ", "goto_line": "指定行にジャンプ",
"goto_file": "ファイルをオープンする",
"go": "Go", "go": "Go",
"tip": "ヒント", "tip": "ヒント",
"confirm": "確認", "confirm": "確認",
@ -73,8 +75,10 @@
"show_expr_info": "式の情報を表示", "show_expr_info": "式の情報を表示",
"find_usages": "使用方法を検索する", "find_usages": "使用方法を検索する",
"delete_line": "行を削除", "delete_line": "行を削除",
"copy_line_up": "前行にコピー", "copy_lines_up": "フロントへのコピー",
"copy_line_down": "次行にコピー", "copy_lines_down": "一番下にコピー",
"move_lines_up": "前面に移動します",
"move_lines_down": "以下に移動",
"save_editor_file": "保存", "save_editor_file": "保存",
"save_all_editors_files": "全てを保存", "save_all_editors_files": "全てを保存",
"close_editor": "エディタを閉じる", "close_editor": "エディタを閉じる",
@ -118,5 +122,18 @@
"discard": "あきらめる", "discard": "あきらめる",
"close": "クローズ", "close": "クローズ",
"close_other": "閉じるその他", "close_other": "閉じるその他",
"clear": "空の" "clear": "空の",
"perference": "偏好设定",
"appearence": "エクステリア",
"gotool": "Go ツール",
"user": "ユーザー",
"font": "フォント",
"font_size": "フォント·サイズ",
"line_height": "行の高さ",
"go_format": "Go フォーマット",
"locale": "ロケール",
"apply": "適用する",
"clearOutput": "空の出力",
"export": "輸出",
"refresh": "リフレッシュ"
} }

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Internationalization manipulations. // Internationalization manipulations.
package i18n package i18n
@ -67,3 +81,14 @@ func Get(locale, key string) interface{} {
func GetAll(locale string) map[string]interface{} { func GetAll(locale string) map[string]interface{} {
return Locales[locale].Langs return Locales[locale].Langs
} }
// GetLocalesNames gets names of all locales. Returns ["zh_CN", "en_US"] for example.
func GetLocalesNames() []string {
ret := []string{}
for name, _ := range Locales {
ret = append(ret, name)
}
return ret
}

View File

@ -22,6 +22,7 @@
"create": "创建", "create": "创建",
"create_dir": "创建目录", "create_dir": "创建目录",
"delete": "删除", "delete": "删除",
"rename": "重命名",
"save": "保存", "save": "保存",
"exit": "退出", "exit": "退出",
"close_all_files": "关闭所有文件", "close_all_files": "关闭所有文件",
@ -39,6 +40,7 @@
"notification_3": "没有检查到 ide_stub这将会导致 [跳转到声明]、[查找使用] 失效", "notification_3": "没有检查到 ide_stub这将会导致 [跳转到声明]、[查找使用] 失效",
"notification_4": "服务器内部错误", "notification_4": "服务器内部错误",
"goto_line": "跳转到行", "goto_line": "跳转到行",
"goto_file": "打开文件",
"go": "跳转", "go": "跳转",
"tip": "提示", "tip": "提示",
"confirm": "确定", "confirm": "确定",
@ -73,8 +75,10 @@
"show_expr_info": "查看表达式信息", "show_expr_info": "查看表达式信息",
"find_usages": "查找使用", "find_usages": "查找使用",
"delete_line": "删除当前行", "delete_line": "删除当前行",
"copy_line_up": "复制当前行到上一行", "copy_lines_up": "复制到上方",
"copy_line_down": "复制当前行到下一行", "copy_lines_down": "复制到下方",
"move_lines_up": "移动到上方",
"move_lines_down": "移动到下方",
"save_editor_file": "保存当前编辑器文件", "save_editor_file": "保存当前编辑器文件",
"save_all_editors_files": "保存所有编辑器文件", "save_all_editors_files": "保存所有编辑器文件",
"close_editor": "关闭当前编辑器", "close_editor": "关闭当前编辑器",
@ -118,5 +122,18 @@
"discard": "放弃", "discard": "放弃",
"close": "关闭", "close": "关闭",
"close_other": "关闭其它", "close_other": "关闭其它",
"clear": "清空" "clear": "清空",
"perference": "偏好设定",
"appearence": "外观",
"gotool": "Go 工具",
"user": "用户",
"font": "字体",
"font_size": "字体大小",
"line_height": "行高",
"go_format": "Go 格式化",
"locale": "语言环境",
"apply": "应用",
"clearOutput": "清空输出",
"export": "导出",
"refresh": "刷新"
} }

View File

@ -22,6 +22,7 @@
"create": "建立", "create": "建立",
"create_dir": "建立目錄", "create_dir": "建立目錄",
"delete": "删除", "delete": "删除",
"rename": "重命名",
"save": "儲存", "save": "儲存",
"exit": "退出", "exit": "退出",
"close_all_files": "關閉所有文件", "close_all_files": "關閉所有文件",
@ -39,6 +40,7 @@
"notification_3": "没有檢查到 ide_stub這將會導致 [跳轉到聲明]、[查找使用] 失效", "notification_3": "没有檢查到 ide_stub這將會導致 [跳轉到聲明]、[查找使用] 失效",
"notification_4": "服務器內部錯誤", "notification_4": "服務器內部錯誤",
"goto_line": "跳轉到行", "goto_line": "跳轉到行",
"goto_file": "打開文件",
"go": "跳到", "go": "跳到",
"tip": "提示", "tip": "提示",
"confirm": "確定", "confirm": "確定",
@ -73,8 +75,10 @@
"show_expr_info": "查看表達式信息", "show_expr_info": "查看表達式信息",
"find_usages": "尋找使用", "find_usages": "尋找使用",
"delete_line": "删除當前行", "delete_line": "删除當前行",
"copy_line_up": "插入當前行到上一行", "copy_lines_up": "複製到上方",
"copy_line_down": "插入當前行到下一行", "copy_lines_down": "複製到下方",
"move_lines_up": "移動到上方",
"move_lines_down": "移動到下方",
"save_editor_file": "保存當前編輯器文件", "save_editor_file": "保存當前編輯器文件",
"save_all_editors_files": "保存所有編輯器文件", "save_all_editors_files": "保存所有編輯器文件",
"close_editor": "關閉當前編輯器", "close_editor": "關閉當前編輯器",
@ -118,5 +122,18 @@
"discard": "放棄", "discard": "放棄",
"close": "關閉", "close": "關閉",
"close_other": "關閉其它", "close_other": "關閉其它",
"clear": "清空" "clear": "清空",
"perference": "偏好設定",
"appearence": "外觀",
"gotool": "Go 工具",
"user": "用戶",
"font": "字形",
"font_size": "字體大小",
"line_height": "行高",
"go_format": "Go 格式化",
"locale": "語言環境",
"apply": "應用",
"clearOutput": "清空輸出",
"export": "導出",
"refresh": "刷新"
} }

64
main.go
View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 main package main
import ( import (
@ -6,8 +20,10 @@ import (
"math/rand" "math/rand"
"mime" "mime"
"net/http" "net/http"
"os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
@ -25,26 +41,46 @@ import (
// The only one init function in Wide. // The only one init function in Wide.
func init() { func init() {
// TODO: args confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
flag.Set("logtostderr", "true") confIP := flag.String("ip", "", "ip to visit")
confPort := flag.String("port", "", "port to visit")
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
confChannel := flag.String("channel", "", "this will overwrite Wide.XXXChannel if specified")
confStat := flag.Bool("stat", false, "whether report statistics periodically")
confDocker := flag.Bool("docker", false, "whether run in a docker container")
flag.Set("alsologtostderr", "true")
flag.Set("stderrthreshold", "INFO")
flag.Set("v", "3") flag.Set("v", "3")
flag.Parse() flag.Parse()
wd := util.OS.Pwd()
if strings.HasPrefix(wd, os.TempDir()) {
glog.Error("Don't run wide in OS' temp directory or with `go run`")
os.Exit(-1)
}
i18n.Load() i18n.Load()
event.Load() event.Load()
conf.Load() conf.Load(*confPath, *confIP, *confPort, *confServer, *confChannel, *confDocker)
conf.FixedTimeCheckEnv() conf.FixedTimeCheckEnv()
conf.FixedTimeSave() conf.FixedTimeSave()
session.FixedTimeRelease() session.FixedTimeRelease()
if *confStat {
session.FixedTimeReport()
}
} }
// 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) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -72,10 +108,9 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
locale := user.Locale locale := user.Locale
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUsername(username)
userConf := conf.Wide.GetUser(username)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"session": wideSession, "latestSessionContent": userConf.LatestSessionContent, "session": wideSession, "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer} "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer}
glog.V(3).Infof("User [%s] has [%d] sessions", username, len(wideSessions)) glog.V(3).Infof("User [%s] has [%d] sessions", username, len(wideSessions))
@ -102,7 +137,6 @@ 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, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -140,7 +174,6 @@ 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, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -170,7 +203,6 @@ 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, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -217,7 +249,10 @@ func main() {
serveSingle("/favicon.ico", "./static/favicon.ico") serveSingle("/favicon.ico", "./static/favicon.ico")
// workspaces // workspaces
http.Handle("/data/", http.StripPrefix("/data/", http.FileServer(http.Dir("data")))) for _, user := range conf.Wide.Users {
http.Handle("/workspace/"+user.Name+"/",
http.StripPrefix("/workspace/"+user.Name+"/", http.FileServer(http.Dir(user.GetWorkspace()))))
}
// session // session
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler)) http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
@ -234,12 +269,18 @@ func main() {
// file tree // file tree
http.HandleFunc("/files", handlerWrapper(file.GetFiles)) http.HandleFunc("/files", handlerWrapper(file.GetFiles))
http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectory))
http.HandleFunc("/file", handlerWrapper(file.GetFile)) http.HandleFunc("/file", handlerWrapper(file.GetFile))
http.HandleFunc("/file/save", handlerWrapper(file.SaveFile)) http.HandleFunc("/file/save", handlerWrapper(file.SaveFile))
http.HandleFunc("/file/new", handlerWrapper(file.NewFile)) http.HandleFunc("/file/new", handlerWrapper(file.NewFile))
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFile)) http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFile))
http.HandleFunc("/file/rename", handlerWrapper(file.RenameFile)) http.HandleFunc("/file/rename", handlerWrapper(file.RenameFile))
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchText)) http.HandleFunc("/file/search/text", handlerWrapper(file.SearchText))
http.HandleFunc("/file/find/name", handlerWrapper(file.Find))
// file export/import
http.HandleFunc("/file/zip", handlerWrapper(file.GetZip))
http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZip))
// editor // editor
http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler)) http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler))
@ -260,8 +301,9 @@ func main() {
http.HandleFunc("/login", handlerWrapper(session.LoginHandler)) http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler)) http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc("/signup", handlerWrapper(session.SignUpUser)) http.HandleFunc("/signup", handlerWrapper(session.SignUpUser))
http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
glog.V(0).Infof("Wide is running [%s]", conf.Wide.Server) glog.Infof("Wide is running [%s]", conf.Wide.Server)
err := http.ListenAndServe(conf.Wide.Server, nil) err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil { if err != nil {

View File

@ -1,11 +1,25 @@
// Copyright (c) 2014, B3log
//
// 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.
// Notification manipulations. // Notification manipulations.
package notification package notification
import ( import (
"net/http" "net/http"
"strconv"
"time" "time"
"strconv"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/event" "github.com/b3log/wide/event"
"github.com/b3log/wide/i18n" "github.com/b3log/wide/i18n"
@ -65,7 +79,7 @@ func event2Notification(e *event.Event) {
return return
} }
wsChannel.Conn.WriteJSON(notification) wsChannel.WriteJSON(notification)
wsChannel.Refresh() wsChannel.Refresh()
} }
@ -83,6 +97,12 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()} wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"notification": "Notification initialized", "cmd": "init-notification"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
session.NotificationWS[sid] = &wsChan session.NotificationWS[sid] = &wsChan
glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS)) glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS))
@ -93,17 +113,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
input := map[string]interface{}{} input := map[string]interface{}{}
for { for {
if err := wsChan.Conn.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Notification WS ERROR: " + err.Error())
return return
} }
} }

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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, run and go tool manipulations. // Build, run and go tool manipulations.
package output package output
@ -44,10 +58,13 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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()}
session.OutputWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"} ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"}
wsChan.Conn.WriteJSON(&ret) err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
session.OutputWS[sid] = &wsChan
glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS)) glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
} }
@ -106,7 +123,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
channelRet["cmd"] = "run-done" channelRet["cmd"] = "run-done"
channelRet["output"] = "" channelRet["output"] = ""
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -127,13 +144,13 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath) glog.V(5).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid // push once for front-end to get the 'run' state and pid
if nil != wsChannel { if nil != wsChannel {
channelRet["cmd"] = "run" channelRet["cmd"] = "run"
channelRet["output"] = "" channelRet["output"] = ""
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -155,12 +172,12 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// remove the exited process from user process set // remove the exited process from user process set
processes.remove(wSession, cmd.Process) processes.remove(wSession, cmd.Process)
glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done [stdout err]", sid, runningId, filePath) glog.V(5).Infof("Session [%s] 's running [id=%d, file=%s] has done [stdout err]", sid, runningId, filePath)
if nil != wsChannel { if nil != wsChannel {
channelRet["cmd"] = "run-done" channelRet["cmd"] = "run-done"
channelRet["output"] = "<pre>" + buf + "</pre>" channelRet["output"] = buf
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
break break
@ -173,8 +190,8 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
} else { } else {
if nil != wsChannel { if nil != wsChannel {
channelRet["cmd"] = "run" channelRet["cmd"] = "run"
channelRet["output"] = "<pre>" + buf + "</pre>" channelRet["output"] = buf
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
break break
@ -195,14 +212,14 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// remove the exited process from user process set // remove the exited process from user process set
processes.remove(wSession, cmd.Process) processes.remove(wSession, cmd.Process)
glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done [stderr err]", sid, runningId, filePath) glog.V(5).Infof("Session [%s] 's running [id=%d, file=%s] has done [stderr err]", sid, runningId, filePath)
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run-done" channelRet["cmd"] = "run-done"
channelRet["output"] = "<pre>" + buf + "</pre>" channelRet["output"] = buf
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
break break
@ -217,8 +234,8 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run" channelRet["cmd"] = "run"
channelRet["output"] = "<pre>" + buf + "</pre>" channelRet["output"] = buf
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
break break
@ -237,6 +254,11 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.Wide.GetUser(username).Locale
@ -319,7 +341,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -341,7 +363,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir) glog.V(5).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -419,10 +441,10 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
glog.V(3).Infof("Session [%s] 's build [id=%d, dir=%s] has done", sid, runningId, curDir) glog.V(5).Infof("Session [%s] 's build [id=%d, dir=%s] has done", sid, runningId, curDir)
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
} }
@ -439,6 +461,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.Wide.GetUser(username).Locale
@ -491,7 +518,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -512,7 +539,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer util.Recover()
glog.V(3).Infof("Session [%s] is running [go test] [runningId=%d]", sid, runningId) glog.V(5).Infof("Session [%s] is running [go test] [runningId=%d]", sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test" channelRet["cmd"] = "go test"
@ -524,11 +551,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
glog.V(3).Infof("Session [%s] 's running [go test] [runningId=%d] has done (with error)", sid, runningId) glog.V(5).Infof("Session [%s] 's running [go test] [runningId=%d] has done (with error)", 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 {
glog.V(3).Infof("Session [%s] 's running [go test] [runningId=%d] has done", sid, runningId) glog.V(5).Infof("Session [%s] 's running [go test] [runningId=%d] has done", 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)
} }
@ -536,7 +563,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
} }
@ -552,6 +579,11 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.Wide.GetUser(username).Locale
@ -606,7 +638,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -628,7 +660,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir) glog.V(5).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -692,10 +724,10 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
glog.V(3).Infof("Session [%s] 's running [go install] [id=%d, dir=%s] has done", sid, runningId, curDir) glog.V(5).Infof("Session [%s] 's running [go install] [id=%d, dir=%s] has done", sid, runningId, curDir)
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
} }
@ -712,6 +744,11 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale locale := conf.Wide.GetUser(username).Locale
@ -764,7 +801,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
return return
@ -786,7 +823,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
glog.V(3).Infof("Session [%s] is running [go get] [runningId=%d]", sid, runningId) glog.V(5).Infof("Session [%s] is running [go get] [runningId=%d]", sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get" channelRet["cmd"] = "go get"
@ -795,11 +832,11 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) { if 0 != len(buf) {
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done (with error)", sid, runningId) glog.V(5).Infof("Session [%s] 's running [go get] [runningId=%d] has done (with error)", sid, runningId)
channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf)
} else { } else {
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done", sid, runningId) glog.V(5).Infof("Session [%s] 's running [go get] [runningId=%d] has done", sid, runningId)
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n" channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
@ -808,7 +845,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
glog.Error(err) glog.Error(err)
} }
@ -846,10 +883,9 @@ func StopHandler(w http.ResponseWriter, r *http.Request) {
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.Wide.GetUserWorkspace(username) userWorkspace := conf.Wide.GetUserWorkspace(username)
masterWorkspace := conf.Wide.GetWorkspace()
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace+conf.PathListSeparator+masterWorkspace, "GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS, "GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH, "GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(), "GOROOT="+runtime.GOROOT(),

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 package output
import ( import (
@ -33,7 +47,7 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
// bind process with wide session // bind process with wide session
wSession.SetProcesses(userProcesses) wSession.SetProcesses(userProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) glog.V(5).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
} }
// remove removes the specified process from the user process set. // remove removes the specified process from the user process set.
@ -54,7 +68,7 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
// bind process with wide session // bind process with wide session
wSession.SetProcesses(newProcesses) wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) glog.V(5).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
return return
} }
@ -83,7 +97,7 @@ func (procs *procs) kill(wSession *session.WideSession, pid int) {
// bind process with wide session // bind process with wide session
wSession.SetProcesses(newProcesses) wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid) glog.V(5).Infof("Killed a process [pid=%d] of session [%s]", pid, sid)
} }
return return

2
pkg.sh
View File

@ -9,7 +9,7 @@
ver=$1 ver=$1
target=$2 target=$2
list="conf data doc i18n static views README.md LICENSE" list="conf doc i18n static views README.md LICENSE"
mkdir -p ${target} mkdir -p ${target}

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Session manipulations. // Session manipulations.
// //
// Wide server side needs maintain two kinds of sessions: // Wide server side needs maintain two kinds of sessions:
@ -9,9 +23,11 @@
package session package session
import ( import (
"bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
"strconv"
"sync" "sync"
"time" "time"
@ -79,7 +95,7 @@ func FixedTimeRelease() {
for _, s := range WideSessions { for _, s := range WideSessions {
if s.Updated.Before(threshold) { if s.Updated.Before(threshold) {
glog.V(3).Infof("Removes a invalid session [%s]", s.Id) glog.V(3).Infof("Removes a invalid session [%s], user [%s]", s.Id, s.Username)
WideSessions.Remove(s.Id) WideSessions.Remove(s.Id)
} }
@ -88,6 +104,50 @@ func FixedTimeRelease() {
}() }()
} }
// Online user statistic report.
type userReport struct {
username string
sessionCnt int
updated time.Time
}
// report returns a online user statistics in pretty format.
func (u *userReport) report() string {
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions, latest activity [" +
u.updated.Format("2006-01-02 15:04:05") + "]"
}
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
func FixedTimeReport() {
go func() {
for _ = range time.Tick(10 * time.Minute) {
users := map[string]*userReport{} // <username, *userReport>
for _, s := range WideSessions {
if report, exists := users[s.Username]; exists {
if s.Updated.After(report.updated) {
users[s.Username].updated = s.Updated
}
report.sessionCnt++
} else {
users[s.Username] = &userReport{username: s.Username, sessionCnt: 1, updated: s.Updated}
}
}
var buf bytes.Buffer
buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users are online and [" + strconv.Itoa(len(WideSessions)) +
"] sessions currently\n")
for _, t := range users {
buf.WriteString(" " + t.report() + "\n")
}
glog.Info(buf.String())
}
}()
}
// 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.
@ -104,26 +164,30 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
WideSessions.New(httpSession, sid) wSession = WideSessions.New(httpSession, sid)
glog.Infof("Created a wide session [%s] for websocket reconnecting", sid) glog.Infof("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()}
SessionWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Session initialized", "cmd": "init-session"} ret := map[string]interface{}{"output": "Session initialized", "cmd": "init-session"}
wsChan.Conn.WriteJSON(&ret) err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
SessionWS[sid] = &wsChan
glog.V(4).Infof("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS)) glog.V(4).Infof("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
input := map[string]interface{}{} input := map[string]interface{}{}
for { for {
if err := wsChan.Conn.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid) glog.V(5).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]",
sid, wSession.Username)
WideSessions.Remove(sid) WideSessions.Remove(sid)
@ -132,8 +196,9 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
ret = map[string]interface{}{"output": "", "cmd": "session-output"} ret = map[string]interface{}{"output": "", "cmd": "session-output"}
if err := wsChan.Conn.WriteJSON(&ret); err != nil { if err := wsChan.WriteJSON(&ret); err != nil {
glog.Error("Session WS ERROR: " + err.Error()) glog.Error("Session WS ERROR: " + err.Error())
return return
} }
@ -253,9 +318,9 @@ func (sessions *Sessions) 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 {
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid) glog.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
} else { } else {
glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid) glog.V(3).Infof("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
} }
} }
@ -275,8 +340,6 @@ func (sessions *Sessions) Remove(sid string) {
delete(SessionWS, sid) delete(SessionWS, sid)
} }
glog.V(3).Infof("Removed a session [%s]", s.Id)
cnt := 0 // count wide sessions associated with HTTP session cnt := 0 // count wide sessions associated with HTTP session
for _, s := range *sessions { for _, s := range *sessions {
if s.HTTPSession.ID == s.HTTPSession.ID { if s.HTTPSession.ID == s.HTTPSession.ID {
@ -284,7 +347,7 @@ func (sessions *Sessions) Remove(sid string) {
} }
} }
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt) glog.V(5).Infof("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt)
return return
} }

View File

@ -1,12 +1,29 @@
// Copyright (c) 2014, B3log
//
// 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 session package session
import ( import (
"encoding/json" "encoding/json"
"math/rand" "math/rand"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"text/template" "text/template"
"sync"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n" "github.com/b3log/wide/i18n"
@ -20,6 +37,85 @@ const (
UserCreateError = "user create error" UserCreateError = "user create error"
) )
// Exclusive lock for adding user.
var addUserMutex sync.Mutex
// PreferenceHandle handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/preference", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
username := httpSession.Values["username"].(string)
user := conf.Wide.GetUser(username)
if "GET" == r.Method {
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
"locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats()}
t, err := template.ParseFiles("views/preference.html")
if nil != err {
glog.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as save request
succ := true
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
args := struct {
FontFamily string
FontSize string
EditorFontFamily string
EditorFontSize string
EditorLineHeight string
GoFmt string
Workspace string
Username string
Password string
Locale string
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
succ = false
return
}
user.FontFamily = args.FontFamily
user.FontSize = args.FontSize
user.Editor.FontFamily = args.EditorFontFamily
user.Editor.FontSize = args.EditorFontSize
user.Editor.LineHeight = args.EditorLineHeight
user.GoFormat = args.GoFmt
user.Workspace = args.Workspace
user.Password = args.Password
user.Locale = args.Locale
conf.UpdateCustomizedConf(username)
succ = conf.Save()
}
// LoginHandler handles request of user login. // LoginHandler handles request of user login.
func LoginHandler(w http.ResponseWriter, r *http.Request) { func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method { if "GET" == r.Method {
@ -44,7 +140,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
// non-GET request as login request // non-GET request as login request
succ := false succ := true
data := map[string]interface{}{"succ": &succ} data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -55,14 +151,17 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err) glog.Error(err)
succ = true succ = false
return return
} }
succ = false
for _, user := range conf.Wide.Users { for _, user := range conf.Wide.Users {
if user.Name == args.Username && user.Password == args.Password { if user.Name == args.Username && user.Password == args.Password {
succ = true succ = true
break
} }
} }
@ -142,8 +241,15 @@ func SignUpUser(w http.ResponseWriter, r *http.Request) {
} }
} }
// addUser add a user with the specified username and password.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
func addUser(username, password string) string { func addUser(username, password string) string {
// XXX: validate addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Wide.Users { for _, user := range conf.Wide.Users {
if user.Name == username { if user.Name == username {
@ -155,19 +261,51 @@ func addUser(username, password string) string {
dir := filepath.Dir(firstUserWorkspace) dir := filepath.Dir(firstUserWorkspace)
workspace := filepath.Join(dir, username) workspace := filepath.Join(dir, username)
conf.Wide.Users = append(conf.Wide.Users, newUser := &conf.User{Name: username, Password: password, Workspace: workspace,
&conf.User{Name: username, Password: password, Workspace: workspace, Locale: conf.Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "13px",
Locale: conf.Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "inherit", Editor: &conf.Editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit"}}
Editor: &conf.Editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit"}}) conf.Wide.Users = append(conf.Wide.Users, newUser)
if !conf.Save() { if !conf.Save() {
return UserCreateError return UserCreateError
} }
conf.CreateWorkspaceDir(workspace) conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username) conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
glog.Infof("Created a user [%s]", username) glog.Infof("Created a user [%s]", username)
return UserCreated return UserCreated
} }
// helloWorld generates the 'Hello, 世界' source code in workspace/src/hello/main.go.
func helloWorld(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
glog.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
glog.Error(err)
os.Exit(-1)
}
fout.WriteString(`package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
`)
fout.Close()
}

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Shell. // Shell.
package shell package shell
@ -28,7 +42,6 @@ var ShellWS = map[string]*util.WSChannel{}
// IndexHandler handles request of Shell index. // IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) { func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
@ -68,6 +81,11 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
// WSHandler handles request of creating Shell channel. // WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
@ -75,26 +93,22 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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()}
ShellWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"} ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
wsChan.Conn.WriteJSON(&ret) err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
ShellWS[sid] = &wsChan
glog.V(4).Infof("Open a new [Shell] with session [%s], %d", sid, len(ShellWS)) glog.V(4).Infof("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
input := map[string]interface{}{} input := map[string]interface{}{}
for { for {
if err := wsChan.Conn.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Shell WS ERROR: " + err.Error()) glog.Error("Shell WS ERROR: " + err.Error())
return return
} }
@ -121,7 +135,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
ret = map[string]interface{}{"output": output, "cmd": "shell-output"} ret = map[string]interface{}{"output": output, "cmd": "shell-output"}
if err := wsChan.Conn.WriteJSON(&ret); err != nil { if err := wsChan.WriteJSON(&ret); err != nil {
glog.Error("Shell WS ERROR: " + err.Error()) glog.Error("Shell WS ERROR: " + err.Error())
return return
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
#dialogAboutDialog .dialog-main { #dialogAboutDialog .dialog-main {
background-color: #FFF; background-color: #FFF;
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
/* start reset & function */ /* start reset & function */
body { body {
font-size: 13px; font-size: 13px;
@ -44,3 +60,32 @@ button {
display: none; display: none;
} }
/* end reset & function */ /* end reset & function */
/* start common */
.ft-small {
color: #999;
font-size: 12px;
}
.list li {
cursor: pointer;
line-height: 20px;
padding: 0 3px;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.list li.selected,
.list li:hover{
background-color: #3875d7;
color: #FFF;
}
.list li.selected .ft-small,
.list li:hover .ft-small {
color: #FFF;
}
/* end common */

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
/** /**
* dialig style * dialig style
* *
@ -81,3 +97,27 @@
width: 100%; width: 100%;
margin: 2px auto; margin: 2px auto;
} }
#dialogGoFilePrompt > ul {
position: relative;
height: 260px;
overflow: auto;
margin-top: 5px;
background-color: #FFF;
border: 1px solid #9B9B9B;
}
#dialogPreference {
margin: 10px;
border: 1px solid #A4A4A4;
border-top-width: 0;
}
#dialogPreference .select {
width: 100%;
margin: 2px auto;
}
#dialogPreference .tabs-panel {
padding: 10px;
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
#shellOutput, #shellInput { #shellOutput, #shellInput {
width: 100%; width: 100%;
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
/* start side */ /* start side */
.side { .side {
background-color: #FFF; background-color: #FFF;
@ -29,6 +45,7 @@
border-width: 0; border-width: 0;
color: #fff; color: #fff;
height: 18px; height: 18px;
opacity: 1;
} }
.ztree li a:hover { .ztree li a:hover {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
.wrapper { .wrapper {
margin: 0 auto; margin: 0 auto;
width: 980px; width: 980px;
@ -71,6 +87,33 @@
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
} }
.button {
width: 100%;
color: #fff;
font-size: 16px;
text-shadow: 0 -1px 0 rgba(0,0,0,0.25);
background-color: #60b044;
background-image: linear-gradient(#8add6d, #60b044);
border: 1px solid #5ca941;
padding: 10px;
}
.button:hover {
background-color: #569e3d;
background-image: linear-gradient(#79d858, #569e3d);
border-color: #4a993e;
}
.header .button {
color: #fff;
padding: 0 8px;
font-size: 13px;
}
.header .button:hover {
color: #FFF;
}
#msg { #msg {
background-color: #fcdede; background-color: #fcdede;
border: 1px solid #d2b2b2; border: 1px solid #d2b2b2;
@ -112,6 +155,10 @@
} }
.form.sign-up { .form.sign-up {
margin-top: -20px; margin-top: -71px;
}
#signUpBtn {
margin-top: 20px;
} }
/* end sign up */ /* end sign up */

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
#startPage { #startPage {
padding: 50px 70px; padding: 50px 70px;
line-height: 28px; line-height: 28px;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
/* start icon */ /* start icon */
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
@ -193,7 +209,8 @@
top: 1px; top: 1px;
} }
.edit-panel .tabs .ico { .edit-panel .tabs .ico,
#dialogGoFilePrompt .ico {
background-image: url("../images/ico-file.png"); background-image: url("../images/ico-file.png");
float: left; float: left;
height: 16px; height: 16px;
@ -243,6 +260,10 @@
background: #08f; background: #08f;
color: white; color: white;
} }
.CodeMirror div.CodeMirror-cursor {
border-left: 2px solid #333;
}
/* end editor */ /* end editor */
/* start bottom-window-group */ /* start bottom-window-group */
@ -315,31 +336,6 @@
width: 50px; width: 50px;
padding: 0 5px; padding: 0 5px;
} }
.bottom-window-group .search li {
cursor: pointer;
line-height: 20px;
padding: 0 3px;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bottom-window-group .search li.selected {
background-color: #3875d7;
color: #FFF;
}
.bottom-window-group .search .path {
color: #999;
font-size: 12px;
}
.bottom-window-group .search li.selected .path {
color: #FFF;
}
/* end bottom-window-group */ /* end bottom-window-group */
/* start footer */ /* start footer */

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var bottomGroup = { var bottomGroup = {
tabs: undefined, tabs: undefined,
searchTab: undefined, searchTab: undefined,

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011, Liyuan Li * Copyright (c) 2014, B3log
* *
* 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.
@ -13,6 +13,7 @@
* 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.
*/ */
(function ($) { (function ($) {
$.fn.extend({ $.fn.extend({
dialog: { dialog: {
@ -143,6 +144,9 @@
} else { } else {
// 20(footer) + 23(header) // 20(footer) + 23(header)
top = parseInt((windowH - dialogH - 43) / 2); top = parseInt((windowH - dialogH - 43) / 2);
if (top < 0) {
top = 0;
}
left = parseInt((windowW - dialogW) / 2); left = parseInt((windowW - dialogW) / 2);
} }
$dialog.css({ $dialog.css({
@ -175,6 +179,10 @@
} }
}); });
$(window).resize(function () {
$(".dialog-background").height($("body").height());
});
if (typeof settings.afterInit === "function") { if (typeof settings.afterInit === "function") {
settings.afterInit(); settings.afterInit();
} }
@ -210,12 +218,12 @@
if (positionX > $(window).width() - $(dialog).width()) { if (positionX > $(window).width() - $(dialog).width()) {
positionX = $(window).width() - $(dialog).width(); positionX = $(window).width() - $(dialog).width();
} }
if (positionY < 0) {
positionY = 0;
}
if (positionY > $(window).height() - $(dialog).height()) { if (positionY > $(window).height() - $(dialog).height()) {
positionY = $(window).height() - $(dialog).height(); positionY = $(window).height() - $(dialog).height();
} }
if (positionY < 0) {
positionY = 0;
}
dialog.style.left = positionX + "px"; dialog.style.left = positionX + "px";
dialog.style.top = positionY + "px"; dialog.style.top = positionY + "px";
}; };
@ -231,7 +239,7 @@
_document.ondragstart = null; _document.ondragstart = null;
_document.onselectstart = null; _document.onselectstart = null;
_document.onselect = null; _document.onselect = null;
} };
}); });
}, },
_close: function (id, settings) { _close: function (id, settings) {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var editors = { var editors = {
data: [], data: [],
tabs: {}, tabs: {},
@ -94,6 +110,7 @@ var editors = {
id: ".edit-panel", id: ".edit-panel",
clickAfter: function (id) { clickAfter: function (id) {
if (id === 'startPage') { if (id === 'startPage') {
$(".footer .cursor").text('');
return false; return false;
} }
@ -109,7 +126,11 @@ var editors = {
} }
} }
var cursor = wide.curEditor.getCursor();
wide.curEditor.setCursor(cursor);
wide.curEditor.focus(); wide.curEditor.focus();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
}, },
removeBefore: function (id) { removeBefore: function (id) {
if (id === 'startPage') { // 当前关闭的 tab 是起始页 if (id === 'startPage') { // 当前关闭的 tab 是起始页
@ -117,7 +138,6 @@ var editors = {
return true; return true;
} }
// 移除编辑器
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) { if (editors.data[i].id === id) {
if (editors.data[i].editor.doc.isClean()) { if (editors.data[i].editor.doc.isClean()) {
@ -163,6 +183,7 @@ var editors = {
tree.fileTree.cancelSelectedNode(); tree.fileTree.cancelSelectedNode();
wide.curNode = undefined; wide.curNode = undefined;
wide.curEditor = undefined; wide.curEditor = undefined;
$(".footer .cursor").text('');
return false; return false;
} }
@ -182,6 +203,9 @@ var editors = {
break; break;
} }
} }
var cursor = wide.curEditor.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
} }
}); });
@ -347,6 +371,11 @@ var editors = {
}); });
CodeMirror.commands.autocompleteAfterDot = function (cm) { CodeMirror.commands.autocompleteAfterDot = function (cm) {
var token = cm.getTokenAt(cm.getCursor());
if ("comment" === token.type) {
return CodeMirror.Pass;
}
setTimeout(function () { setTimeout(function () {
if (!cm.state.completionActive) { if (!cm.state.completionActive) {
cm.showHint({hint: CodeMirror.hint.go, completeSingle: false}); cm.showHint({hint: CodeMirror.hint.go, completeSingle: false});
@ -413,33 +442,11 @@ var editors = {
return; return;
} }
var cursorLine = data.cursorLine; var tId = tree.getTIdByPath(data.path);
var cursorCh = data.cursorCh; wide.curNode = tree.fileTree.getNodeByTId(tId);
tree.fileTree.selectNode(wide.curNode);
var request = newWideRequest(); tree.openFile(wide.curNode, CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1));
request.path = data.path;
$.ajax({
type: 'POST',
url: '/file',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogAlert").dialog("open", data.msg);
return false;
}
var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId);
tree.fileTree.selectNode(wide.curNode);
data.cursorLine = cursorLine;
data.cursorCh = cursorCh;
editors.newEditor(data);
}
});
} }
}); });
}; };
@ -469,7 +476,7 @@ var editors = {
}; };
}, },
appendSearch: function (data, type, key) { appendSearch: function (data, type, key) {
var searcHTML = '<ul>'; var searcHTML = '<ul class="list">';
for (var i = 0, ii = data.length; i < ii; i++) { for (var i = 0, ii = data.length; i < ii; i++) {
var contents = data[i].contents[0], var contents = data[i].contents[0],
@ -479,7 +486,7 @@ var editors = {
+ contents.substring(index + key.length); + contents.substring(index + key.length);
searcHTML += '<li title="' + data[i].path + '">' searcHTML += '<li title="' + data[i].path + '">'
+ contents + "&nbsp;&nbsp;&nbsp;&nbsp;<span class='path'>" + data[i].path + contents + "&nbsp;&nbsp;&nbsp;&nbsp;<span class='ft-small'>" + data[i].path
+ '<i class="position" data-line="' + '<i class="position" data-line="'
+ data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':' + data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':'
+ data[i].ch + ')</i></span></li>'; + data[i].ch + ')</i></span></li>';
@ -544,33 +551,10 @@ var editors = {
$(".bottom-window-group .search").focus(); $(".bottom-window-group .search").focus();
}, },
// 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab. // 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab.
newEditor: function (data) { newEditor: function (data, cursor) {
$(".toolbars").show(); $(".toolbars").show();
var id = wide.curNode.tId; var id = wide.curNode.tId;
var cursor = CodeMirror.Pos(0, 0);
if (data.cursorLine && data.cursorCh) {
cursor = CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1);
}
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) {
editors.tabs.setCurrent(id);
wide.curEditor = editors.data[i].editor;
var editor = wide.curEditor;
editor.setCursor(cursor);
var half = Math.floor(editor.getScrollInfo().clientHeight / editor.defaultTextHeight() / 2);
var cursorCoords = editor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
editor.scrollTo(0, cursorCoords.top);
editor.focus();
return false;
}
}
editors.tabs.add({ editors.tabs.add({
id: id, id: id,
title: '<span title="' + wide.curNode.path + '"><span class="' title: '<span title="' + wide.curNode.path + '"><span class="'
@ -580,9 +564,6 @@ var editors = {
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-get', 'go-install']); menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-get', 'go-install']);
var rulers = [];
rulers.push({color: "#ccc", column: 120, lineStyle: "dashed"});
var textArea = document.getElementById("editor" + id); var textArea = document.getElementById("editor" + id);
textArea.value = data.content; textArea.value = data.content;
@ -592,11 +573,12 @@ var editors = {
autoCloseBrackets: true, autoCloseBrackets: true,
matchBrackets: true, matchBrackets: true,
highlightSelectionMatches: {showToken: /\w/}, highlightSelectionMatches: {showToken: /\w/},
rulers: rulers, rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
styleActiveLine: true, styleActiveLine: true,
theme: 'wide', theme: 'wide',
indentUnit: 4, indentUnit: 4,
foldGutter: true, foldGutter: true,
cursorHeight: 1,
path: data.path, path: data.path,
extraKeys: { extraKeys: {
"Ctrl-\\": "autocompleteAnyWord", "Ctrl-\\": "autocompleteAnyWord",
@ -610,7 +592,7 @@ var editors = {
wide.saveFile(); wide.saveFile();
}, },
"Shift-Ctrl-S": function () { "Shift-Ctrl-S": function () {
wide.saveAllFiles(); menu.saveAllFiles();
}, },
"Shift-Alt-F": function () { "Shift-Alt-F": function () {
var currentPath = editors.getCurrentPath(); var currentPath = editors.getCurrentPath();
@ -628,26 +610,121 @@ var editors = {
} }
}, },
"Shift-Ctrl-Up": function (cm) { "Shift-Ctrl-Up": function (cm) {
var cursor = cm.getCursor(); var content = '',
var line = cursor.line; selectoion = cm.listSelections()[0];
var content = cm.getLine(line);
if (0 === line) { var from = selectoion.anchor,
cm.replaceRange("", CodeMirror.Pos(0)); to = selectoion.head;
line++; if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
} }
cm.replaceRange("\n" + content, CodeMirror.Pos(line - 1)); for (var i = from.line, max = to.line; i <= max; i++) {
cm.setCursor(cursor); if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
cm.setSelection(CodeMirror.Pos(from.line, from.ch),
CodeMirror.Pos(to.line, to.ch));
}, },
"Shift-Ctrl-Down": function (cm) { "Shift-Ctrl-Down": function (cm) {
var cursor = cm.getCursor(); var content = '',
var line = cursor.line; selectoion = cm.listSelections()[0];
var content = cm.getLine(line);
cm.replaceRange("\n", CodeMirror.Pos(line)); var from = selectoion.anchor,
cm.replaceRange(content, CodeMirror.Pos(line + 1)); to = selectoion.head;
cm.setCursor(CodeMirror.Pos(line + 1, cursor.ch)); if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
for (var i = from.line, max = to.line; i <= max; i++) {
if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
var offset = replaceToLine - from.line + 1;
cm.setSelection(CodeMirror.Pos(from.line + offset, from.ch),
CodeMirror.Pos(to.line + offset, to.ch));
},
"Shift-Alt-Up": function (cm) {
var selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
if (from.line === 0) {
return false;
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange('\n' + cm.getLine(from.line - 1), CodeMirror.Pos(replaceToLine));
if (from.line === 1) {
// 移除第一行的换行
cm.replaceRange('', CodeMirror.Pos(0, 0),
CodeMirror.Pos(1, 0));
} else {
cm.replaceRange('', CodeMirror.Pos(from.line - 2, cm.getLine(from.line - 2).length),
CodeMirror.Pos(from.line - 1, cm.getLine(from.line - 1).length));
}
cm.setSelection(CodeMirror.Pos(from.line - 1, from.ch),
CodeMirror.Pos(to.line - 1, to.ch));
},
"Shift-Alt-Down": function (cm) {
var selectoion = cm.listSelections()[0];
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
if (to.line === cm.lastLine()) {
return false;
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
// 把选中的下一行添加到选中区域的上一行
if (from.line === 0) {
cm.replaceRange(cm.getLine(replaceToLine + 1) + '\n', CodeMirror.Pos(0, 0));
} else {
cm.replaceRange('\n' + cm.getLine(replaceToLine + 1), CodeMirror.Pos(from.line - 1));
}
// 删除选中的下一行
cm.replaceRange('', CodeMirror.Pos(replaceToLine + 1, cm.getLine(replaceToLine + 1).length),
CodeMirror.Pos(replaceToLine + 2, cm.getLine(replaceToLine + 2).length));
cm.setSelection(CodeMirror.Pos(from.line + 1, from.ch),
CodeMirror.Pos(to.line + 1, to.ch));
} }
} }
}); });
@ -657,7 +734,6 @@ var editors = {
var cursor = cm.getCursor(); var cursor = cm.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |'); $(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
// TODO: 关闭 tab 的时候要重置
}); });
editor.on('focus', function (cm) { editor.on('focus', function (cm) {
@ -700,16 +776,19 @@ var editors = {
editor.setOption("autoCloseTags", true); editor.setOption("autoCloseTags", true);
} }
editor.setCursor(cursor);
var half = Math.floor(editor.getScrollInfo().clientHeight / editor.defaultTextHeight() / 2);
var cursorCoords = editor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
editor.scrollTo(0, cursorCoords.top);
wide.curEditor = editor; wide.curEditor = editor;
editors.data.push({ editors.data.push({
"editor": editor, "editor": editor,
"id": id "id": id
}); });
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
var half = Math.floor(wide.curEditor.getScrollInfo().clientHeight / wide.curEditor.defaultTextHeight() / 2);
var cursorCoords = wide.curEditor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
wide.curEditor.scrollTo(0, cursorCoords.top);
editor.setCursor(cursor);
editor.focus();
} }
}; };

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var hotkeys = { var hotkeys = {
defaultKeyMap: { defaultKeyMap: {
// Ctrl+0 焦点切换到当前编辑器 // Ctrl+0 焦点切换到当前编辑器
@ -15,7 +31,7 @@ var hotkeys = {
which: 49 which: 49
}, },
// Ctrl+4 焦点切换到输出窗口 // Ctrl+4 焦点切换到输出窗口
goOutPut: { goOutput: {
ctrlKey: true, ctrlKey: true,
altKey: false, altKey: false,
shiftKey: false, shiftKey: false,
@ -63,7 +79,21 @@ var hotkeys = {
shiftKey: false, shiftKey: false,
which: 81 which: 81
}, },
// F6 构建并运行 // Shift+Alt+O 跳转到文件
goFile: {
ctrlKey: false,
altKey: true,
shiftKey: true,
which: 79
},
// F5 Build
build: {
ctrlKey: false,
altKey: false,
shiftKey: false,
which: 116
},
// F6 Build & Run
buildRun: { buildRun: {
ctrlKey: false, ctrlKey: false,
altKey: false, altKey: false,
@ -71,6 +101,60 @@ var hotkeys = {
which: 117 which: 117
} }
}, },
bindList: function ($source, $list, enterFun) {
$list.data("index", 0);
$source.keydown(function (event) {
var index = $list.data("index"),
count = $list.find("li").length;
if (count === 0) {
return true;
}
if (event.which === 38) { // up
index--;
if (index < 0) {
index = count - 1;
}
}
if (event.which === 40) { // down
index++;
if (index > count - 1) {
index = 0;
}
}
var $selected = $list.find("li:eq(" + index + ")");
if (event.which === 13) { // enter
enterFun($selected);
}
$list.find("li").removeClass("selected");
$list.data("index", index);
$selected.addClass("selected");
if (index === 0) {
$list.scrollTop(0);
} else {
if ($selected[0].offsetTop + $list.scrollTop() > $list.height()) {
if (event.which === 40) {
$list.scrollTop($list.scrollTop() + $selected.height());
} else {
$list.scrollTop($selected[0].offsetTop);
}
} else {
$list.scrollTop(0);
}
}
// 阻止上下键改变光标位置
if (event.which === 38 || event.which === 40 || event.which === 13) {
return false;
}
});
},
_bindOutput: function () { _bindOutput: function () {
$(".bottom-window-group .output").keydown(function (event) { $(".bottom-window-group .output").keydown(function (event) {
event.preventDefault(); event.preventDefault();
@ -241,8 +325,8 @@ var hotkeys = {
return; return;
} }
if (event.ctrlKey === hotKeys.goOutPut.ctrlKey if (event.ctrlKey === hotKeys.goOutput.ctrlKey
&& event.which === hotKeys.goOutPut.which) { // Ctrl+4 焦点切换到输出窗口 && event.which === hotKeys.goOutput.which) { // Ctrl+4 焦点切换到输出窗口
bottomGroup.tabs.setCurrent("output"); bottomGroup.tabs.setCurrent("output");
windows.flowBottom(); windows.flowBottom();
@ -339,12 +423,26 @@ var hotkeys = {
return false; return false;
} }
if (event.which === hotKeys.buildRun.which) { // F6 构建并运行 if (event.which === hotKeys.build.which) { // F5 Build
wide.run(); menu.build();
event.preventDefault(); event.preventDefault();
return; return;
} }
if (event.which === hotKeys.buildRun.which) { // F6 Build & Run
menu.run();
event.preventDefault();
return;
}
if (event.ctrlKey === hotKeys.goFile.ctrlKey
&& event.altKey === hotKeys.goFile.altKey
&& event.shiftKey === hotKeys.goFile.shiftKey
&& event.which === hotKeys.goFile.which) { // Shift+Alt+O 跳转到文件
$("#dialogGoFilePrompt").dialog("open");
}
}); });
}, },
init: function () { init: function () {

View File

@ -1,6 +1,24 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var menu = { var menu = {
init: function () { init: function () {
this.subMenu(); this.subMenu();
this._initPreference();
this._initAbout();
// 点击子菜单后消失 // 点击子菜单后消失
$(".frame li").click(function () { $(".frame li").click(function () {
@ -8,6 +26,34 @@ var menu = {
$(".menu > ul > li > a, .menu > ul> li > span").removeClass("selected"); $(".menu > ul > li > a, .menu > ul> li > span").removeClass("selected");
}); });
}, },
_initAbout: function () {
$("#dialogAbout").load('/about', function () {
$("#dialogAbout").dialog({
"modal": true,
"height": 460,
"width": 800,
"title": config.label.about,
"hideFooter": true,
"afterOpen": function () {
$.ajax({
url: "http://rhythm.b3log.org/version/wide/latest",
type: "GET",
dataType: "jsonp",
jsonp: "callback",
success: function (data, textStatus) {
if ($("#dialogAbout .version").text() === data.wideVersion) {
$(".upgrade").text(config.label.uptodate);
} else {
$(".upgrade").html(config.label.new_version_available + config.label.colon
+ "<a href='" + data.wideDownload
+ "' target='_blank'>" + data.wideVersion + "</a>");
}
}
});
}
});
});
},
disabled: function (list) { disabled: function (list) {
for (var i = 0, max = list.length; i < max; i++) { for (var i = 0, max = list.length; i < max; i++) {
$(".menu li." + list[i]).addClass("disabled"); $(".menu li." + list[i]).addClass("disabled");
@ -35,5 +81,321 @@ var menu = {
$(this).addClass("selected"); $(this).addClass("selected");
}); });
}); });
} },
openPreference: function () {
$("#dialogPreference").dialog("open");
},
saveAllFiles: function () {
if ($(".menu li.save-all").hasClass("disabled")) {
return false;
}
for (var i = 0, ii = editors.data.length; i < ii; i++) {
var path = tree.fileTree.getNodeByTId(editors.data[i].id).path;
var editor = editors.data[i].editor;
if ("text/x-go" === editor.getOption("mode")) {
wide.fmt(path, editor);
} else {
wide._save(path, editor);
}
}
},
closeAllFiles: function () {
if ($(".menu li.close-all").hasClass("disabled")) {
return false;
}
// 设置全部关闭标识
var removeData = [];
$(".edit-panel .tabs > div").each(function (i) {
if (i !== 0) {
removeData.push($(this).data("index"));
}
});
$("#dialogCloseEditor").data("removeData", removeData);
// 开始关闭
$(".edit-panel .tabs .ico-close:eq(0)").click();
},
exit: function () {
var request = newWideRequest();
$.ajax({
type: 'POST',
url: '/logout',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (data.succ) {
window.location.href = "/login";
}
}
});
},
openAbout: function () {
$("#dialogAbout").dialog("open");
},
goget: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.go-get").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/get',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
goinstall: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.go-install").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/install',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
// 测试.
test: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.test").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/test',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
// Build & Run.
run: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.run").hasClass("disabled")) {
return false;
}
if ($(".toolbars .ico-stop").length === 1) {
wide.stop();
return false;
}
var request = newWideRequest();
request.file = currentPath;
request.code = wide.curEditor.getValue();
request.nextCmd = "run";
$.ajax({
type: 'POST',
url: '/build',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
$(".toolbars .ico-buildrun").addClass("ico-stop")
.removeClass("ico-buildrun").attr("title", config.label.stop);
}
});
},
// Build.
build: function () {
menu.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.build").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
request.code = wide.curEditor.getValue();
request.nextCmd = ""; // 只构建,无下一步操作
$.ajax({
type: 'POST',
url: '/build',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
_initPreference: function () {
$("#dialogPreference").load('/preference', function () {
$("#localeSelect").on('change', function () {
var $dialogPreference = $("#dialogPreference"),
$input = $dialogPreference.find("input[name=locale]")
$input.val(this.value);
});
$("#goFmtSelect").on('change', function () {
var $dialogPreference = $("#dialogPreference"),
$input = $dialogPreference.find("input[name=goFmt]")
$input.val(this.value);
});
$("#dialogPreference input").keyup(function () {
var isChange = false;
$("#dialogPreference input").each(function () {
if ($(this).val() !== $(this).data("value")) {
isChange = true;
}
});
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (isChange) {
$okBtn.prop("disabled", false);
} else {
$okBtn.prop("disabled", true);
}
});
$("#dialogPreference select").on("change", function () {
var isChange = false;
$("#dialogPreference input").each(function () {
if ($(this).val() !== $(this).data("value")) {
isChange = true;
}
});
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (isChange) {
$okBtn.prop("disabled", false);
} else {
$okBtn.prop("disabled", true);
}
});
$("#dialogPreference").dialog({
"modal": true,
"height": 460,
"width": 800,
"title": config.label.perference,
"okText": config.label.apply,
"cancelText": config.label.cancel,
"afterOpen": function () {
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
$okBtn.prop("disabled", true);
},
"ok": function () {
var request = newWideRequest(),
$dialogPreference = $("#dialogPreference"),
$fontFamily = $dialogPreference.find("input[name=fontFamily]"),
$fontSize = $dialogPreference.find("input[name=fontSize]"),
$editorFontFamily = $dialogPreference.find("input[name=editorFontFamily]"),
$editorFontSize = $dialogPreference.find("input[name=editorFontSize]"),
$editorLineHeight = $dialogPreference.find("input[name=editorLineHeight]"),
$goFmt = $dialogPreference.find("input[name=goFmt]"),
$workspace = $dialogPreference.find("input[name=workspace]"),
$password = $dialogPreference.find("input[name=password]"),
$locale = $dialogPreference.find("input[name=locale]");
$.extend(request, {
"fontFamily": $fontFamily.val(),
"fontSize": $fontSize.val(),
"editorFontFamily": $editorFontFamily.val(),
"editorFontSize": $editorFontSize.val(),
"editorLineHeight": $editorLineHeight.val(),
"goFmt": $goFmt.val(),
"workspace": $workspace.val(),
"password": $password.val(),
"locale": $locale.val()
});
$.ajax({
type: 'POST',
url: '/preference',
data: JSON.stringify(request),
success: function (data, textStatus, jqXHR) {
if (!data.succ) {
return false;
}
$fontFamily.data("value", $fontFamily.val());
$fontSize.data("value", $fontSize.val());
$editorFontFamily.data("value", $editorFontFamily.val());
$editorFontSize.data("value", $editorFontSize.val());
$editorLineHeight.data("value", $editorLineHeight.val());
$goFmt.data("value", $goFmt.val());
$workspace.data("value", $workspace.val());
$password.data("value", $password.val());
$locale.data("value", $locale.val());
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
$okBtn.prop("disabled", true);
}
});
}
});
new Tabs({
id: ".preference"
});
});
},
}; };

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var notification = { var notification = {
init: function () { init: function () {
$(".notification-count").click(function () { $(".notification-count").click(function () {
@ -20,6 +36,12 @@ var notification = {
$notification = $('.bottom-window-group .notification > table'), $notification = $('.bottom-window-group .notification > table'),
notificationHTML = ''; notificationHTML = '';
if (data.cmd && "init-notification" === data.cmd) {
console.log('[notification onmessage]' + e.data);
return;
}
notificationHTML += '<tr><td class="severity">' + data.severity notificationHTML += '<tr><td class="severity">' + data.severity
+ '</td><td class="message">' + data.message + '</td><td class="message">' + data.message
+ '</td><td class="type">' + data.type + '</td></tr>'; + '</td><td class="type">' + data.type + '</td></tr>';

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: http://codemirror.net/LICENSE

View File

@ -1,6 +1,22 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
.cm-s-wide span.cm-meta {color: rgb(98,143,181);} .cm-s-wide span.cm-meta {color: rgb(98,143,181);}
.cm-s-wide span.cm-keyword { color: rgb(0,0,230); } .cm-s-wide span.cm-keyword { color: rgb(0,0,230); }
.cm-s-wide span.cm-atom {color: #219;} .cm-s-wide span.cm-atom {color: rgb(153,51,204);}
.cm-s-wide span.cm-number {color: #B35E4D;} .cm-s-wide span.cm-number {color: #B35E4D;}
.cm-s-wide span.cm-def {color: #00f;} .cm-s-wide span.cm-def {color: #00f;}
.cm-s-wide span.cm-variable {color: black;} .cm-s-wide span.cm-variable {color: black;}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var session = { var session = {
init: function () { init: function () {
this._initWS(); this._initWS();
@ -47,7 +63,20 @@ var session = {
// expand tree // expand tree
for (var j = 0, jj = fileTree.length; j < jj; j++) { for (var j = 0, jj = fileTree.length; j < jj; j++) {
if (nodes[i].path === fileTree[j]) { if (nodes[i].path === fileTree[j]) {
tree.fileTree.expandNode(nodes[i], true, false, true); // 当父节点都展开时,才展开该节点
var parents = tree.getAllParents(tree.fileTree.getNodeByTId(nodes[i].tId)),
isOpen = true;
for (var l = 0, max = parents.length; l < max; l++) {
if (parents[l].open === false) {
isOpen = false;
}
}
if (isOpen) {
tree.fileTree.expandNode(nodes[i], true, false, true);
} else {
// 设置状态
nodes[i].open = true;
}
break; break;
} }
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var shell = { var shell = {
_shellWS: undefined, _shellWS: undefined,
_initWS: function () { _initWS: function () {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var Tabs = function (obj) { var Tabs = function (obj) {
obj._$tabsPanel = $(obj.id + " > .tabs-panel"); obj._$tabsPanel = $(obj.id + " > .tabs-panel");
obj._$tabs = $(obj.id + " > .tabs"); obj._$tabs = $(obj.id + " > .tabs");

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var tree = { var tree = {
fileTree: undefined, fileTree: undefined,
// 递归获取当前节点展开中的最后一个节点 // 递归获取当前节点展开中的最后一个节点
@ -61,7 +77,19 @@ var tree = {
return paths; return paths;
}, },
_isParents: function (tId, parentTId) { getAllParents: function (node, parents) {
if (!parents) {
parents = [];
}
if (!node || !node.parentTId) {
return parents;
} else {
parents.push(node.getParentNode());
return tree.getAllParents(node.getParentNode(), parents);
}
},
isParents: function (tId, parentTId) {
var node = tree.fileTree.getNodeByTId(tId); var node = tree.fileTree.getNodeByTId(tId);
if (!node || !node.parentTId) { if (!node || !node.parentTId) {
return false; return false;
@ -69,7 +97,7 @@ var tree = {
if (node.parentTId === parentTId) { if (node.parentTId === parentTId) {
return true; return true;
} else { } else {
return tree._isParents(node.parentTId, parentTId); return tree.isParents(node.parentTId, parentTId);
} }
} }
}, },
@ -111,6 +139,58 @@ var tree = {
$("#fileRMenu").hide(); $("#fileRMenu").hide();
$("#dialogRemoveConfirm").dialog("open"); $("#dialogRemoveConfirm").dialog("open");
}, },
rename: function (it) {
if (it) {
if ($(it).hasClass("disabled")) {
return false;
}
}
$("#dirRMenu").hide();
$("#fileRMenu").hide();
$("#dialogRenamePrompt").dialog("open");
},
export: function (it) {
if (it) {
if ($(it).hasClass("disabled")) {
return false;
}
}
var request = newWideRequest();
request.path = wide.curNode.path;
$.ajax({
type: 'POST',
url: '/file/zip/new',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogAlert").dialog("open", data.msg);
return false;
}
window.open('/file/zip?path=' + wide.curNode.path + '.zip');
}
});
$("#dirRMenu").hide();
$("#fileRMenu").hide();
},
refresh: function (it) {
if (it) {
if ($(it).hasClass("disabled")) {
return false;
}
}
tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh");
$("#dirRMenu").hide();
$("#fileRMenu").hide();
},
init: function () { init: function () {
$("#file").click(function () { $("#file").click(function () {
$(this).focus(); $(this).focus();
@ -130,13 +210,18 @@ var tree = {
var setting = { var setting = {
data: { data: {
key: { key: {
title: "title" title: "path"
} }
}, },
view: { view: {
showTitle: true, showTitle: true,
selectedMulti: false selectedMulti: false
}, },
async: {
enable: true,
url: "/file/refresh",
autoParam: ["path"]
},
callback: { callback: {
onDblClick: function (event, treeId, treeNode) { onDblClick: function (event, treeId, treeNode) {
if (treeNode) { if (treeNode) {
@ -164,9 +249,9 @@ var tree = {
}); });
} else { // 右击了目录 } else { // 右击了目录
if (wide.curNode.removable) { if (wide.curNode.removable) {
$("#dirRMenu .remove").removeClass("disabled"); $("#dirRMenu .remove, #dirRMenu .rename").removeClass("disabled");
} else { } else {
$("#dirRMenu .remove").addClass("disabled"); $("#dirRMenu .remove, #dirRMenu .rename").addClass("disabled");
} }
if (wide.curNode.creatable) { if (wide.curNode.creatable) {
@ -200,17 +285,30 @@ var tree = {
} }
} }
}); });
this._initSearch();
}, },
openFile: function (treeNode) { openFile: function (treeNode, cursor) {
wide.curNode = treeNode; wide.curNode = treeNode;
var tempCursor = cursor;
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
// 该节点文件已经打开 // 该节点文件已经打开
if (editors.data[i].id === treeNode.tId) { if (editors.data[i].id === treeNode.tId) {
editors.tabs.setCurrent(treeNode.tId); editors.tabs.setCurrent(treeNode.tId);
wide.curNode = treeNode;
wide.curEditor = editors.data[i].editor; wide.curEditor = editors.data[i].editor;
if (!tempCursor) {
tempCursor = wide.curEditor.getCursor();
}
$(".footer .cursor").text('| ' + (tempCursor.line + 1) + ':' + (tempCursor.ch + 1) + ' |');
wide.curEditor.setCursor(tempCursor);
var half = Math.floor(wide.curEditor.getScrollInfo().clientHeight / wide.curEditor.defaultTextHeight() / 2);
var cursorCoords = wide.curEditor.cursorCoords({line: tempCursor.line - half, ch: 0}, "local");
wide.curEditor.scrollTo(0, cursorCoords.top);
wide.curEditor.focus(); wide.curEditor.focus();
return false; return false;
} }
} }
@ -238,9 +336,68 @@ var tree = {
return false; return false;
} }
editors.newEditor(data); if (!tempCursor) {
tempCursor = CodeMirror.Pos(0, 0);
}
editors.newEditor(data, tempCursor);
} }
}); });
} }
},
_initSearch: function () {
$("#dialogSearchForm > input:eq(0)").keyup(function (event) {
var $okBtn = $(this).closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (event.which === 13 && !$okBtn.prop("disabled")) {
$okBtn.click();
}
if ($.trim($(this).val()) === "") {
$okBtn.prop("disabled", true);
} else {
$okBtn.prop("disabled", false);
}
});
$("#dialogSearchForm > input:eq(1)").keyup(function (event) {
var $okBtn = $(this).closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (event.which === 13 && !$okBtn.prop("disabled")) {
$okBtn.click();
}
});
$("#dialogSearchForm").dialog({
"modal": true,
"height": 62,
"width": 260,
"title": config.label.search,
"okText": config.label.search,
"cancelText": config.label.cancel,
"afterOpen": function () {
$("#dialogSearchForm > input:eq(0)").val('').focus();
$("#dialogSearchForm > input:eq(1)").val('');
$("#dialogSearchForm").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
},
"ok": function () {
var request = newWideRequest();
request.dir = wide.curNode.path;
request.text = $("#dialogSearchForm > input:eq(0)").val();
request.extension = $("#dialogSearchForm > input:eq(1)").val();
$.ajax({
type: 'POST',
url: '/file/search/text',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
return;
}
$("#dialogSearchForm").dialog("close");
editors.appendSearch(data.founds, 'founds', request.text);
}
});
}
});
} }
}; };

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var wide = { var wide = {
curNode: undefined, curNode: undefined,
curEditor: undefined, curEditor: undefined,
@ -50,6 +66,10 @@ var wide = {
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
if (!data.succ) { if (!data.succ) {
$("#dialogRemoveConfirm").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false; return false;
} }
@ -66,7 +86,7 @@ var wide = {
} }
} else { } else {
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (tree._isParents(editors.data[i].id, wide.curNode.tId)) { if (tree.isParents(editors.data[i].id, wide.curNode.tId)) {
$(".edit-panel .tabs > div[data-index=" + editors.data[i].id + "]").find(".ico-close").click(); $(".edit-panel .tabs > div[data-index=" + editors.data[i].id + "]").find(".ico-close").click();
i--; i--;
ii--; ii--;
@ -78,6 +98,65 @@ var wide = {
} }
}); });
$("#dialogRenamePrompt").dialog({
"modal": true,
"height": 52,
"width": 260,
"title": config.label.rename,
"okText": config.label.rename,
"cancelText": config.label.cancel,
"afterOpen": function () {
var index = wide.curNode.name.lastIndexOf("."),
name = wide.curNode.name.substring(0, index);
if (index === -1) {
name = wide.curNode.name;
}
$("#dialogRenamePrompt").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
$("#dialogRenamePrompt > input").val(name).select().focus();
},
"ok": function () {
var name = $("#dialogRenamePrompt > input").val(),
request = newWideRequest();
request.oldPath = wide.curNode.path;
var pathIndex = wide.curNode.path.lastIndexOf(config.pathSeparator),
nameIndex = wide.curNode.name.lastIndexOf("."),
ext = wide.curNode.name.substring(nameIndex, wide.curNode.name.length);
request.newPath = wide.curNode.path.substring(0, pathIndex) + config.pathSeparator
+ name + ext;
$.ajax({
type: 'POST',
url: '/file/rename',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogRenamePrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false;
}
$("#dialogRenamePrompt").dialog("close");
// update tree node
wide.curNode.name = name + ext;
wide.curNode.title = request.newPath;
wide.curNode.path = request.newPath;
tree.fileTree.updateNode(wide.curNode);
// update open editor tab name
var $currentSpan = $(".edit-panel .tabs > div[data-index=" + wide.curNode.tId + "] > span:eq(0)");
$currentSpan.attr("title", request.newPath);
$currentSpan.html($currentSpan.find("span").html() + wide.curNode.name);
}
});
}
});
$("#dialogNewFilePrompt").dialog({ $("#dialogNewFilePrompt").dialog({
"modal": true, "modal": true,
"height": 52, "height": 52,
@ -103,43 +182,15 @@ var wide = {
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
if (!data.succ) { if (!data.succ) {
$("#dialogNewFilePrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false; return false;
} }
$("#dialogNewFilePrompt").dialog("close"); $("#dialogNewFilePrompt").dialog("close");
var suffix = name.split(".")[1], var iconSkin = wide.getClassBySuffix(name.split(".")[1]);
iconSkin = "ico-ztree-other ";
switch (suffix) {
case "html", "htm":
iconSkin = "ico-ztree-html ";
break;
case "go":
iconSkin = "ico-ztree-go ";
break;
case "css":
iconSkin = "ico-ztree-css ";
break;
case "txt":
iconSkin = "ico-ztree-text ";
break;
case "sql":
iconSkin = "ico-ztree-sql ";
break;
case "properties":
iconSkin = "ico-ztree-pro ";
break;
case "md":
iconSkin = "ico-ztree-md ";
break;
case "js", "json":
iconSkin = "ico-ztree-js ";
break;
case "xml":
iconSkin = "ico-ztree-xml ";
break;
case "jpg", "jpeg", "bmp", "gif", "png", "svg", "ico":
iconSkin = "ico-ztree-img ";
break;
}
tree.fileTree.addNodes(wide.curNode, [{ tree.fileTree.addNodes(wide.curNode, [{
"name": name, "name": name,
@ -179,6 +230,10 @@ var wide = {
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
if (!data.succ) { if (!data.succ) {
$("#dialogNewDirPrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false; return false;
} }
@ -196,6 +251,92 @@ var wide = {
} }
}); });
$("#dialogGoFilePrompt").dialog({
"modal": true,
"height": 300,
"width": 660,
"title": config.label.goto_file,
"okText": config.label.go,
"cancelText": config.label.cancel,
"afterInit": function () {
$("#dialogGoFilePrompt").on("dblclick", "li", function () {
var tId = tree.getTIdByPath($(this).find(".ft-small").text());
tree.openFile(tree.fileTree.getNodeByTId(tId));
tree.fileTree.selectNode(wide.curNode);
$("#dialogGoFilePrompt").dialog("close");
});
$("#dialogGoFilePrompt").on("click", "li", function () {
var $list = $("#dialogGoFilePrompt > .list")
$list.find("li").removeClass("selected");
$list.data("index", $(this).data("index"));
$(this).addClass("selected");
});
hotkeys.bindList($("#dialogGoFilePrompt > input"), $("#dialogGoFilePrompt > .list"), function ($selected) {
var tId = tree.getTIdByPath($selected.find(".ft-small").text());
tree.openFile(tree.fileTree.getNodeByTId(tId));
tree.fileTree.selectNode(wide.curNode);
$("#dialogGoFilePrompt").dialog("close");
});
$("#dialogGoFilePrompt > input").bind("input", function () {
var name = $("#dialogGoFilePrompt > input").val();
var request = newWideRequest();
request.path = '';
request.name = '*' + name + '*';
if (wide.curNode) {
request.path = wide.curNode.path;
}
$.ajax({
type: 'POST',
url: '/file/find/name',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
return;
}
var goFileHTML = '';
for (var i = 0, max = data.founds.length; i < max; i++) {
var path = data.founds[i].path,
name = path.substr(path.lastIndexOf(config.pathSeparator) + 1),
icoSkin = wide.getClassBySuffix(name.split(".")[1]);
if (i === 0) {
goFileHTML += '<li data-index="' + i + '" class="selected" title="'
+ path + '"><span class="'
+ icoSkin + 'ico"></span>'
+ name + '&nbsp;&nbsp;&nbsp;&nbsp;<span class="ft-small">'
+ path + '</span></li>';
} else {
goFileHTML += '<li data-index="' + i + '" title="'
+ path + '"><span class="' + icoSkin + 'ico"></span>'
+ name + '&nbsp;&nbsp;&nbsp;&nbsp;<span class="ft-small">'
+ path + '</span></li>';
}
}
$("#dialogGoFilePrompt > ul").html(goFileHTML);
}
});
});
},
"afterOpen": function () {
$("#dialogGoFilePrompt > input").val('').focus();
$("#dialogGoFilePrompt").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
$("#dialogGoFilePrompt .list").html('').data("index", 0);
},
"ok": function () {
var tId = tree.getTIdByPath($("#dialogGoFilePrompt .selected .ft-small").text());
tree.openFile(tree.fileTree.getNodeByTId(tId));
tree.fileTree.selectNode(wide.curNode);
$("#dialogGoFilePrompt").dialog("close");
}
});
$("#dialogGoLinePrompt").dialog({ $("#dialogGoLinePrompt").dialog({
"modal": true, "modal": true,
"height": 52, "height": 52,
@ -223,88 +364,6 @@ var wide = {
editor.focus(); editor.focus();
} }
}); });
$("#dialogSearchForm > input:eq(0)").keyup(function (event) {
var $okBtn = $(this).closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (event.which === 13 && !$okBtn.prop("disabled")) {
$okBtn.click();
}
if ($.trim($(this).val()) === "") {
$okBtn.prop("disabled", true);
} else {
$okBtn.prop("disabled", false);
}
});
$("#dialogSearchForm > input:eq(1)").keyup(function (event) {
var $okBtn = $(this).closest(".dialog-main").find(".dialog-footer > button:eq(0)");
if (event.which === 13 && !$okBtn.prop("disabled")) {
$okBtn.click();
}
});
$("#dialogSearchForm").dialog({
"modal": true,
"height": 62,
"width": 260,
"title": config.label.search,
"okText": config.label.search,
"cancelText": config.label.cancel,
"afterOpen": function () {
$("#dialogSearchForm > input:eq(0)").val('').focus();
$("#dialogSearchForm > input:eq(1)").val('');
$("#dialogSearchForm").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
},
"ok": function () {
var request = newWideRequest();
request.dir = wide.curNode.path;
request.text = $("#dialogSearchForm > input:eq(0)").val();
request.extension = $("#dialogSearchForm > input:eq(1)").val();
$.ajax({
type: 'POST',
url: '/file/search/text',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
return;
}
$("#dialogSearchForm").dialog("close");
editors.appendSearch(data.founds, 'founds', request.text);
}
});
}
});
$("#dialogAbout").load('/about', function () {
$("#dialogAbout").dialog({
"modal": true,
"height": 460,
"width": 800,
"title": config.label.about,
"hideFooter": true,
"afterOpen": function () {
$.ajax({
url: "http://rhythm.b3log.org/version/wide/latest",
type: "GET",
dataType: "jsonp",
jsonp: "callback",
success: function (data, textStatus) {
if ($("#dialogAbout .version").text() === data.wideVersion) {
$(".upgrade").text(config.label.uptodate);
} else {
$(".upgrade").html(config.label.new_version_available + config.label.colon
+ "<a href='" + data.wideDownload
+ "' target='_blank'>" + data.wideVersion + "</a>");
}
}
});
}
});
});
}, },
_initLayout: function () { _initLayout: function () {
var mainH = $(window).height() - $(".menu").height() - $(".footer").height(), var mainH = $(window).height() - $(".menu").height() - $(".footer").height(),
@ -343,7 +402,7 @@ var wide = {
switch (data.cmd) { switch (data.cmd) {
case 'run': // 正在运行 case 'run': // 正在运行
bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output); bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + '<pre>' + data.output + '</pre>');
wide.curProcessId = data.pid; wide.curProcessId = data.pid;
break; break;
@ -358,17 +417,17 @@ var wide = {
case 'start-test': case 'start-test':
case 'start-install': case 'start-install':
case 'start-get': case 'start-get':
bottomGroup.fillOutput(data.output); bottomGroup.fillOutput('<pre>' + data.output + '</pre>');
break; break;
case 'go test': case 'go test':
case 'go install': case 'go install':
case 'go get': case 'go get':
bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output); bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + '<pre>' + data.output + '</pre>');
break; break;
case 'build': case 'build':
bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output); bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + '<pre>' + data.output + '</pre>');
if (data.lints) { // 说明编译有错误输出 if (data.lints) { // 说明编译有错误输出
for (var i = 0; i < data.lints.length; i++) { for (var i = 0; i < data.lints.length; i++) {
@ -495,55 +554,9 @@ var wide = {
wide._save(path, wide.curEditor); wide._save(path, wide.curEditor);
}, },
saveAllFiles: function () {
if ($(".menu li.save-all").hasClass("disabled")) {
return false;
}
for (var i = 0, ii = editors.data.length; i < ii; i++) {
var path = tree.fileTree.getNodeByTId(editors.data[i].id).path;
var editor = editors.data[i].editor;
if ("text/x-go" === editor.getOption("mode")) {
wide.fmt(path, editor);
} else {
wide._save(path, editor);
}
}
},
closeAllFiles: function () {
if ($(".menu li.close-all").hasClass("disabled")) {
return false;
}
// 设置全部关闭标识
var removeData = [];
$(".edit-panel .tabs > div").each(function (i) {
if (i !== 0) {
removeData.push($(this).data("index"));
}
});
$("#dialogCloseEditor").data("removeData", removeData);
// 开始关闭
$(".edit-panel .tabs .ico-close:eq(0)").click();
},
exit: function () {
var request = newWideRequest();
$.ajax({
type: 'POST',
url: '/logout',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (data.succ) {
window.location.href = "/login";
}
}
});
},
stop: function () { stop: function () {
if ($(".toolbars .ico-buildrun").length === 1) { if ($(".toolbars .ico-buildrun").length === 1) {
wide.run(); menu.run();
return false; return false;
} }
@ -565,155 +578,6 @@ var wide = {
} }
}); });
}, },
// 构建.
build: function () {
wide.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.build").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
request.code = wide.curEditor.getValue();
request.nextCmd = ""; // 只构建,无下一步操作
$.ajax({
type: 'POST',
url: '/build',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
// 构建并运行.
run: function () {
wide.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.run").hasClass("disabled")) {
return false;
}
if ($(".toolbars .ico-stop").length === 1) {
wide.stop();
return false;
}
var request = newWideRequest();
request.file = currentPath;
request.code = wide.curEditor.getValue();
request.nextCmd = "run";
$.ajax({
type: 'POST',
url: '/build',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
$(".toolbars .ico-buildrun").addClass("ico-stop")
.removeClass("ico-buildrun").attr("title", config.label.stop);
}
});
},
// 测试.
test: function () {
wide.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.test").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/test',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
goget: function () {
wide.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.go-get").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/get',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
goinstall: function () {
wide.saveAllFiles();
var currentPath = editors.getCurrentPath();
if (!currentPath) {
return false;
}
if ($(".menu li.go-install").hasClass("disabled")) {
return false;
}
var request = newWideRequest();
request.file = currentPath;
$.ajax({
type: 'POST',
url: '/go/install',
data: JSON.stringify(request),
dataType: "json",
beforeSend: function (data) {
bottomGroup.resetOutput();
},
success: function (data) {
}
});
},
gofmt: function (path, editor) { gofmt: function (path, editor) {
var cursor = editor.getCursor(); var cursor = editor.getCursor();
var scrollInfo = editor.getScrollInfo(); var scrollInfo = editor.getScrollInfo();
@ -793,8 +657,42 @@ var wide = {
wide._save(path, editor); wide._save(path, editor);
} }
}, },
openAbout: function () { getClassBySuffix: function (suffix) {
$("#dialogAbout").dialog("open"); var iconSkin = "ico-ztree-other ";
switch (suffix) {
case "html", "htm":
iconSkin = "ico-ztree-html ";
break;
case "go":
iconSkin = "ico-ztree-go ";
break;
case "css":
iconSkin = "ico-ztree-css ";
break;
case "txt":
iconSkin = "ico-ztree-text ";
break;
case "sql":
iconSkin = "ico-ztree-sql ";
break;
case "properties":
iconSkin = "ico-ztree-pro ";
break;
case "md":
iconSkin = "ico-ztree-md ";
break;
case "js", "json":
iconSkin = "ico-ztree-js ";
break;
case "xml":
iconSkin = "ico-ztree-xml ";
break;
case "jpg", "jpeg", "bmp", "gif", "png", "svg", "ico":
iconSkin = "ico-ztree-img ";
break;
}
return iconSkin;
} }
}; };

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2014, B3log
*
* 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.
*/
var windows = { var windows = {
isMaxEditor: false, isMaxEditor: false,
init: function () { init: function () {

View File

@ -0,0 +1 @@
This directory is used to hold user _admin_ customized style file `style.css`, it will be generated when wide startup automatically.

View File

@ -1,13 +0,0 @@
body,
input,
button {
font-family: Helvetica;
font-size: 13px;
}
.edit-exprinfo,
.CodeMirror,
.CodeMirror-hints {
font-family: Consolas, 'Courier New', monospace;
font-size: inherit;
}

View File

@ -10,4 +10,5 @@ button {
.CodeMirror-hints { .CodeMirror-hints {
font-family: {{.user.Editor.FontFamily}}; font-family: {{.user.Editor.FontFamily}};
font-size: {{.user.Editor.FontSize}}; font-size: {{.user.Editor.FontSize}};
line-height: {{.user.Editor.LineHeight}};
} }

79
util/file.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright (c) 2014, B3log
//
// 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 util
import (
"os"
"strings"
"github.com/golang/glog"
)
type myfile struct{}
// File utilities.
var File = myfile{}
// GetFileSize get the length in bytes of file of the specified path.
func (*myfile) GetFileSize(path string) int64 {
fi, err := os.Stat(path)
if nil != err {
return -1
}
return fi.Size()
}
// IsExist determines whether the file spcified by the given path is exists.
func (*myfile) IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
// IsBinary determines whether the specified content is a binary file content.
func (*myfile) IsBinary(content string) bool {
for _, b := range content {
if 0 == b {
return true
}
}
return false
}
// IsImg determines whether the specified extension is a image.
func (*myfile) IsImg(extension string) bool {
ext := strings.ToLower(extension)
switch ext {
case ".jpg", ".jpeg", ".bmp", ".gif", ".png", ".svg", ".ico":
return true
default:
return false
}
}
// IsDir determines whether the specified path is a directory.
func (*myfile) IsDir(path string) bool {
fio, err := os.Lstat(path)
if nil != err {
glog.Warningf("Determines whether [%s] is a directory failed: [%v]", path, err)
return false
}
return fio.IsDir()
}

101
util/go.go Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) 2014, B3log
//
// 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 util
import (
"path/filepath"
"path"
"runtime"
"strings"
"os"
)
const (
PathSeparator = string(os.PathSeparator) // OS-specific path separator
PathListSeparator = string(os.PathListSeparator) // OS-specific path list separator
)
type mygo struct{}
// Go utilities.
var Go = mygo{}
// GetAPIPath gets the Go source code path.
//
// 1. before Go 1.4: $GOROOT/src/pkg
// 2. Go 1.4 and after: $GOROOT/src
func (*mygo) GetAPIPath() string {
ret := runtime.GOROOT() + "/src/pkg" // before Go 1.4
if !File.IsExist(ret) {
ret = runtime.GOROOT() + "/src" // Go 1.4 and after
}
return filepath.FromSlash(path.Clean(ret))
}
// IsAPI determines whether the specified path belongs to Go API.
func (*mygo) IsAPI(path string) bool {
apiPath := Go.GetAPIPath()
return strings.HasPrefix(path, apiPath)
}
// GetGoFormats gets Go format tools. It may return ["gofmt", "goimports"].
func (*mygo) GetGoFormats() []string {
ret := []string {"gofmt"}
p := Go.GetExecutableInGOBIN("goimports")
if File.IsExist(p) {
ret = append(ret, "goimports")
}
return ret
}
// GetExecutableInGOBIN gets executable file under GOBIN path.
//
// The specified executable should not with extension, this function will append .exe if on Windows.
func (*mygo) GetExecutableInGOBIN(executable string) string {
if OS.IsWindows() {
executable += ".exe"
}
gopaths := filepath.SplitList(os.Getenv("GOPATH"))
for _, gopath := range gopaths {
// $GOPATH/bin/$GOOS_$GOARCH/executable
ret := gopath + PathSeparator + "bin" + PathSeparator +
os.Getenv("GOOS") + "_" + os.Getenv("GOARCH") + PathSeparator + executable
if File.IsExist(ret) {
return ret
}
// $GOPATH/bin/{runtime.GOOS}_{runtime.GOARCH}/executable
ret = gopath + PathSeparator + "bin" + PathSeparator +
runtime.GOOS + "_" + runtime.GOARCH + PathSeparator + executable
if File.IsExist(ret) {
return ret
}
// $GOPATH/bin/executable
ret = gopath + PathSeparator + "bin" + PathSeparator + executable
if File.IsExist(ret) {
return ret
}
}
// $GOBIN/executable
return os.Getenv("GOBIN") + PathSeparator + executable
}

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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.
// Utilities. // Utilities.
package util package util

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 util package util
import ( import (

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 util package util
import ( import (
@ -8,7 +22,7 @@ import (
// Recover recovers a panic. // Recover recovers a panic.
func Recover() { func Recover() {
if re := recover(); re != nil { if re := recover(); nil != re {
glog.Errorf("PANIC RECOVERED:\n %v, %s", re, debug.Stack()) glog.Errorf("PANIC RECOVERED:\n %v, %s", re, debug.Stack())
} }
} }

View File

@ -1,3 +1,17 @@
// Copyright (c) 2014, B3log
//
// 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 util package util
import ( import (

61
util/string.go Executable file
View File

@ -0,0 +1,61 @@
// Copyright (c) 2014, B3log
//
// 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 util
type str struct{}
// String utilities.
var Str = str{}
// Contains determines whether the str is in the strs.
func (*str) Contains(str string, strs []string) bool {
for _, v := range strs {
if v == str {
return true
}
}
return false
}
// LCS gets the longest common substring of s1 and s2.
//
// Refers to http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Longest_common_substring.
func (*str) LCS(s1 string, s2 string) string {
var m = make([][]int, 1+len(s1))
for i := 0; i < len(m); i++ {
m[i] = make([]int, 1+len(s2))
}
longest := 0
x_longest := 0
for x := 1; x < 1+len(s1); x++ {
for y := 1; y < 1+len(s2); y++ {
if s1[x-1] == s2[y-1] {
m[x][y] = m[x-1][y-1] + 1
if m[x][y] > longest {
longest = m[x][y]
x_longest = x
}
} else {
m[x][y] = 0
}
}
}
return s1[x_longest-longest : x_longest]
}

View File

@ -1,4 +1,17 @@
//部分代码参考(Ctrl+c)自golang bufio // Copyright (c) 2014, B3log
//
// 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 util package util
import ( import (

View File

@ -1,6 +1,21 @@
// Copyright (c) 2014, B3log
//
// 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 util package util
import ( import (
"errors"
"net/http" "net/http"
"time" "time"
@ -15,9 +30,41 @@ type WSChannel struct {
Time time.Time // the latest use time Time time.Time // the latest use time
} }
// WriteJSON writes the JSON encoding of v to the channel.
func (c *WSChannel) WriteJSON(v interface{}) (ret error) {
if nil == c.Conn {
return errors.New("connection is nil, channel has been closed")
}
defer func() {
if r := recover(); nil != r {
ret = errors.New("channel has been closed")
}
}()
return c.Conn.WriteJSON(v)
}
// ReadJSON reads the next JSON-encoded message from the channel and stores it in the value pointed to by v.
func (c *WSChannel) ReadJSON(v interface{}) (ret error) {
if nil == c.Conn {
return errors.New("connection is nil, channel has been closed")
}
defer func() {
if r := recover(); nil != r {
ret = errors.New("channel has been closed")
}
}()
return c.Conn.ReadJSON(v)
}
// Close closed the channel. // Close closed the channel.
func (c *WSChannel) Close() { func (c *WSChannel) Close() {
c.Conn.Close() if nil != c.Conn {
c.Conn.Close()
}
} }
// Refresh refreshes the channel by updating its use time. // Refresh refreshes the channel by updating its use time.

136
util/zip.go Normal file
View File

@ -0,0 +1,136 @@
// Copyright (c) 2014, B3log
//
// 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 util
import (
"archive/zip"
"io"
"io/ioutil"
"os"
"path/filepath"
)
type myzip struct{}
// Zip utilities.
var Zip = myzip{}
type ZipFile struct {
zipFile *os.File
writer *zip.Writer
}
func (*myzip) Create(filename string) (*ZipFile, error) {
file, err := os.Create(filename)
if err != nil {
return nil, err
}
return &ZipFile{zipFile: file, writer: zip.NewWriter(file)}, nil
}
func (z *ZipFile) Close() error {
err := z.writer.Close()
if nil != err {
return err
}
return z.zipFile.Close() // close the underlying writer
}
func (z *ZipFile) AddEntryN(path string, names ...string) error {
for _, name := range names {
zipPath := filepath.Join(path, name)
err := z.AddEntry(zipPath, name)
if err != nil {
return err
}
}
return nil
}
func (z *ZipFile) AddEntry(path, name string) error {
fi, err := os.Stat(name)
if err != nil {
return err
}
fh, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
fh.Name = filepath.ToSlash(filepath.Clean(path))
fh.Method = zip.Deflate // data compression algorithm
if fi.IsDir() {
fh.Name = fh.Name + "/" // be care the ending separator
}
entry, err := z.writer.CreateHeader(fh)
if err != nil {
return err
}
if fi.IsDir() {
return nil
}
file, err := os.Open(name)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(entry, file)
return err
}
func (z *ZipFile) AddDirectoryN(path string, names ...string) error {
for _, name := range names {
err := z.AddDirectory(path, name)
if err != nil {
return err
}
}
return nil
}
func (z *ZipFile) AddDirectory(path, dirName string) error {
files, err := ioutil.ReadDir(dirName)
if err != nil {
return err
}
for _, file := range files {
localPath := filepath.Join(dirName, file.Name())
zipPath := filepath.Join(path, file.Name())
err = nil
if file.IsDir() {
z.AddEntry(path, dirName)
err = z.AddDirectory(zipPath, localPath)
} else {
err = z.AddEntry(zipPath, localPath)
}
if err != nil {
return err
}
}
return nil
}

View File

@ -31,14 +31,14 @@
<span>{{.i18n.file}}</span> <span>{{.i18n.file}}</span>
<div class="frame"> <div class="frame">
<ul> <ul>
<li class="save-all disabled" onclick="wide.saveAllFiles()"> <li class="save-all disabled" onclick="menu.saveAllFiles()">
<span>{{.i18n.save_all_files}}</span> <span>{{.i18n.save_all_files}}</span>
</li> </li>
<li class="close-all" onclick="wide.closeAllFiles()"> <li class="close-all" onclick="menu.closeAllFiles()">
<span>{{.i18n.close_all_files}}</span> <span>{{.i18n.close_all_files}}</span>
</li> </li>
<li class="hr"></li> <li class="hr"></li>
<li onclick="wide.exit()"> <li onclick="menu.exit()">
<span>{{.i18n.exit}}</span> <span>{{.i18n.exit}}</span>
</li> </li>
</ul> </ul>
@ -48,21 +48,21 @@
<span>{{.i18n.run}}</span> <span>{{.i18n.run}}</span>
<div class="frame"> <div class="frame">
<ul> <ul>
<li class="build disabled" onclick="wide.build()"> <li class="build disabled" onclick="menu.build()">
<span>{{.i18n.build}}</span> <span>{{.i18n.build}}</span>
</li> </li>
<li class="run disabled" onclick="wide.run()"> <li class="run disabled" onclick="menu.run()">
<span>{{.i18n.build_n_run}}</span> <span>{{.i18n.build_n_run}}</span>
</li> </li>
<li class="hr"></li> <li class="hr"></li>
<li class="go-test disabled" onclick="wide.test()"> <li class="go-test disabled" onclick="menu.test()">
<span>{{.i18n.test}}</span> <span>{{.i18n.test}}</span>
</li> </li>
<li class="hr"></li> <li class="hr"></li>
<li class="go-get disabled" onclick="wide.goget()"> <li class="go-get disabled" onclick="menu.goget()">
<span>{{.i18n.goget}}</span> <span>{{.i18n.goget}}</span>
</li> </li>
<li class="go-install disabled" onclick="wide.goinstall()"> <li class="go-install disabled" onclick="menu.goinstall()">
<span>{{.i18n.goinstall}}</span> <span>{{.i18n.goinstall}}</span>
</li> </li>
</ul> </ul>
@ -90,6 +90,9 @@
</ul> </ul>
</div> </div>
</li> </li>
<li onclick="menu.openPreference()">
<span>{{.i18n.perference}}</span>
</li>
<li> <li>
<span>{{.i18n.help}}</span> <span>{{.i18n.help}}</span>
<div class="frame"> <div class="frame">
@ -108,7 +111,7 @@
<li onclick="editors.openStartPage()"> <li onclick="editors.openStartPage()">
<span>{{.i18n.start_page}}</span> <span>{{.i18n.start_page}}</span>
</li> </li>
<li onclick="wide.openAbout()"> <li onclick="menu.openAbout()">
<span>{{.i18n.about}}</span> <span>{{.i18n.about}}</span>
</li> </li>
</ul> </ul>
@ -134,6 +137,10 @@
<li class="create" onclick="tree.newFile(this);">{{.i18n.create_file}}</li> <li class="create" onclick="tree.newFile(this);">{{.i18n.create_file}}</li>
<li class="create" onclick="tree.newDir(this);">{{.i18n.create_dir}}</li> <li class="create" onclick="tree.newDir(this);">{{.i18n.create_dir}}</li>
<li class="remove" onclick="tree.removeIt(this);">{{.i18n.delete}}</li> <li class="remove" onclick="tree.removeIt(this);">{{.i18n.delete}}</li>
<li class="rename" onclick="tree.rename(this);">{{.i18n.rename}}</li>
<li class="hr"></li>
<li class="refresh" onclick="tree.refresh(this);">{{.i18n.refresh}}</li>
<li class="export" onclick="tree.export(this);">{{.i18n.export}}</li>
</ul> </ul>
</div> </div>
@ -141,6 +148,9 @@
<div id="fileRMenu" class="frame"> <div id="fileRMenu" class="frame">
<ul> <ul>
<li class="remove" onclick="tree.removeIt(this);">{{.i18n.delete}}</li> <li class="remove" onclick="tree.removeIt(this);">{{.i18n.delete}}</li>
<li class="rename" onclick="tree.rename(this);">{{.i18n.rename}}</li>
<li class="hr"></li>
<li class="export" onclick="tree.export(this);">{{.i18n.export}}</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -149,7 +159,7 @@
<div class="edit-panel"> <div class="edit-panel">
<div class="toolbars fn-none"> <div class="toolbars fn-none">
<span onclick="wide.run()" class="font-ico ico-buildrun" title="{{.i18n.build_n_run}}"></span> <span onclick="menu.run()" class="font-ico ico-buildrun" title="{{.i18n.build_n_run}}"></span>
<span onclick="wide.saveFile()" title="{{.i18n.save}}" class="font-ico ico-save"></span> <span onclick="wide.saveFile()" title="{{.i18n.save}}" class="font-ico ico-save"></span>
<span onclick="wide.fmt(editors.getCurrentPath(), wide.curEditor)" class="ico-format font-ico" title="{{.i18n.format}}"></span> <span onclick="wide.fmt(editors.getCurrentPath(), wide.curEditor)" class="ico-format font-ico" title="{{.i18n.format}}"></span>
<span class="font-ico ico-max" onclick="windows.maxEditor()" title="{{.i18n.max_editor}}"></span> <span class="font-ico ico-max" onclick="windows.maxEditor()" title="{{.i18n.max_editor}}"></span>
@ -159,7 +169,7 @@
<li onclick="editors.close()" title="{{.i18n.close}}"> <li onclick="editors.close()" title="{{.i18n.close}}">
<span>{{.i18n.close}}</span> <span>{{.i18n.close}}</span>
</li> </li>
<li onclick="wide.closeAllFiles()" title="{{.i18n.close_all_files}}"> <li onclick="menu.closeAllFiles()" title="{{.i18n.close_all_files}}">
<span>{{.i18n.close_all_files}}</span> <span>{{.i18n.close_all_files}}</span>
</li> </li>
<li onclick="editors.closeOther()" title="{{.i18n.close_other}}"> <li onclick="editors.closeOther()" title="{{.i18n.close_other}}">
@ -230,15 +240,23 @@
</div> </div>
<div id="dialogAlert" class="fn-none"></div> <div id="dialogAlert" class="fn-none"></div>
<div id="dialogAbout" class="fn-none"></div> <div id="dialogAbout" class="fn-none"></div>
<div id="dialogPreference" class="fn-none"></div>
<div id="dialogNewFilePrompt" class="dialog-prompt fn-none"> <div id="dialogNewFilePrompt" class="dialog-prompt fn-none">
<input/> <input/>
</div> </div>
<div id="dialogRenamePrompt" class="dialog-prompt fn-none">
<input/>
</div>
<div id="dialogNewDirPrompt" class="dialog-prompt fn-none"> <div id="dialogNewDirPrompt" class="dialog-prompt fn-none">
<input/> <input/>
</div> </div>
<div id="dialogGoLinePrompt" class="dialog-prompt fn-none"> <div id="dialogGoLinePrompt" class="dialog-prompt fn-none">
<input/> <input/>
</div> </div>
<div id="dialogGoFilePrompt" class="dialog-prompt fn-none">
<input/>
<ul class="list" tabindex="-1"></ul>
</div>
<div id="dialogSearchForm" class="dialog-form fn-none"> <div id="dialogSearchForm" class="dialog-form fn-none">
<input placeholder="{{.i18n.keyword}}" /> <input placeholder="{{.i18n.keyword}}" />
<input placeholder="{{.i18n.file_format}}" /> <input placeholder="{{.i18n.file_format}}" />
@ -259,8 +277,10 @@
"restore_editor": "{{.i18n.restore_editor}}", "restore_editor": "{{.i18n.restore_editor}}",
"max_editor": "{{.i18n.max_editor}}", "max_editor": "{{.i18n.max_editor}}",
"delete": "{{.i18n.delete}}", "delete": "{{.i18n.delete}}",
"rename": "{{.i18n.rename}}",
"cancel": "{{.i18n.cancel}}", "cancel": "{{.i18n.cancel}}",
"goto_line": "{{.i18n.goto_line}}", "goto_line": "{{.i18n.goto_line}}",
"goto_file": "{{.i18n.goto_file}}",
"go": "{{.i18n.go}}", "go": "{{.i18n.go}}",
"create": "{{.i18n.create}}", "create": "{{.i18n.create}}",
"create_file": "{{.i18n.create_file}}", "create_file": "{{.i18n.create_file}}",
@ -279,7 +299,9 @@
"new_version_available": "{{.i18n.new_version_available}}", "new_version_available": "{{.i18n.new_version_available}}",
"colon": "{{.i18n.colon}}", "colon": "{{.i18n.colon}}",
"file": "{{.i18n.file}}", "file": "{{.i18n.file}}",
"uptodate": "{{.i18n.uptodate}}" "uptodate": "{{.i18n.uptodate}}",
"perference": "{{.i18n.perference}}",
"apply": "{{.i18n.apply}}"
}, },
"channel": { "channel": {
"editor": '{{.conf.EditorChannel}}', "editor": '{{.conf.EditorChannel}}',

View File

@ -15,8 +15,10 @@
<li>Alt-Shift-F{{.i18n.colon}}{{.i18n.format}}</li> <li>Alt-Shift-F{{.i18n.colon}}{{.i18n.format}}</li>
<li>Ctrl-L{{.i18n.colon}}{{.i18n.goto_line}}</li> <li>Ctrl-L{{.i18n.colon}}{{.i18n.goto_line}}</li>
<li>Ctrl-E{{.i18n.colon}}{{.i18n.delete_line}}</li> <li>Ctrl-E{{.i18n.colon}}{{.i18n.delete_line}}</li>
<li>Shift-Ctrl-Up{{.i18n.colon}}{{.i18n.copy_line_up}}</li> <li>Shift-Ctrl-Up{{.i18n.colon}}{{.i18n.copy_lines_up}}</li>
<li>Shift-Ctrl-Down{{.i18n.colon}}{{.i18n.copy_line_down}}</li> <li>Shift-Ctrl-Down{{.i18n.colon}}{{.i18n.copy_lines_down}}</li>
<li>Shift-Alt-Up{{.i18n.colon}}{{.i18n.move_lines_up}}</li>
<li>Shift-Alt-Down{{.i18n.colon}}{{.i18n.move_lines_down}}</li>
<li>Ctrl-S{{.i18n.colon}}{{.i18n.save_editor_file}}</li> <li>Ctrl-S{{.i18n.colon}}{{.i18n.save_editor_file}}</li>
<li>Shift-Ctrl-S{{.i18n.colon}}{{.i18n.save_all_editors_files}}</li> <li>Shift-Ctrl-S{{.i18n.colon}}{{.i18n.save_all_editors_files}}</li>
<li>Ctrl-Q{{.i18n.colon}}{{.i18n.close_editor}}</li> <li>Ctrl-Q{{.i18n.colon}}{{.i18n.close_editor}}</li>
@ -27,6 +29,7 @@
</ul> </ul>
<h2>{{.i18n.search}}</h2> <h2>{{.i18n.search}}</h2>
<ul> <ul>
<li>Shift-Alt-O{{.i18n.colon}}{{.i18n.goto_file}}</li>
<li>Ctrl-F{{.i18n.colon}}{{.i18n.search}}/{{.i18n.find}}</li> <li>Ctrl-F{{.i18n.colon}}{{.i18n.search}}/{{.i18n.find}}</li>
<li>Ctrl-G{{.i18n.colon}}{{.i18n.find_next}}</li> <li>Ctrl-G{{.i18n.colon}}{{.i18n.find_next}}</li>
<li>Shift-Ctrl-G{{.i18n.colon}}{{.i18n.find_previous}}</li> <li>Shift-Ctrl-G{{.i18n.colon}}{{.i18n.find_previous}}</li>
@ -44,7 +47,9 @@
</ul> </ul>
<h2>{{.i18n.run}}</h2> <h2>{{.i18n.run}}</h2>
<ul> <ul>
<li>F5{{.i18n.colon}}{{.i18n.build}}</li>
<li>F6{{.i18n.colon}}{{.i18n.build_n_run}}</li> <li>F6{{.i18n.colon}}{{.i18n.build_n_run}}</li>
<li>Ctrl-C{{.i18n.colon}}{{.i18n.clearOutput}}</li>
</ul> </ul>
</body> </body>
</html> </html>

View File

@ -17,7 +17,7 @@
<li><a href="https://github.com/b3log/wide" target="_blank">GitHub</a></li> <li><a href="https://github.com/b3log/wide" target="_blank">GitHub</a></li>
<li><a href="https://www.gitbook.io/book/88250/wide-user-guide" target="_blank">{{.i18n.help}}</a></li> <li><a href="https://www.gitbook.io/book/88250/wide-user-guide" target="_blank">{{.i18n.help}}</a></li>
<li><a href="https://github.com/b3log/wide/issues/new" target="_blank">{{.i18n.report_issues}}</a></li> <li><a href="https://github.com/b3log/wide/issues/new" target="_blank">{{.i18n.report_issues}}</a></li>
<li><a href="/signup">{{.i18n.sign_up}}</a></li> <li><a class="button" href="/signup">{{.i18n.sign_up}}</a></li>
</ul> </ul>
</div> </div>
</div> </div>

76
views/preference.html Normal file
View File

@ -0,0 +1,76 @@
<div class="preference">
<div class="tabs">
<div class="current" data-index="appearence">
<span title="{{.i18n.appearence}}">{{.i18n.appearence}}</span>
</div>
<div data-index="editor">
<span title="{{.i18n.editor}}">{{.i18n.editor}}</span>
</div>
<div data-index="gotool">
<span title="{{.i18n.gotool}}">{{.i18n.gotool}}</span>
</div>
<div data-index="user">
<span title="{{.i18n.user}}">{{.i18n.user}}</span>
</div>
</div>
<div class="tabs-panel">
<div data-index="appearence">
<label>
{{.i18n.font}}{{.i18n.colon}}
<input data-value="{{.user.FontFamily}}" value="{{.user.FontFamily}}" name="fontFamily"/>
</label>
<label>
{{.i18n.font_size}}{{.i18n.colon}}
<input data-value="{{.user.FontSize}}" value="{{.user.FontSize}}" name="fontSize"/>
</label>
</div>
<div class="fn-none" data-index="editor">
<label>
{{.i18n.font}}{{.i18n.colon}}
<input data-value="{{.user.Editor.FontFamily}}" value="{{.user.Editor.FontFamily}}" name="editorFontFamily"/>
</label>
<label>
{{.i18n.font_size}}{{.i18n.colon}}
<input data-value="{{.user.Editor.FontSize}}" value="{{.user.Editor.FontSize}}" name="editorFontSize"/>
</label>
<label>
{{.i18n.line_height}}{{.i18n.colon}}
<input data-value="{{.user.Editor.LineHeight}}" value="{{.user.Editor.LineHeight}}" name="editorLineHeight"/>
</label>
</div>
<div class="fn-none" data-index="gotool">
<label>
{{.i18n.go_format}}{{.i18n.colon}}
<br/>
<select class="select" id="goFmtSelect">
{{range $index, $gofmt := .gofmts }}
<option name="{{$gofmt}}" value="{{$gofmt}}" {{if eq $.user.GoFormat $gofmt}}selected="selected"{{end}}>{{$gofmt}}</option>
{{end}}
</select>
<br/>
</label>
<input data-value="{{.user.GoFormat}}" value="{{.user.GoFormat}}" name="goFmt" hidden="hidden" />
</div>
<div class="fn-none" data-index="user">
<label>
{{.i18n.locale}}{{.i18n.colon}}
<br/>
<select class="select" id="localeSelect">
{{range $index, $localeName := .locales }}
<option name="{{$localeName}}" value="{{$localeName}}" {{if eq $.user.Locale $localeName}}selected="selected"{{end}}>{{$localeName}}</option>
{{end}}
</select>
<br/>
</label>
<input data-value="{{.user.Locale}}" value="{{.user.Locale}}" name="locale" hidden="hidden" />
<label>
{{.i18n.workspace}}{{.i18n.colon}}
<input data-value="{{.user.Workspace}}" value="{{.user.Workspace}}" name="workspace"/>
</label>
<label>
{{.i18n.password}}{{.i18n.colon}}
<input data-value="{{.user.Password}}" value="{{.user.Password}}" name="password" type="password"/>
</label>
</div>
</div>
</div>

View File

@ -36,6 +36,7 @@
<input id="username" placeholder="Username"/><br/> <input id="username" placeholder="Username"/><br/>
<input id="password" type="password" placeholder="Password"/><br/> <input id="password" type="password" placeholder="Password"/><br/>
<input id="confirmPassword" type="password" placeholder="Confirm your password"/> <input id="confirmPassword" type="password" placeholder="Confirm your password"/>
<button id="signUpBtn" class="button">{{.i18n.sign_up}}</button>
</div> </div>
</div> </div>
</div> </div>
@ -134,6 +135,10 @@
$("#msg").hide(); $("#msg").hide();
} }
}); });
$("#signUpBtn").click(function () {
signUp();
});
})(); })();
</script> </script>
</body> </body>