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
/main
/packer
/data/workspace/bin/
/data/workspace/pkg/
/data/workspace/src/
/static/user/admin/style.css
/data/user_workspaces/*/bin/
/data/user_workspaces/*/pkg/
/data/user_workspaces/*/src/**/*.exe
/header
/header.exe

View File

@ -1,11 +1,10 @@
author: DL88250@gmail.com
description: A Web IDE for Teams using Golang.
description: A Web-based IDE for Teams using Golang.
filesets:
depth: 10
includes:
- conf
- data
- doc
- i18n
- 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,4 +1,6 @@
# 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
@ -98,17 +100,22 @@ We have provided OS-specific executable binary as follows:
* windows-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
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
* `go get -u`
* `go get -u github.com/88250/ide_stub`
* `go get -u github.com/nsf/gocode`
* `go get`
* `go get github.com/88250/ide_stub`
* `go get github.com/nsf/gocode`
3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -u wide -p {ip}:{port}:7070 88250/wide:latest ./wide -docker=true -channel=ws://{ip}:{port}`
## Known Issues
* [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)
* [gocode](https://github.com/nsf/gocode)
* [Gorilla](https://github.com/gorilla)
* [GoBuild](http://gobuild.io)
* [Docker](https://docker.com)
----
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />
</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.
package conf
@ -7,7 +21,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"text/template"
"time"
@ -51,23 +65,24 @@ type User struct {
type Editor struct {
FontFamily string
FontSize string
LineHeight string
}
// Configuration.
type conf struct {
IP string // server ip, ${ip}
Server string // server host and port ({IP}:7070)
StaticServer string // static resources server scheme, host and port (http://{IP}:7070)
EditorChannel string // editor channel (ws://{IP}:7070)
OutputChannel string // output channel (ws://{IP}:7070)
ShellChannel string // shell channel(ws://{IP}:7070)
SessionChannel string // wide session channel (ws://{IP}:7070)
Port string // server port
Server string // server host and port ({IP}:{Port})
StaticServer string // static resources server scheme, host and port (http://{IP}:{Port})
EditorChannel string // editor channel (ws://{IP}:{Port})
OutputChannel string // output channel (ws://{IP}:{Port})
ShellChannel string // shell channel(ws://{IP}:{Port})
SessionChannel string // wide session channel (ws://{IP}:{Port})
HTTPSessionMaxAge int // HTTP session max age (in seciond)
StaticResourceVersion string // version of static resources
MaxProcs int // Go max procs
RuntimeMode string // runtime mode (dev/prod)
WD string // current working direcitory, ${pwd}
Workspace string // path of master workspace
Locale string // default locale
Users []*User // configurations of users
}
@ -95,28 +110,31 @@ func FixedTimeCheckEnv() {
}
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") {
glog.Fatal("Not found $GOPATH, please configure it before running Wide")
os.Exit(-1)
}
if "" == os.Getenv("GOROOT") {
glog.Fatal("Not found $GOROOT, please configure it before running Wide")
os.Exit(-1)
}
gocode := Wide.GetExecutableInGOBIN("gocode")
cmd := exec.Command(gocode, "close")
_, err := cmd.Output()
gocode := util.Go.GetExecutableInGOBIN("gocode")
cmd = exec.Command(gocode, "close")
_, err = cmd.Output()
if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
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")
_, err = cmd.Output()
if nil != err {
@ -141,25 +159,14 @@ func FixedTimeSave() {
func (c *conf) GetUserWorkspace(username string) string {
for _, user := range c.Users {
if user.Name == username {
ret := strings.Replace(user.Workspace, "{WD}", c.WD, 1)
return filepath.FromSlash(ret)
return user.GetWorkspace()
}
}
return ""
}
// GetWorkspace gets the master workspace path.
//
// 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.
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
func (c *conf) GetGoFmt(username string) string {
for _, user := range c.Users {
if user.Name == username {
@ -167,7 +174,7 @@ func (c *conf) GetGoFmt(username string) string {
case "gofmt":
return "gofmt"
case "goimports":
return c.GetExecutableInGOBIN("goimports")
return util.Go.GetExecutableInGOBIN("goimports")
default:
glog.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
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:
// 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 {
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.
@ -198,42 +209,6 @@ func (*conf) GetUser(username string) *User {
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.
func Save() bool {
// just the Users field are volatile
@ -258,12 +233,12 @@ func Save() bool {
}
// Load loads the configurations from wide.json.
func Load() {
bytes, _ := ioutil.ReadFile("conf/wide.json")
func Load(confPath, confIP, confPort, confServer, confChannel string, confDocker bool) {
bytes, _ := ioutil.ReadFile(confPath)
err := json.Unmarshal(bytes, &Wide)
if err != nil {
glog.Error(err)
glog.Error("Parses wide.json error: ", err)
os.Exit(-1)
}
@ -271,6 +246,11 @@ func Load() {
// keep the raw content
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()
if err != nil {
glog.Error(err)
@ -280,17 +260,54 @@ func Load() {
glog.V(5).Infof("${ip} [%s]", ip)
Wide.WD = util.OS.Pwd()
glog.V(5).Infof("${pwd} [%s]", Wide.WD)
if confDocker {
// TODO: may be we need to do something here
}
if "" != confIP {
ip = confIP
}
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)
if "" != confServer {
Wide.Server = confServer
}
// Static Server
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)
if "" != confChannel {
Wide.EditorChannel = confChannel
}
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)
if "" != confChannel {
Wide.ShellChannel = confChannel
}
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))
@ -331,7 +348,7 @@ func UpdateCustomizedConf(username string) {
wd := util.OS.Pwd()
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)
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.
func initWorkspaceDirs() {
paths := filepath.SplitList(Wide.GetWorkspace())
paths := []string{}
for _, user := range Wide.Users {
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.
func createDir(path string) {
if !isExist(path) {
if !util.File.IsExist(path) {
if err := os.MkdirAll(path, 0775); nil != err {
glog.Error(err)
@ -393,10 +410,3 @@ func createDir(path string) {
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}",
"Server": "{IP}:7070",
"StaticServer": "http://{IP}:7070",
"EditorChannel": "ws://{IP}:7070",
"OutputChannel": "ws://{IP}:7070",
"ShellChannel": "ws://{IP}:7070",
"SessionChannel": "ws://{IP}:7070",
"Port": "7070",
"Server": "{IP}:{Port}",
"StaticServer": "",
"EditorChannel": "ws://{IP}:{Port}",
"OutputChannel": "ws://{IP}:{Port}",
"ShellChannel": "ws://{IP}:{Port}",
"SessionChannel": "ws://{IP}:{Port}",
"HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "201411062300",
"StaticResourceVersion": "${time}",
"MaxProcs": 4,
"RuntimeMode": "dev",
"WD": "${pwd}",
"Workspace": "{WD}/data/workspace",
"Locale": "en_US",
"Users": [
{
"Name": "admin",
"Password": "admin",
"Workspace": "{WD}/data/user_workspaces/admin",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "inherit"
"FontSize": "13px",
"LineHeight": "17px"
},
"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\\time"
],
"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"
"FileTree": [],
"Files": [],
"CurrentFile": ""
}
}
]

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.
package editor
@ -24,30 +38,30 @@ import (
// WSHandler handles request of creating editor channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := httpSession.Values["id"].(string)
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
session.EditorWS[sid] = &editorChan
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))
args := map[string]interface{}{}
for {
if err := session.EditorWS[sid].Conn.ReadJSON(&args); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Editor WS ERROR: " + err.Error())
if err := session.EditorWS[sid].ReadJSON(&args); err != nil {
return
}
@ -59,7 +73,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset: %d", offset)
gocode := conf.Wide.GetExecutableInGOBIN("gocode")
gocode := util.Go.GetExecutableInGOBIN("gocode")
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
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"}
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())
return
}
@ -94,6 +108,11 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
}
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(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)
// 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}
exec.Command(gocode, argv...).Run()
@ -205,7 +224,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// 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", "."}
cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir
@ -236,6 +255,11 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
var args map[string]interface{}
@ -276,7 +300,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// 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", "."}
cmd := exec.Command(ide_stub, argv...)
cmd.Dir = curDir
@ -315,6 +339,11 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
var args map[string]interface{}
@ -355,7 +384,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch)
// 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", "."}
cmd := exec.Command(ide_stub, argv...)
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
import (
@ -5,8 +19,6 @@ import (
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
@ -24,6 +36,11 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
var args map[string]interface{}
@ -37,8 +54,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string)
apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg"
if strings.HasPrefix(filePath, apiPath) { // if it is Go API source code
if util.Go.IsAPI(filePath) {
// ignore it
return
}
@ -70,7 +86,9 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
bytes, _ := cmd.Output()
output := string(bytes)
if "" == output {
data["succ"] = false
// format error, returns the original content
data["succ"] = true
data["code"] = code
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.
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.
package file
@ -7,7 +21,6 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
@ -21,7 +34,6 @@ import (
// File node, used to construct the file tree.
type FileNode struct {
Name string `json:"name"`
Title string `json:"title"`
Path string `json:"path"`
IconSkin string `json:"iconSkin"` // Value should be end with a space
Type string `json:"type"` // "f": file, "d": directory
@ -39,28 +51,49 @@ type Snippet struct {
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.
//
// The Go API source code package ($GOROOT/src/pkg) also as a child node,
// so that users can easily view the Go API source code.
// The Go API source code package also as a child node,
// so that users can easily view the Go API source code in file tree.
func GetFiles(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
userWorkspace := conf.Wide.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
root := FileNode{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}}
if nil == apiNode { // lazy init
initAPINode()
}
// workspace node process
for _, workspace := range workspaces {
workspacePath := workspace + conf.PathSeparator + "src"
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{}}
walk(workspacePath, &workspaceNode, true, true)
@ -69,28 +102,31 @@ func GetFiles(w http.ResponseWriter, r *http.Request) {
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
root.FileNodes = append(root.FileNodes, &apiNode)
root.FileNodes = append(root.FileNodes, apiNode)
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.
func GetFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
@ -106,25 +142,40 @@ func GetFile(w http.ResponseWriter, r *http.Request) {
}
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)
extension := filepath.Ext(path)
if isImg(extension) {
if util.File.IsImg(extension) {
// image file will be open in a browser tab
data["mode"] = "img"
path2 := strings.Replace(path, "\\", "/", -1)
idx := strings.Index(path2, "/data/user_workspaces")
data["path"] = path2[idx:]
user := GetUsre(path)
if nil == user {
glog.Warningf("The path [%s] has no owner")
data["path"] = ""
return
}
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1)
return
}
content := string(buf)
if isBinary(content) {
if util.File.IsBinary(content) {
data["succ"] = false
data["msg"] = "Can't open a binary file :("
} else {
@ -262,10 +313,70 @@ func RenameFile(w http.ResponseWriter, r *http.Request) {
data["succ"] = false
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.
func SearchText(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
@ -298,7 +409,7 @@ func walk(path string, node *FileNode, creatable, removable bool) {
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)
if nil == fio {
@ -340,7 +451,14 @@ func listFiles(dirname string) []string {
// sort: directories in front of files
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() {
// exclude the .git direcitory
@ -361,7 +479,7 @@ func listFiles(dirname string) []string {
//
// Refers to the zTree document for CSS class names.
func getIconSkin(filenameExtension string) string {
if isImg(filenameExtension) {
if util.File.IsImg(filenameExtension) {
return "ico-ztree-img "
}
@ -426,7 +544,7 @@ func getEditorMode(filenameExtension string) string {
func createFile(path, fileType string) bool {
switch fileType {
case "f":
file, err := os.OpenFile(path, os.O_CREATE, 0664)
file, err := os.OpenFile(path, os.O_CREATE, 0775)
if nil != err {
glog.Error(err)
@ -465,7 +583,7 @@ func removeFile(path string) bool {
return false
}
glog.Infof("Removed [%s]", path)
glog.V(5).Infof("Removed [%s]", path)
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.
func renameFile(oldPath, newPath string) bool {
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
}
glog.Infof("Renamed [%s] to [%s]", oldPath, newPath)
glog.V(5).Infof("Renamed [%s] to [%s]", oldPath, newPath)
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 {
if !strings.HasSuffix(dir, conf.PathSeparator) {
dir += conf.PathSeparator
@ -528,7 +698,7 @@ func searchInFile(path string, text string) []*Snippet {
}
content := string(bytes)
if isBinary(content) {
if util.File.IsBinary(content) {
return ret
}
@ -547,25 +717,13 @@ func searchInFile(path string, text string) []*Snippet {
return ret
}
// isBinary determines whether the specified content is a binary file content.
func isBinary(content string) bool {
for _, b := range content {
if 0 == b {
return true
// GetUsre gets the user the specified path belongs to. Returns nil if not found.
func GetUsre(path string) *conf.User {
for _, user := range conf.Wide.Users {
if strings.HasPrefix(path, user.GetWorkspace()) {
return user
}
}
return false
}
// 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
}
return nil
}

View File

@ -22,6 +22,7 @@
"create": "Create",
"create_dir": "Create Dir",
"delete": "Delete",
"rename": "Rename",
"save": "Save",
"exit": "Exit",
"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_4": "Server Internal Error",
"goto_line": "Goto Line",
"goto_file": "Goto File",
"go": "Go",
"tip": "Tip",
"confirm": "Confirm",
@ -73,8 +75,10 @@
"show_expr_info": "Show Expression Info",
"find_usages": "Find Usages",
"delete_line": "Delete Line",
"copy_line_up": "Copy Line Up",
"copy_line_down": "Copy Line Down",
"copy_lines_up": "Copy Lines Up",
"copy_lines_down": "Copy Lines Down",
"move_lines_up": "Move Lines Up",
"move_lines_down": "Move Lines Down",
"save_editor_file": "Save File",
"save_all_editors_files": "Save All",
"close_editor": "Close File",
@ -118,5 +122,18 @@
"discard": "Discard",
"close": "Close",
"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_dir": "新規ディレクトリ",
"delete": "削除",
"rename": "名前の変更",
"save": "保存",
"exit": "終了",
"close_all_files": "全てのファイルを閉じる",
@ -39,6 +40,7 @@
"notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。",
"notification_4": "内部サーバーエラー",
"goto_line": "指定行にジャンプ",
"goto_file": "ファイルをオープンする",
"go": "Go",
"tip": "ヒント",
"confirm": "確認",
@ -73,8 +75,10 @@
"show_expr_info": "式の情報を表示",
"find_usages": "使用方法を検索する",
"delete_line": "行を削除",
"copy_line_up": "前行にコピー",
"copy_line_down": "次行にコピー",
"copy_lines_up": "フロントへのコピー",
"copy_lines_down": "一番下にコピー",
"move_lines_up": "前面に移動します",
"move_lines_down": "以下に移動",
"save_editor_file": "保存",
"save_all_editors_files": "全てを保存",
"close_editor": "エディタを閉じる",
@ -118,5 +122,18 @@
"discard": "あきらめる",
"close": "クローズ",
"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.
package i18n
@ -67,3 +81,14 @@ func Get(locale, key string) interface{} {
func GetAll(locale string) map[string]interface{} {
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_dir": "创建目录",
"delete": "删除",
"rename": "重命名",
"save": "保存",
"exit": "退出",
"close_all_files": "关闭所有文件",
@ -39,6 +40,7 @@
"notification_3": "没有检查到 ide_stub这将会导致 [跳转到声明]、[查找使用] 失效",
"notification_4": "服务器内部错误",
"goto_line": "跳转到行",
"goto_file": "打开文件",
"go": "跳转",
"tip": "提示",
"confirm": "确定",
@ -73,8 +75,10 @@
"show_expr_info": "查看表达式信息",
"find_usages": "查找使用",
"delete_line": "删除当前行",
"copy_line_up": "复制当前行到上一行",
"copy_line_down": "复制当前行到下一行",
"copy_lines_up": "复制到上方",
"copy_lines_down": "复制到下方",
"move_lines_up": "移动到上方",
"move_lines_down": "移动到下方",
"save_editor_file": "保存当前编辑器文件",
"save_all_editors_files": "保存所有编辑器文件",
"close_editor": "关闭当前编辑器",
@ -118,5 +122,18 @@
"discard": "放弃",
"close": "关闭",
"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_dir": "建立目錄",
"delete": "删除",
"rename": "重命名",
"save": "儲存",
"exit": "退出",
"close_all_files": "關閉所有文件",
@ -39,6 +40,7 @@
"notification_3": "没有檢查到 ide_stub這將會導致 [跳轉到聲明]、[查找使用] 失效",
"notification_4": "服務器內部錯誤",
"goto_line": "跳轉到行",
"goto_file": "打開文件",
"go": "跳到",
"tip": "提示",
"confirm": "確定",
@ -73,8 +75,10 @@
"show_expr_info": "查看表達式信息",
"find_usages": "尋找使用",
"delete_line": "删除當前行",
"copy_line_up": "插入當前行到上一行",
"copy_line_down": "插入當前行到下一行",
"copy_lines_up": "複製到上方",
"copy_lines_down": "複製到下方",
"move_lines_up": "移動到上方",
"move_lines_down": "移動到下方",
"save_editor_file": "保存當前編輯器文件",
"save_all_editors_files": "保存所有編輯器文件",
"close_editor": "關閉當前編輯器",
@ -118,5 +122,18 @@
"discard": "放棄",
"close": "關閉",
"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
import (
@ -6,8 +20,10 @@ import (
"math/rand"
"mime"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/b3log/wide/conf"
@ -25,26 +41,46 @@ import (
// The only one init function in Wide.
func init() {
// TODO: args
flag.Set("logtostderr", "true")
confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
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.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()
event.Load()
conf.Load()
conf.Load(*confPath, *confIP, *confPort, *confServer, *confChannel, *confDocker)
conf.FixedTimeCheckEnv()
conf.FixedTimeSave()
session.FixedTimeRelease()
if *confStat {
session.FixedTimeReport()
}
}
// indexHandler handles request of Wide index.
func indexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
@ -72,10 +108,9 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
locale := user.Locale
wideSessions := session.WideSessions.GetByUsername(username)
userConf := conf.Wide.GetUser(username)
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}
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.
func startHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
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.
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
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.
func aboutHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
@ -217,7 +249,10 @@ func main() {
serveSingle("/favicon.ico", "./static/favicon.ico")
// 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
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
@ -234,12 +269,18 @@ func main() {
// file tree
http.HandleFunc("/files", handlerWrapper(file.GetFiles))
http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectory))
http.HandleFunc("/file", handlerWrapper(file.GetFile))
http.HandleFunc("/file/save", handlerWrapper(file.SaveFile))
http.HandleFunc("/file/new", handlerWrapper(file.NewFile))
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFile))
http.HandleFunc("/file/rename", handlerWrapper(file.RenameFile))
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
http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler))
@ -260,8 +301,9 @@ func main() {
http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
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)
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.
package notification
import (
"net/http"
"strconv"
"time"
"strconv"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/event"
"github.com/b3log/wide/i18n"
@ -65,7 +79,7 @@ func event2Notification(e *event.Event) {
return
}
wsChannel.Conn.WriteJSON(notification)
wsChannel.WriteJSON(notification)
wsChannel.Refresh()
}
@ -83,6 +97,12 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
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
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{}{}
for {
if err := wsChan.Conn.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
glog.Error("Notification WS ERROR: " + err.Error())
if err := wsChan.ReadJSON(&input); err != nil {
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.
package output
@ -44,10 +58,13 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
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"}
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))
}
@ -106,7 +123,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -127,13 +144,13 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover()
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
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -155,12 +172,12 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// remove the exited process from user process set
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 {
channelRet["cmd"] = "run-done"
channelRet["output"] = "<pre>" + buf + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
channelRet["output"] = buf
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
@ -173,8 +190,8 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
} else {
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = "<pre>" + buf + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
channelRet["output"] = buf
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
@ -195,14 +212,14 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// remove the exited process from user process set
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] {
wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run-done"
channelRet["output"] = "<pre>" + buf + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
channelRet["output"] = buf
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
@ -217,8 +234,8 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid]
channelRet["cmd"] = "run"
channelRet["output"] = "<pre>" + buf + "</pre>"
err := wsChannel.Conn.WriteJSON(&channelRet)
channelRet["output"] = buf
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
break
@ -237,6 +254,11 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
@ -319,7 +341,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -341,7 +363,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover()
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
buf, _ := ioutil.ReadAll(reader)
@ -419,10 +441,10 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
}
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]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
@ -439,6 +461,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
@ -491,7 +518,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -512,7 +539,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
go func(runningId int) {
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["cmd"] = "go test"
@ -524,11 +551,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait()
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)
} 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)
}
@ -536,7 +563,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
@ -552,6 +579,11 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
@ -606,7 +638,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -628,7 +660,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover()
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
buf, _ := ioutil.ReadAll(reader)
@ -692,10 +724,10 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
}
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]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
@ -712,6 +744,11 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
defer util.RetJSON(w, r, data)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.Wide.GetUser(username).Locale
@ -764,7 +801,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
return
@ -786,7 +823,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover()
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["cmd"] = "go get"
@ -795,11 +832,11 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) {
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done (with error)", sid, runningId)
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)
} 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"
@ -808,7 +845,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.Conn.WriteJSON(&channelRet)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
glog.Error(err)
}
@ -846,10 +883,9 @@ func StopHandler(w http.ResponseWriter, r *http.Request) {
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.Wide.GetUserWorkspace(username)
masterWorkspace := conf.Wide.GetWorkspace()
cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace+conf.PathListSeparator+masterWorkspace,
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH,
"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
import (
@ -33,7 +47,7 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
// bind process with wide session
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.
@ -54,7 +68,7 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
// bind process with wide session
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
}
@ -83,7 +97,7 @@ func (procs *procs) kill(wSession *session.WideSession, pid int) {
// bind process with wide session
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

2
pkg.sh
View File

@ -9,7 +9,7 @@
ver=$1
target=$2
list="conf data doc i18n static views README.md LICENSE"
list="conf doc i18n static views README.md LICENSE"
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.
//
// Wide server side needs maintain two kinds of sessions:
@ -9,9 +23,11 @@
package session
import (
"bytes"
"encoding/json"
"net/http"
"os"
"strconv"
"sync"
"time"
@ -79,7 +95,7 @@ func FixedTimeRelease() {
for _, s := range WideSessions {
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)
}
@ -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.
//
// 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.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)
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"}
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))
input := map[string]interface{}{}
for {
if err := wsChan.Conn.ReadJSON(&input); err != nil {
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid)
if err := wsChan.ReadJSON(&input); err != nil {
glog.V(5).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]",
sid, wSession.Username)
WideSessions.Remove(sid)
@ -132,8 +196,9 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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())
return
}
@ -253,9 +318,9 @@ func (sessions *Sessions) Remove(sid string) {
// kill processes
for _, p := range s.Processes {
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 {
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)
}
glog.V(3).Infof("Removed a session [%s]", s.Id)
cnt := 0 // count wide sessions associated with HTTP session
for _, s := range *sessions {
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
}

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
import (
"encoding/json"
"math/rand"
"net/http"
"os"
"path/filepath"
"runtime"
"strconv"
"text/template"
"sync"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
@ -20,6 +37,85 @@ const (
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.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
@ -44,7 +140,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
// non-GET request as login request
succ := false
succ := true
data := map[string]interface{}{"succ": &succ}
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 {
glog.Error(err)
succ = true
succ = false
return
}
succ = false
for _, user := range conf.Wide.Users {
if user.Name == args.Username && user.Password == args.Password {
succ = true
break
}
}
@ -142,9 +241,16 @@ 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 {
// XXX: validate
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Wide.Users {
if user.Name == username {
return UserExists
@ -155,19 +261,51 @@ func addUser(username, password string) string {
dir := filepath.Dir(firstUserWorkspace)
workspace := filepath.Join(dir, username)
conf.Wide.Users = append(conf.Wide.Users,
&conf.User{Name: username, Password: password, Workspace: workspace,
Locale: conf.Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "inherit",
Editor: &conf.Editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit"}})
newUser := &conf.User{Name: username, Password: password, Workspace: workspace,
Locale: conf.Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "13px",
Editor: &conf.Editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit"}}
conf.Wide.Users = append(conf.Wide.Users, newUser)
if !conf.Save() {
return UserCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
glog.Infof("Created a user [%s]", username)
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.
package shell
@ -28,7 +42,6 @@ var ShellWS = map[string]*util.WSChannel{}
// IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
@ -68,6 +81,11 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
// WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
sid := r.URL.Query()["sid"][0]
@ -75,26 +93,22 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ShellWS[sid] = &wsChan
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
wsChan.Conn.WriteJSON(&ret)
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))
input := map[string]interface{}{}
for {
if err := wsChan.Conn.ReadJSON(&input); err != nil {
if err.Error() == "EOF" {
return
}
if err.Error() == "unexpected EOF" {
return
}
if err := wsChan.ReadJSON(&input); err != nil {
glog.Error("Shell WS ERROR: " + err.Error())
return
}
@ -121,7 +135,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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())
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 {
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 */
body {
font-size: 13px;
@ -44,3 +60,32 @@ button {
display: none;
}
/* 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
*
@ -80,4 +96,28 @@
.dialog-main input {
width: 100%;
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 {
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 */
.side {
background-color: #FFF;
@ -29,6 +45,7 @@
border-width: 0;
color: #fff;
height: 18px;
opacity: 1;
}
.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 {
margin: 0 auto;
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);
}
.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 {
background-color: #fcdede;
border: 1px solid #d2b2b2;
@ -112,6 +155,10 @@
}
.form.sign-up {
margin-top: -20px;
margin-top: -71px;
}
#signUpBtn {
margin-top: 20px;
}
/* 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 {
padding: 50px 70px;
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 */
@font-face {
font-family: 'icomoon';
@ -193,7 +209,8 @@
top: 1px;
}
.edit-panel .tabs .ico {
.edit-panel .tabs .ico,
#dialogGoFilePrompt .ico {
background-image: url("../images/ico-file.png");
float: left;
height: 16px;
@ -243,6 +260,10 @@
background: #08f;
color: white;
}
.CodeMirror div.CodeMirror-cursor {
border-left: 2px solid #333;
}
/* end editor */
/* start bottom-window-group */
@ -315,31 +336,6 @@
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 */

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 = {
tabs: undefined,
searchTab: undefined,

View File

@ -1,18 +1,19 @@
/*
* Copyright (C) 2011, Liyuan Li
*
/*
* 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.
*/
*/
(function ($) {
$.fn.extend({
dialog: {
@ -143,6 +144,9 @@
} else {
// 20(footer) + 23(header)
top = parseInt((windowH - dialogH - 43) / 2);
if (top < 0) {
top = 0;
}
left = parseInt((windowW - dialogW) / 2);
}
$dialog.css({
@ -174,6 +178,10 @@
$.dialog._close(id, settings);
}
});
$(window).resize(function () {
$(".dialog-background").height($("body").height());
});
if (typeof settings.afterInit === "function") {
settings.afterInit();
@ -210,12 +218,12 @@
if (positionX > $(window).width() - $(dialog).width()) {
positionX = $(window).width() - $(dialog).width();
}
if (positionY < 0) {
positionY = 0;
}
if (positionY > $(window).height() - $(dialog).height()) {
positionY = $(window).height() - $(dialog).height();
}
if (positionY < 0) {
positionY = 0;
}
dialog.style.left = positionX + "px";
dialog.style.top = positionY + "px";
};
@ -231,7 +239,7 @@
_document.ondragstart = null;
_document.onselectstart = null;
_document.onselect = null;
}
};
});
},
_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 = {
data: [],
tabs: {},
@ -94,6 +110,7 @@ var editors = {
id: ".edit-panel",
clickAfter: function (id) {
if (id === 'startPage') {
$(".footer .cursor").text('');
return false;
}
@ -109,7 +126,11 @@ var editors = {
}
}
var cursor = wide.curEditor.getCursor();
wide.curEditor.setCursor(cursor);
wide.curEditor.focus();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
},
removeBefore: function (id) {
if (id === 'startPage') { // 当前关闭的 tab 是起始页
@ -117,7 +138,6 @@ var editors = {
return true;
}
// 移除编辑器
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) {
if (editors.data[i].editor.doc.isClean()) {
@ -163,6 +183,7 @@ var editors = {
tree.fileTree.cancelSelectedNode();
wide.curNode = undefined;
wide.curEditor = undefined;
$(".footer .cursor").text('');
return false;
}
@ -182,6 +203,9 @@ var editors = {
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) {
var token = cm.getTokenAt(cm.getCursor());
if ("comment" === token.type) {
return CodeMirror.Pass;
}
setTimeout(function () {
if (!cm.state.completionActive) {
cm.showHint({hint: CodeMirror.hint.go, completeSingle: false});
@ -413,33 +442,11 @@ var editors = {
return;
}
var cursorLine = data.cursorLine;
var cursorCh = data.cursorCh;
var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId);
tree.fileTree.selectNode(wide.curNode);
var request = newWideRequest();
request.path = data.path;
$.ajax({
type: 'POST',
url: '/file',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogAlert").dialog("open", data.msg);
return false;
}
var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId);
tree.fileTree.selectNode(wide.curNode);
data.cursorLine = cursorLine;
data.cursorCh = cursorCh;
editors.newEditor(data);
}
});
tree.openFile(wide.curNode, CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1));
}
});
};
@ -469,7 +476,7 @@ var editors = {
};
},
appendSearch: function (data, type, key) {
var searcHTML = '<ul>';
var searcHTML = '<ul class="list">';
for (var i = 0, ii = data.length; i < ii; i++) {
var contents = data[i].contents[0],
@ -479,7 +486,7 @@ var editors = {
+ contents.substring(index + key.length);
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="'
+ data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':'
+ data[i].ch + ')</i></span></li>';
@ -544,33 +551,10 @@ var editors = {
$(".bottom-window-group .search").focus();
},
// 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab.
newEditor: function (data) {
newEditor: function (data, cursor) {
$(".toolbars").show();
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({
id: id,
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']);
var rulers = [];
rulers.push({color: "#ccc", column: 120, lineStyle: "dashed"});
var textArea = document.getElementById("editor" + id);
textArea.value = data.content;
@ -592,11 +573,12 @@ var editors = {
autoCloseBrackets: true,
matchBrackets: true,
highlightSelectionMatches: {showToken: /\w/},
rulers: rulers,
rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
styleActiveLine: true,
theme: 'wide',
indentUnit: 4,
foldGutter: true,
cursorHeight: 1,
path: data.path,
extraKeys: {
"Ctrl-\\": "autocompleteAnyWord",
@ -610,7 +592,7 @@ var editors = {
wide.saveFile();
},
"Shift-Ctrl-S": function () {
wide.saveAllFiles();
menu.saveAllFiles();
},
"Shift-Alt-F": function () {
var currentPath = editors.getCurrentPath();
@ -628,26 +610,121 @@ var editors = {
}
},
"Shift-Ctrl-Up": function (cm) {
var cursor = cm.getCursor();
var line = cursor.line;
var content = cm.getLine(line);
var content = '',
selectoion = cm.listSelections()[0];
if (0 === line) {
cm.replaceRange("", CodeMirror.Pos(0));
line++;
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
cm.replaceRange("\n" + content, CodeMirror.Pos(line - 1));
cm.setCursor(cursor);
for (var i = from.line, max = to.line; i <= max; i++) {
if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
cm.setSelection(CodeMirror.Pos(from.line, from.ch),
CodeMirror.Pos(to.line, to.ch));
},
"Shift-Ctrl-Down": function (cm) {
var cursor = cm.getCursor();
var line = cursor.line;
var content = cm.getLine(line);
var content = '',
selectoion = cm.listSelections()[0];
cm.replaceRange("\n", CodeMirror.Pos(line));
cm.replaceRange(content, CodeMirror.Pos(line + 1));
cm.setCursor(CodeMirror.Pos(line + 1, cursor.ch));
var from = selectoion.anchor,
to = selectoion.head;
if (from.line > to.line) {
from = selectoion.head;
to = selectoion.anchor;
}
for (var i = from.line, max = to.line; i <= max; i++) {
if (to.ch !== 0 || i !== max) { // 下一行选中为0时不应添加内容
content += '\n' + cm.getLine(i);
}
}
// 下一行选中为0时应添加到上一行末
var replaceToLine = to.line;
if (to.ch === 0) {
replaceToLine = to.line - 1;
}
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
var offset = replaceToLine - from.line + 1;
cm.setSelection(CodeMirror.Pos(from.line + offset, from.ch),
CodeMirror.Pos(to.line + offset, to.ch));
},
"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();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
// TODO: 关闭 tab 的时候要重置
});
editor.on('focus', function (cm) {
@ -700,16 +776,19 @@ var editors = {
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;
editors.data.push({
"editor": editor,
"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 = {
defaultKeyMap: {
// Ctrl+0 焦点切换到当前编辑器
@ -15,7 +31,7 @@ var hotkeys = {
which: 49
},
// Ctrl+4 焦点切换到输出窗口
goOutPut: {
goOutput: {
ctrlKey: true,
altKey: false,
shiftKey: false,
@ -63,7 +79,21 @@ var hotkeys = {
shiftKey: false,
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: {
ctrlKey: false,
altKey: false,
@ -71,6 +101,60 @@ var hotkeys = {
which: 117
}
},
bindList: function ($source, $list, enterFun) {
$list.data("index", 0);
$source.keydown(function (event) {
var index = $list.data("index"),
count = $list.find("li").length;
if (count === 0) {
return true;
}
if (event.which === 38) { // up
index--;
if (index < 0) {
index = count - 1;
}
}
if (event.which === 40) { // down
index++;
if (index > count - 1) {
index = 0;
}
}
var $selected = $list.find("li:eq(" + index + ")");
if (event.which === 13) { // enter
enterFun($selected);
}
$list.find("li").removeClass("selected");
$list.data("index", index);
$selected.addClass("selected");
if (index === 0) {
$list.scrollTop(0);
} else {
if ($selected[0].offsetTop + $list.scrollTop() > $list.height()) {
if (event.which === 40) {
$list.scrollTop($list.scrollTop() + $selected.height());
} else {
$list.scrollTop($selected[0].offsetTop);
}
} else {
$list.scrollTop(0);
}
}
// 阻止上下键改变光标位置
if (event.which === 38 || event.which === 40 || event.which === 13) {
return false;
}
});
},
_bindOutput: function () {
$(".bottom-window-group .output").keydown(function (event) {
event.preventDefault();
@ -241,8 +325,8 @@ var hotkeys = {
return;
}
if (event.ctrlKey === hotKeys.goOutPut.ctrlKey
&& event.which === hotKeys.goOutPut.which) { // Ctrl+4 焦点切换到输出窗口
if (event.ctrlKey === hotKeys.goOutput.ctrlKey
&& event.which === hotKeys.goOutput.which) { // Ctrl+4 焦点切换到输出窗口
bottomGroup.tabs.setCurrent("output");
windows.flowBottom();
@ -251,7 +335,7 @@ var hotkeys = {
return;
}
if (event.ctrlKey === hotKeys.goSearch.ctrlKey
&& event.which === hotKeys.goSearch.which) { // Ctrl+5 焦点切换到搜索窗口
bottomGroup.tabs.setCurrent("search");
@ -339,13 +423,27 @@ var hotkeys = {
return false;
}
if (event.which === hotKeys.buildRun.which) { // F6 构建并运行
wide.run();
if (event.which === hotKeys.build.which) { // F5 Build
menu.build();
event.preventDefault();
return;
}
});
if (event.which === hotKeys.buildRun.which) { // F6 Build & Run
menu.run();
event.preventDefault();
return;
}
if (event.ctrlKey === hotKeys.goFile.ctrlKey
&& event.altKey === hotKeys.goFile.altKey
&& event.shiftKey === hotKeys.goFile.shiftKey
&& event.which === hotKeys.goFile.which) { // Shift+Alt+O 跳转到文件
$("#dialogGoFilePrompt").dialog("open");
}
});
},
init: function () {
this._bindFileTree();

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 = {
init: function () {
this.subMenu();
this._initPreference();
this._initAbout();
// 点击子菜单后消失
$(".frame li").click(function () {
@ -8,6 +26,34 @@ var menu = {
$(".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) {
for (var i = 0, max = list.length; i < max; i++) {
$(".menu li." + list[i]).addClass("disabled");
@ -35,5 +81,321 @@ var menu = {
$(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 = {
init: function () {
$(".notification-count").click(function () {
@ -19,6 +35,12 @@ var notification = {
var data = JSON.parse(e.data),
$notification = $('.bottom-window-group .notification > table'),
notificationHTML = '';
if (data.cmd && "init-notification" === data.cmd) {
console.log('[notification onmessage]' + e.data);
return;
}
notificationHTML += '<tr><td class="severity">' + data.severity
+ '</td><td class="message">' + data.message

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
// 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-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-def {color: #00f;}
.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 = {
init: function () {
this._initWS();
@ -47,7 +63,20 @@ var session = {
// expand tree
for (var j = 0, jj = fileTree.length; j < jj; 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;
}
}
@ -62,7 +91,7 @@ var session = {
if (nodes[i].path === currentFile) {
id = nodes[i].tId;
// FIXME: 上面的展开是异步进行的,所以执行到这里的时候可能还没有展开完,导致定位不了可视区域
tree.fileTree.selectNode(nodes[i]);
wide.curNode = nodes[i];

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 = {
_shellWS: undefined,
_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) {
obj._$tabsPanel = $(obj.id + " > .tabs-panel");
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 = {
fileTree: undefined,
// 递归获取当前节点展开中的最后一个节点
@ -61,7 +77,19 @@ var tree = {
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);
if (!node || !node.parentTId) {
return false;
@ -69,7 +97,7 @@ var tree = {
if (node.parentTId === parentTId) {
return true;
} else {
return tree._isParents(node.parentTId, parentTId);
return tree.isParents(node.parentTId, parentTId);
}
}
},
@ -111,6 +139,58 @@ var tree = {
$("#fileRMenu").hide();
$("#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 () {
$("#file").click(function () {
$(this).focus();
@ -130,13 +210,18 @@ var tree = {
var setting = {
data: {
key: {
title: "title"
title: "path"
}
},
view: {
showTitle: true,
selectedMulti: false
},
async: {
enable: true,
url: "/file/refresh",
autoParam: ["path"]
},
callback: {
onDblClick: function (event, treeId, treeNode) {
if (treeNode) {
@ -154,7 +239,7 @@ var tree = {
} else {
$("#fileRMenu .remove").addClass("disabled");
}
$("#fileRMenu").show();
fileRMenu.css({
@ -164,9 +249,9 @@ var tree = {
});
} else { // 右击了目录
if (wide.curNode.removable) {
$("#dirRMenu .remove").removeClass("disabled");
$("#dirRMenu .remove, #dirRMenu .rename").removeClass("disabled");
} else {
$("#dirRMenu .remove").addClass("disabled");
$("#dirRMenu .remove, #dirRMenu .rename").addClass("disabled");
}
if (wide.curNode.creatable) {
@ -200,17 +285,30 @@ var tree = {
}
}
});
this._initSearch();
},
openFile: function (treeNode) {
openFile: function (treeNode, cursor) {
wide.curNode = treeNode;
var tempCursor = cursor;
for (var i = 0, ii = editors.data.length; i < ii; i++) {
// 该节点文件已经打开
if (editors.data[i].id === treeNode.tId) {
editors.tabs.setCurrent(treeNode.tId);
wide.curNode = treeNode;
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();
return false;
}
}
@ -238,9 +336,68 @@ var tree = {
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 = {
curNode: undefined,
curEditor: undefined,
@ -50,6 +66,10 @@ var wide = {
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogRemoveConfirm").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false;
}
@ -66,7 +86,7 @@ var wide = {
}
} else {
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();
i--;
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({
"modal": true,
"height": 52,
@ -103,43 +182,15 @@ var wide = {
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogNewFilePrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
return false;
}
$("#dialogNewFilePrompt").dialog("close");
var suffix = 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;
}
var iconSkin = wide.getClassBySuffix(name.split(".")[1]);
tree.fileTree.addNodes(wide.curNode, [{
"name": name,
@ -179,6 +230,10 @@ var wide = {
dataType: "json",
success: function (data) {
if (!data.succ) {
$("#dialogNewDirPrompt").dialog("close");
bottomGroup.tabs.setCurrent("notification");
windows.flowBottom();
$(".bottom-window-group .notification").focus();
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({
"modal": true,
"height": 52,
@ -210,7 +351,7 @@ var wide = {
"ok": function () {
var line = parseInt($("#dialogGoLinePrompt > input").val()) - 1;
$("#dialogGoLinePrompt").dialog("close");
var editor = wide.curEditor;
var cursor = editor.getCursor();
@ -223,88 +364,6 @@ var wide = {
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 () {
var mainH = $(window).height() - $(".menu").height() - $(".footer").height(),
@ -343,7 +402,7 @@ var wide = {
switch (data.cmd) {
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;
break;
@ -358,17 +417,17 @@ var wide = {
case 'start-test':
case 'start-install':
case 'start-get':
bottomGroup.fillOutput(data.output);
bottomGroup.fillOutput('<pre>' + data.output + '</pre>');
break;
case 'go test':
case 'go install':
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;
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) { // 说明编译有错误输出
for (var i = 0; i < data.lints.length; i++) {
@ -495,55 +554,9 @@ var wide = {
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 () {
if ($(".toolbars .ico-buildrun").length === 1) {
wide.run();
menu.run();
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) {
var cursor = editor.getCursor();
var scrollInfo = editor.getScrollInfo();
@ -793,8 +657,42 @@ var wide = {
wide._save(path, editor);
}
},
openAbout: function () {
$("#dialogAbout").dialog("open");
getClassBySuffix: function (suffix) {
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 = {
isMaxEditor: false,
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 {
font-family: {{.user.Editor.FontFamily}};
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.
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
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
import (
@ -8,7 +22,7 @@ import (
// Recover recovers a panic.
func Recover() {
if re := recover(); re != nil {
if re := recover(); nil != re {
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
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
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
import (
"errors"
"net/http"
"time"
@ -15,9 +30,41 @@ type WSChannel struct {
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.
func (c *WSChannel) Close() {
c.Conn.Close()
if nil != c.Conn {
c.Conn.Close()
}
}
// 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>
<div class="frame">
<ul>
<li class="save-all disabled" onclick="wide.saveAllFiles()">
<li class="save-all disabled" onclick="menu.saveAllFiles()">
<span>{{.i18n.save_all_files}}</span>
</li>
<li class="close-all" onclick="wide.closeAllFiles()">
<li class="close-all" onclick="menu.closeAllFiles()">
<span>{{.i18n.close_all_files}}</span>
</li>
<li class="hr"></li>
<li onclick="wide.exit()">
<li onclick="menu.exit()">
<span>{{.i18n.exit}}</span>
</li>
</ul>
@ -48,21 +48,21 @@
<span>{{.i18n.run}}</span>
<div class="frame">
<ul>
<li class="build disabled" onclick="wide.build()">
<li class="build disabled" onclick="menu.build()">
<span>{{.i18n.build}}</span>
</li>
<li class="run disabled" onclick="wide.run()">
<li class="run disabled" onclick="menu.run()">
<span>{{.i18n.build_n_run}}</span>
</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>
</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>
</li>
<li class="go-install disabled" onclick="wide.goinstall()">
<li class="go-install disabled" onclick="menu.goinstall()">
<span>{{.i18n.goinstall}}</span>
</li>
</ul>
@ -90,6 +90,9 @@
</ul>
</div>
</li>
<li onclick="menu.openPreference()">
<span>{{.i18n.perference}}</span>
</li>
<li>
<span>{{.i18n.help}}</span>
<div class="frame">
@ -108,7 +111,7 @@
<li onclick="editors.openStartPage()">
<span>{{.i18n.start_page}}</span>
</li>
<li onclick="wide.openAbout()">
<li onclick="menu.openAbout()">
<span>{{.i18n.about}}</span>
</li>
</ul>
@ -134,6 +137,10 @@
<li class="create" onclick="tree.newFile(this);">{{.i18n.create_file}}</li>
<li class="create" onclick="tree.newDir(this);">{{.i18n.create_dir}}</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>
</div>
@ -141,6 +148,9 @@
<div id="fileRMenu" class="frame">
<ul>
<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>
</div>
</div>
@ -149,7 +159,7 @@
<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="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.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>
@ -159,7 +169,7 @@
<li onclick="editors.close()" title="{{.i18n.close}}">
<span>{{.i18n.close}}</span>
</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>
</li>
<li onclick="editors.closeOther()" title="{{.i18n.close_other}}">
@ -230,15 +240,23 @@
</div>
<div id="dialogAlert" 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">
<input/>
</div>
<div id="dialogRenamePrompt" 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="dialogGoFilePrompt" class="dialog-prompt fn-none">
<input/>
<ul class="list" tabindex="-1"></ul>
</div>
<div id="dialogSearchForm" class="dialog-form fn-none">
<input placeholder="{{.i18n.keyword}}" />
<input placeholder="{{.i18n.file_format}}" />
@ -259,8 +277,10 @@
"restore_editor": "{{.i18n.restore_editor}}",
"max_editor": "{{.i18n.max_editor}}",
"delete": "{{.i18n.delete}}",
"rename": "{{.i18n.rename}}",
"cancel": "{{.i18n.cancel}}",
"goto_line": "{{.i18n.goto_line}}",
"goto_file": "{{.i18n.goto_file}}",
"go": "{{.i18n.go}}",
"create": "{{.i18n.create}}",
"create_file": "{{.i18n.create_file}}",
@ -279,7 +299,9 @@
"new_version_available": "{{.i18n.new_version_available}}",
"colon": "{{.i18n.colon}}",
"file": "{{.i18n.file}}",
"uptodate": "{{.i18n.uptodate}}"
"uptodate": "{{.i18n.uptodate}}",
"perference": "{{.i18n.perference}}",
"apply": "{{.i18n.apply}}"
},
"channel": {
"editor": '{{.conf.EditorChannel}}',

View File

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

View File

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