Compare commits

..

No commits in common. "master" and "1.5.2" have entirely different histories.

135 changed files with 6428 additions and 6688 deletions

3
.gitattributes vendored
View File

@ -1,3 +0,0 @@
static/js/lib/* linguist-vendored
static/js/overwrite/* linguist-vendored
*.min.js linguist-vendored

3
.gitignore vendored
View File

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

View File

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

View File

@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
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,

View File

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

23
Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM golang:cross
MAINTAINER Liang Ding <dl88250@gmail.com>
ADD . /wide/gogogo/src/github.com/b3log/wide
RUN tar zxf /wide/gogogo/src/github.com/b3log/wide/deps/golang.org.tar.gz -C /wide/gogogo/src/
RUN tar zxf /wide/gogogo/src/github.com/b3log/wide/deps/github.com.tar.gz -C /wide/gogogo/src/
RUN useradd wide && useradd runner
ENV GOROOT /usr/src/go
ENV GOPATH /wide/gogogo
RUN go build github.com/go-fsnotify/fsnotify
RUN go build github.com/gorilla/sessions
RUN go build github.com/gorilla/websocket
RUN go install github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports
WORKDIR /wide/gogogo/src/github.com/b3log/wide
RUN go build -v
EXPOSE 7070

View File

@ -192,7 +192,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
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,

182
README.md
View File

@ -1,18 +1,180 @@
* [Wide 用户指南](https://ld246.com/article/1538873544275)
* [Wide 开发指南](https://ld246.com/article/1538876422995)
# [Wide](https://github.com/b3log/wide) [![Build Status](https://img.shields.io/travis/b3log/wide.svg?style=flat)](https://travis-ci.org/b3log/wide) [![Coverage Status](https://img.shields.io/coveralls/b3log/wide.svg?style=flat)](https://coveralls.io/r/b3log/wide) [![Apache License](http://img.shields.io/badge/license-apache2-orange.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![API Documentation](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](http://godoc.org/github.com/b3log/wide) [![Download](http://img.shields.io/badge/download-~4.3K-red.svg?style=flat)](http://pan.baidu.com/s/1dD3XwOT)
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
_Have a [try](http://wide.b3log.org/signup) first, then [download](http://pan.baidu.com/s/1dD3XwOT) and setup it on your local area network, enjoy yourself!_
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
先试试我们搭建好的[在线服务](http://wide.b3log.org/signup),你可以在这里[下载](http://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请小伙伴们来玩吧!
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
> * 关于 Wide 的产品定位,请看[这里](http://hacpai.com/article/1438407961481),并欢迎参与讨论~
> * 加入[**黑客派**](http://hacpai.com/register),与其他程序员、设计师共同成长!
![Theme](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
## Introduction
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
A <b>W</b>eb-based <b>IDE</b> for Teams using Go programming language/Golang.
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
![Hello, 世界](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png)
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
## Authors
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
[Daniel](https://github.com/88250) and [Vanessa](https://github.com/Vanessa219) are the main authors of Wide, [here](https://github.com/b3log/wide/graphs/contributors) are all contributors.
Wide 的主要作者是 [Daniel](https://github.com/88250) 与 [Vanessa](https://github.com/Vanessa219),所有贡献者可以在[这里](https://github.com/b3log/wide/graphs/contributors)看到。
## Motivation
* **Team** IDE:
* _Safe and reliable_: the project source code stored on the server in real time, the developer's machine crashes without losing any source code
* _Unified environment_: server unified development environment configuration, the developer machine without any additional configuration
* _Out of the box_: 5 minutes to setup a server then open browser to develop, debug
* _Version Control_: each developer has its own source code repository, easy sync with the trunk
* **Web-based** IDE:
* Developer needs a browser only
* Cross-platform, even on mobile devices
* Easy to extend
* Easy to integrate with other systems
* For the geeks
* A try for commercial-open source: versions customized for enterprises, close to their development work flows respectively
* Currently more popular Go IDE has some defects or regrets:
* Text editor (vim/emacs/sublime/Atom, etc.): For the Go newbie is too complex
* Plug-in (goclipse, etc.): the need for the original IDE support, not professional
* LiteIDE: no modern user interface :p
* No team development experience
* There are a few of GO IDEs, and no one developed by Go itself, this is a nice try
## Features
* [X] Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc.
* [X] Autocomplete: Go/HTML etc.
* [X] Format: Go/HTML/JSON etc.
* [X] Build & Run
* [X] Multiplayer: a real team development experience
* [X] Navigation, Jump to declaration, Find usages, File search etc.
* [X] Shell: run command on the server
* [X] Web development: HTML/JS/CSS editor with [Emmet](http://emmet.io) integrated
* [X] Go tool: go get/install/fmt etc.
* [X] File Import & Export
* [X] Themes: editor and UI adjust, respectively
* [X] Cross-Compilation
* [ ] Debug
* [ ] Git integration: git command on the web
## Screenshots
* **Overview**
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
* **Goto File**
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
* **Autocomplete**
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
* **Theme**
![4](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
* **Show Expression Info**
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
* **Build Error Info**
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
* **Git Clone**
![Git Clone](https://cloud.githubusercontent.com/assets/873584/6545235/2284f230-c5b7-11e4-985e-7e04367921b1.png)
* **Cross-Compilation**
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
* **Playground**
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
## Architecture
### Build & Run
![Build & Run](https://cloud.githubusercontent.com/assets/873584/4389219/3642bc62-43f3-11e4-8d1f-06d7aaf22784.png)
* A browser tab corresponds to a Wide session
* Execution output push via WebSocket
Flow:
1. Browser sends ````Build```` request
2. Server executes ````go build```` command via ````os/exec````<br/>
2.1. Generates a executable file
3. Browser sends ````Run```` request
4. Server executes the file via ````os/exec````<br/>
4.1. A running process<br/>
4.2. Execution output push via WebSocket channel
5. Browser renders with callback function ````ws.onmessage````
### Code Assist
![Code Assist](https://cloud.githubusercontent.com/assets/873584/4399135/3b80c21c-4463-11e4-8e94-7f7e8d12a4df.png)
* Autocompletion
* Find Usages/Jump To Declaration/etc.
Flow:
1. Browser sends code assist request
2. Handler gets user workspace of the request with HTTP session
3. Server executes ````gocode````/````ide_stub(gotools)````<br/>
3.1 Sets environment variables (e.g. ${GOPATH})<br/>
3.2 ````gocode```` with ````lib-path```` parameter
## Documents
* [用户指南](https://www.gitbook.com/book/88250/wide-user-guide)
* [开发指南](https://www.gitbook.com/book/88250/wide-dev-guide)
## Setup
### Download Binary
We have provided OS-specific executable binary as follows:
* linux-amd64/386
* windows-amd64/386
* darwin-amd64/386
Download [HERE](http://pan.baidu.com/s/1dD3XwOT)!
### Build Wide for yourself
1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone https://github.com/b3log/wide`
2. Get dependencies with
* `go get`
* `go get github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports`
3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -p 127.0.0.1:7070:7070 88250/wide:latest ./wide -docker=true -channel=ws://127.0.0.1:7070`
3. Open browser: http://127.0.0.1:7070
## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
* [Rename directory](https://github.com/b3log/wide/issues/251)
## Terms
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any page, even which you made
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for a commercial license request
* Copyright &copy; b3log.org, all rights reserved
## Credits
Wide is made possible by the following open source projects.
* [golang](http://golang.org)
* [CodeMirror](https://github.com/marijnh/CodeMirror)
* [zTree](https://github.com/zTree/zTree_v3)
* [LiteIDE](https://github.com/visualfc/liteide)
* [gocode](https://github.com/nsf/gocode)
* [Gorilla](https://github.com/gorilla)
* [Docker](https://docker.com)
----
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />

View File

@ -1,4 +1,4 @@
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](https://b3log.org)" from any pages, even the pages are developed by you
* If you want to use this software for commercial purpose, please mail to os@b3log.org for request a commercial license
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any pages, even the pages are developed by you
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for request a commercial license
* Copyright (c) b3log.org, all rights reserved

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -15,13 +15,17 @@
package conf
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/b3log/wide/util"
)
// Panel represents a UI panel.
@ -47,24 +51,23 @@ type LatestSessionContent struct {
// User configuration.
type User struct {
Id string
Name string
Avatar string
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Locale string
GoFormat string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
FontFamily string
FontSize string
Theme string
Keymap string // wide/vim
Created int64 // user create time in unix nano
Updated int64 // preference update time in unix nano
Lived int64 // the latest session activity in unix nano
Editor *editor
LatestSessionContent *LatestSessionContent
Name string
Password string
Salt string
Email string
Gravatar string // see http://gravatar.com
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Locale string
GoFormat string
FontFamily string
FontSize string
Theme string
Keymap string // wide/vim
Created int64 // user create time in unix nano
Updated int64 // preference update time in unix nano
Lived int64 // the latest session activity in unix nano
Editor *editor
LatestSessionContent *LatestSessionContent
}
// Editor configuration of a user.
@ -76,7 +79,26 @@ type editor struct {
TabSize string
}
// Save saves the user's configurations in conf/users/{userId}.json.
// NewUser creates a user with the specified username, password, email and workspace.
func NewUser(username, password, email, workspace string) *User {
md5hash := md5.New()
md5hash.Write([]byte(email))
gravatar := hex.EncodeToString(md5hash.Sum(nil))
salt := util.Rand.String(16)
password = Salt(password, salt)
now := time.Now().UnixNano()
return &User{Name: username, Password: password, Salt: salt, Email: email, Gravatar: gravatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt", FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Keymap: "wide",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// Save saves the user's configurations in conf/users/{username}.json.
func (u *User) Save() bool {
bytes, err := json.MarshalIndent(u, "", " ")
@ -87,12 +109,12 @@ func (u *User) Save() bool {
}
if "" == string(bytes) {
logger.Error("Truncated user [" + u.Id + "]")
logger.Error("Truncated user [" + u.Name + "]")
return false
}
if err = ioutil.WriteFile(filepath.Join(Wide.Data, "users", u.Id+".json"), bytes, 0644); nil != err {
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err {
logger.Error(err)
return false
@ -101,62 +123,34 @@ func (u *User) Save() bool {
return true
}
// NewUser creates a user with the specified username and workspace.
func NewUser(id, name, avatar, workspace string) *User {
now := time.Now().UnixNano()
return &User{Id: id, Name: name, Avatar: avatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt",
GoBuildArgsForLinux: "", GoBuildArgsForWindows: "", GoBuildArgsForDarwin: "",
FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Keymap: "wide",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// WorkspacePath gets workspace path of the user.
// GetWorkspace gets workspace path of the user.
//
// 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 ${GOPATH} with enviorment variable GOPATH
// 3. Replace "/" with "\\" (Windows)
func (u *User) WorkspacePath() string {
w := u.Workspace
func (u *User) GetWorkspace() string {
w := strings.Replace(u.Workspace, "{WD}", Wide.WD, 1)
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
return filepath.FromSlash(w)
}
// BuildArgs get build args with the specified os.
func (u *User) BuildArgs(os string) []string {
var tmp string
if os == "windows" {
tmp = u.GoBuildArgsForWindows
}
if os == "linux" {
tmp = u.GoBuildArgsForLinux
}
if os == "darwin" {
tmp = u.GoBuildArgsForDarwin
}
exp := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)'`)
ret := exp.FindAllString(tmp, -1)
for idx := range ret {
ret[idx] = strings.Replace(ret[idx], "\"", "", -1)
}
return ret
}
// GetOwner gets the user the specified path belongs to. Returns "" if not found.
func GetOwner(path string) string {
for _, user := range Users {
if strings.HasPrefix(path, user.WorkspacePath()) {
return user.Id
if strings.HasPrefix(path, user.GetWorkspace()) {
return user.Name
}
}
return ""
}
// Salt salts the specified password with the specified salt.
func Salt(password, salt string) string {
sha1hash := sha1.New()
sha1hash.Write([]byte(password + salt))
return hex.EncodeToString(sha1hash.Sum(nil))
}

50
conf/users/admin.json Normal file
View File

@ -0,0 +1,50 @@
{
"Name": "admin",
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
"Salt": "dnoyeb",
"Email": "",
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Theme": "default",
"Keymap": "wide",
"Created": 1414080000000000000,
"Updated": 1477750918395543731,
"Lived": 1477756867409471881,
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "13px",
"LineHeight": "17px",
"Theme": "lesser-dark",
"TabSize": "4"
},
"LatestSessionContent": {
"fileTree": [
"/Users/Vanessa/Work/GoGoGo/src",
"/Users/Vanessa/Work/GoGoGo/src/github.com",
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log",
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide"
],
"files": [
"/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide/.gitignore"
],
"currentFile": "/Users/Vanessa/Work/GoGoGo/src/github.com/b3log/wide/.gitignore",
"layout": {
"side": {
"state": "normal",
"size": 200
},
"sideRight": {
"state": "normal",
"size": 200
},
"bottom": {
"state": "normal",
"size": 100
}
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -17,17 +17,19 @@ package conf
import (
"encoding/json"
"html/template"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/event"
"github.com/b3log/wide/event"
"github.com/b3log/wide/log"
"github.com/b3log/wide/util"
)
const (
@ -37,43 +39,43 @@ const (
PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current Wide's version.
WideVersion = "1.6.0"
WideVersion = "1.5.2"
// CodeMirrorVer holds the current editor version.
CodeMirrorVer = "5.1"
// UserAgent represents HTTP client user agent.
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
HelloWorld = `package main
import "fmt"
func main() {
fmt.Println("欢迎通过《边看边练 Go 系列》来学习 Go 语言 https://ld246.com/article/1437497122181")
fmt.Println("Hello, 世界")
}
`
)
// Configuration.
type conf struct {
Server string // server
LogLevel string // logging level: trace/debug/info/warn/error
Data string // data directory
RuntimeMode string // runtime mode (dev/prod)
HTTPSessionMaxAge int // HTTP session max age (in seciond)
StaticResourceVersion string // version of static resources
Locale string // default locale
Autocomplete bool // default autocomplete
SiteStatCode template.HTML // site statistic code
ReadOnly bool // read-only mode
OAuthLoginURL string
OAuthAccessTokenURL string
OAuthUserInfoURL string
OAuthClientID string
OAuthClientSecret string
IP string // server ip, ${ip}
Port string // server port
Context string // server context
Server string // server host and port ({IP}:{Port})
StaticServer string // static resources server scheme, host and port (http://{IP}:{Port})
LogLevel string // logging level: trace/debug/info/warn/error
Channel string // 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}
Locale string // default locale
Playground string // playground directory
UsersWorkspaces string // users' workspaces directory (admin defaults to ${GOPATH}, others using this)
AllowRegister bool // allow register or not
Autocomplete bool // default autocomplete
}
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Wide configurations.
var Wide *conf
@ -81,28 +83,21 @@ var Wide *conf
// configurations of users.
var Users []*User
// Indicates whether Docker is available.
// Indicates whether runs via Docker.
var Docker bool
// Docker image to run user's program
const DockerImageGo = "golang"
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json.
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
confPlayground string, confDocker bool, confUsersWorkspaces string) {
// XXX: ugly args list....
// Load loads the Wide configurations from wide.json and users' configurations from users/{userId}.json.
func Load(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
initWide(confPath, confData, confServer, confLogLevel, confReadOnly, confSiteStatCode)
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
confPlayground, confDocker, confUsersWorkspaces)
initUsers()
cmd := exec.Command("docker", "version")
_, err := cmd.CombinedOutput()
if nil != err {
logger.Warnf("Not found 'docker' installed, running user's code will cause security problem")
} else {
Docker = true
}
}
func initUsers() {
f, err := os.Open(Wide.Data + PathSeparator + "users")
f, err := os.Open("conf/users")
if nil != err {
logger.Error(err)
@ -128,7 +123,8 @@ func initUsers() {
user := &User{}
bytes, _ := os.ReadFile(filepath.Join(Wide.Data, "users", name))
bytes, _ := ioutil.ReadFile("conf/users/" + name)
err := json.Unmarshal(bytes, user)
if err != nil {
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
@ -141,17 +137,6 @@ func initUsers() {
user.Keymap = "wide"
}
// Compatibility upgrade (1.5.3): https://github.com/b3log/wide/issues/308
//if "" == user.GoBuildArgsForLinux {
// user.GoBuildArgsForLinux = "-i"
//}
//if "" == user.GoBuildArgsForWindows {
// user.GoBuildArgsForWindows = "-i"
//}
//if "" == user.GoBuildArgsForDarwin {
// user.GoBuildArgsForDarwin = "-i"
//}
Users = append(Users, user)
}
@ -159,8 +144,9 @@ func initUsers() {
initCustomizedConfs()
}
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
bytes, err := os.ReadFile(confPath)
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
confPlayground string, confDocker bool, confUsersWorkspaces string) {
bytes, err := ioutil.ReadFile(confPath)
if nil != err {
logger.Error(err)
@ -176,65 +162,102 @@ func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string,
os.Exit(-1)
}
Wide.Autocomplete = true // default to true
// Logging Level
gulu.Log.SetLevel(Wide.LogLevel)
log.SetLevel(Wide.LogLevel)
if "" != confLogLevel {
Wide.LogLevel = confLogLevel
gulu.Log.SetLevel(confLogLevel)
log.SetLevel(confLogLevel)
}
logger.Debug("Conf: \n" + string(bytes))
// Working Directory
Wide.WD = util.OS.Pwd()
logger.Debugf("${pwd} [%s]", Wide.WD)
// User Home
home, err := gulu.OS.Home()
home, err := util.OS.Home()
if nil != err {
logger.Error("Can't get user's home, please report this issue to developer", err)
os.Exit(-1)
}
logger.Debugf("${user.home} [%s]", home)
// Data directory
if "" != confData {
Wide.Data = confData
// Playground Directory
Wide.Playground = strings.Replace(Wide.Playground, "${home}", home, 1)
if "" != confPlayground {
Wide.Playground = confPlayground
}
Wide.Data = strings.Replace(Wide.Data, "${home}", home, -1)
Wide.Data = filepath.Clean(Wide.Data)
if err := os.MkdirAll(Wide.Data+"/playground/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
// Users' workspaces Directory
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${WD}", Wide.WD, 1)
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${home}", home, 1)
if "" != confUsersWorkspaces {
Wide.UsersWorkspaces = confUsersWorkspaces
}
if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
Wide.UsersWorkspaces = filepath.Clean(Wide.UsersWorkspaces)
os.Exit(-1)
}
if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
if !util.File.IsExist(Wide.Playground) {
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
logger.Errorf("Create Playground [%s] error", err)
os.Exit(-1)
os.Exit(-1)
}
}
// IP
if "" != confIP {
Wide.IP = confIP
} else {
ip, err := util.Net.LocalIP()
if nil != err {
logger.Error(err)
os.Exit(-1)
}
logger.Debugf("${ip} [%s]", ip)
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
}
if "" != confPort {
Wide.Port = confPort
}
// Docker flag
Docker = confDocker
// Server
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
Wide.Server = strings.Replace(Wide.Server, "{Port}", Wide.Port, 1)
if "" != confServer {
Wide.Server = confServer
}
if "" != confReadOnly {
Wide.ReadOnly, _ = strconv.ParseBool(confReadOnly)
// Static Server
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1)
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{Port}", Wide.Port, 1)
if "" != confStaticServer {
Wide.StaticServer = confStaticServer
}
// SiteStatCode
if "" != confSiteStatCode {
Wide.SiteStatCode = confSiteStatCode
// Context
if "" != confContext {
Wide.Context = confContext
}
time := strconv.FormatInt(time.Now().UnixNano(), 10)
logger.Debugf("${time} [%s]", time)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
// Channel
Wide.Channel = strings.Replace(Wide.Channel, "{IP}", Wide.IP, 1)
Wide.Channel = strings.Replace(Wide.Channel, "{Port}", Wide.Port, 1)
if "" != confChannel {
Wide.Channel = confChannel
}
}
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
@ -252,7 +275,7 @@ func FixedTimeCheckEnv() {
}
func checkEnv() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput()
@ -269,16 +292,16 @@ func checkEnv() {
os.Exit(-1)
}
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
gocode := util.Go.GetExecutableInGOBIN("gocode")
cmd = exec.Command(gocode)
_, err = cmd.Output()
if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound}
logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/stamblerre/gocode", gocode)
logger.Warnf("Not found gocode [%s], please install it with this command: go get github.com/nsf/gocode", gocode)
}
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ideStub := util.Go.GetExecutableInGOBIN("gotools")
cmd = exec.Command(ideStub, "version")
_, err = cmd.Output()
if nil != err {
@ -288,11 +311,11 @@ func checkEnv() {
}
}
// GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
func GetUserWorkspace(userId string) string {
// GetUserWorkspace gets workspace path with the specified username, returns "" if not found.
func GetUserWorkspace(username string) string {
for _, user := range Users {
if user.Id == userId {
return user.WorkspacePath()
if user.Name == username {
return user.GetWorkspace()
}
}
@ -300,14 +323,14 @@ func GetUserWorkspace(userId string) string {
}
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
func GetGoFmt(userId string) string {
func GetGoFmt(username string) string {
for _, user := range Users {
if user.Id == userId {
if user.Name == username {
switch user.GoFormat {
case "gofmt":
return "gofmt"
case "goimports":
return gulu.Go.GetExecutableInGOBIN("goimports")
return util.Go.GetExecutableInGOBIN("goimports")
default:
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
return "gofmt"
@ -318,14 +341,15 @@ func GetGoFmt(userId string) string {
return "gofmt"
}
// GetUser gets configuration of the user specified by the given user id, returns nil if not found.
func GetUser(id string) *User {
if "playground" == id { // reserved user for Playground
return NewUser("playground", "playground", "", "")
// GetUser gets configuration of the user specified by the given username, returns nil if not found.
func GetUser(username string) *User {
if "playground" == username { // reserved user for Playground
// mock it
return NewUser("playground", "", "", "")
}
for _, user := range Users {
if user.Id == id {
if user.Name == username {
return user
}
}
@ -336,17 +360,17 @@ func GetUser(id string) *User {
// initCustomizedConfs initializes the user customized configurations.
func initCustomizedConfs() {
for _, user := range Users {
UpdateCustomizedConf(user.Id)
UpdateCustomizedConf(user.Name)
}
}
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
//
// 1. /static/users/{userId}/style.css
func UpdateCustomizedConf(userId string) {
// 1. /static/user/{username}/style.css
func UpdateCustomizedConf(username string) {
var u *User
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
if user.Id == userId {
if user.Name == username {
u = user
}
}
@ -364,7 +388,8 @@ func UpdateCustomizedConf(userId string) {
os.Exit(-1)
}
dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
wd := util.OS.Pwd()
dir := filepath.Clean(wd + "/static/user/" + u.Name)
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
@ -394,7 +419,7 @@ func initWorkspaceDirs() {
paths := []string{}
for _, user := range Users {
paths = append(paths, filepath.SplitList(user.WorkspacePath())...)
paths = append(paths, filepath.SplitList(user.GetWorkspace())...)
}
for _, path := range paths {
@ -417,7 +442,7 @@ func CreateWorkspaceDir(path string) {
// createDir creates a directory on the path if it not exists.
func createDir(path string) {
if !gulu.File.IsExist(path) {
if !util.File.IsExist(path) {
if err := os.MkdirAll(path, 0775); nil != err {
logger.Error(err)

View File

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

24
coverage.sh Executable file
View File

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

BIN
deps/github.com.tar.gz vendored Normal file

Binary file not shown.

BIN
deps/golang.org.tar.gz vendored Normal file

Binary file not shown.

2
doc/README.md Normal file
View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -25,63 +25,129 @@ import (
"runtime"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/file"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/file"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// WSHandler handles request of creating editor channel.
// XXX: NOT used at present
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := httpSession.Values["id"].(string)
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
err := editorChan.WriteJSON(&ret)
if nil != err {
return
}
session.EditorWS[sid] = &editorChan
logger.Tracef("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS))
args := map[string]interface{}{}
for {
if err := session.EditorWS[sid].ReadJSON(&args); err != nil {
return
}
code := args["code"].(string)
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch)
logger.Tracef("offset: %d", offset)
gocode := util.Go.GetExecutableInGOBIN("gocode")
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
var output bytes.Buffer
cmd := exec.Command(gocode, argv...)
cmd.Stdout = &output
stdin, _ := cmd.StdinPipe()
cmd.Start()
stdin.Write([]byte(code))
stdin.Close()
cmd.Wait()
ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"}
if err := session.EditorWS[sid].WriteJSON(&ret); err != nil {
logger.Error("Editor WS ERROR: " + err.Error())
return
}
}
}
// AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
if conf.Wide.ReadOnly {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
username := session.Values["username"].(string)
path := args["path"].(string)
fout, err := os.Create(path)
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
offset := getCursorOffset(code, line, ch)
logger.Tracef("offset: %d", offset)
userWorkspace := conf.GetUserWorkspace(uid)
userWorkspace := conf.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
libPath := ""
for _, workspace := range workspaces {
@ -92,8 +158,8 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("gocode set lib-path [%s]", libPath)
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
// FIXME: using gocode set lib-path has some issues while accrossing workspaces
gocode := util.Go.GetExecutableInGOBIN("gocode")
exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
@ -102,7 +168,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -113,16 +179,16 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
// GetExprInfoHandler handles request of getting expression infomation.
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, session.CookieName)
uid := session.Values["uid"].(string)
session, _ := session.HTTPSession.Get(r, "wide-session")
username := session.Values["username"].(string)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -135,7 +201,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -145,7 +211,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -157,24 +223,24 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ideStub := util.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
exprInfo := strings.TrimSpace(string(output))
if "" == exprInfo {
result.Code = -1
result.Succ = false
return
}
@ -184,21 +250,21 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// FindDeclarationHandler handles request of finding declaration.
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
username := session.Values["username"].(string)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -211,7 +277,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -221,7 +287,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -233,24 +299,24 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ideStub := util.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
found := strings.TrimSpace(string(output))
if "" == found {
result.Code = -1
result.Succ = false
return
}
@ -272,22 +338,22 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// FindUsagesHandler handles request of finding usages.
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
username := session.Values["username"].(string)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -300,7 +366,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -310,7 +376,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -321,24 +387,24 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch)
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ideStub := util.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
out := strings.TrimSpace(string(output))
if "" == out {
result.Code = -1
result.Succ = false
return
}
@ -387,8 +453,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
return offset
}
func setCmdEnv(cmd *exec.Cmd, userId string) {
userWorkspace := conf.GetUserWorkspace(userId)
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.GetUserWorkspace(username)
cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -20,9 +20,9 @@ import (
"os"
"os/exec"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoFmtHandler handles request of formatting Go source code.
@ -31,36 +31,30 @@ import (
// 1. gofmt
// 2. goimports
func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := session.Values["uid"].(string)
username := session.Values["username"].(string)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) {
result.Code = -1
if util.Go.IsAPI(filePath) {
result.Succ = false
return
}
@ -69,7 +63,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -79,7 +73,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -91,7 +85,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
result.Data = data
fmt := conf.GetGoFmt(uid)
fmt := conf.GetGoFmt(username)
argv := []string{filePath}
cmd := exec.Command(fmt, argv...)
@ -100,7 +94,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
output := string(bytes)
if "" == output {
// format error, returns the original content
result.Code = 0
result.Succ = true
return
}
@ -112,7 +106,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -18,7 +18,8 @@ package event
import (
"os"
"github.com/88250/gulu"
"github.com/b3log/wide/log"
"github.com/b3log/wide/util"
)
const (
@ -38,7 +39,7 @@ const (
const maxQueueLength = 10
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Event represents an event.
type Event struct {
@ -69,7 +70,7 @@ var UserEventQueues = queues{}
// Load initializes the event handling.
func Load() {
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
for event := range EventQueue {
logger.Debugf("Received a global event [code=%d]", event.Code)
@ -106,7 +107,7 @@ func (ueqs queues) New(sid string) *UserEventQueue {
ueqs[sid] = q
go func() { // start listening
defer gulu.Panic.Recover(nil)
defer util.Recover()
for evt := range q.Queue {
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)

57
file/decompress.go Normal file
View File

@ -0,0 +1,57 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"encoding/json"
"net/http"
"path/filepath"
"github.com/b3log/wide/util"
)
// DecompressHandler handles request of decompressing zip/tar.gz.
func DecompressHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
path := args["path"].(string)
// base := filepath.Base(path)
dir := filepath.Dir(path)
if !util.File.IsExist(path) {
result.Succ = false
result.Msg = "Can't find file [" + path + "] to descompress"
return
}
err := util.Zip.Unzip(path, dir)
if nil != err {
logger.Error(err)
result.Succ = false
return
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -20,7 +20,7 @@ import (
"os"
"path/filepath"
"github.com/88250/gulu"
"github.com/b3log/wide/util"
)
// GetZipHandler handles request of retrieving zip file.
@ -34,7 +34,7 @@ func GetZipHandler(w http.ResponseWriter, r *http.Request) {
return
}
if !gulu.File.IsExist(path) {
if !util.File.IsExist(path) {
http.Error(w, "Not Found", 404)
return
@ -51,13 +51,13 @@ func GetZipHandler(w http.ResponseWriter, r *http.Request) {
// CreateZipHandler handles request of creating zip.
func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
data := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, data)
data := util.NewResult()
defer util.RetResult(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data.Code = -1
data.Succ = false
return
}
@ -75,24 +75,24 @@ func CreateZipHandler(w http.ResponseWriter, r *http.Request) {
dir := filepath.Dir(path)
if !gulu.File.IsExist(path) {
data.Code = -1
if !util.File.IsExist(path) {
data.Succ = false
data.Msg = "Can't find file [" + path + "]"
return
}
zipPath := filepath.Join(dir, name)
zipFile, err := gulu.Zip.Create(zipPath + ".zip")
zipFile, err := util.Zip.Create(zipPath + ".zip")
if nil != err {
logger.Error(err)
data.Code = -1
data.Succ = false
return
}
defer zipFile.Close()
if gulu.File.IsDir(path) {
if util.File.IsDir(path) {
zipFile.AddDirectory(base, path)
} else {
zipFile.AddEntry(base, path)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -24,14 +24,15 @@ import (
"sort"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/event"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/event"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Node represents a file node in file tree.
type Node struct {
@ -60,7 +61,7 @@ var apiNode *Node
// initAPINode builds the Go API file node.
func initAPINode() {
apiPath := gulu.Go.GetAPIPath()
apiPath := util.Go.GetAPIPath()
apiNode = &Node{Name: "Go API", Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
Creatable: false, Removable: false, IsGoAPI: true, Children: []*Node{}}
@ -73,18 +74,18 @@ func initAPINode() {
// 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 GetFilesHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetGzResult(w, r, result)
result := util.NewResult()
defer util.RetGzResult(w, r, result)
userWorkspace := conf.GetUserWorkspace(uid)
userWorkspace := conf.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}}
@ -122,18 +123,18 @@ func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
// RefreshDirectoryHandler handles request of refresh a directory of file tree.
func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
r.ParseForm()
path := r.FormValue("path")
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -155,37 +156,37 @@ func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
// GetFileHandler handles request of opening file by editor.
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
path := args["path"].(string)
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
size := gulu.File.GetFileSize(path)
size := util.File.GetFileSize(path)
if size > 5242880 { // 5M
result.Code = -1
result.Succ = false
result.Msg = "This file is too large to open :("
return
@ -198,30 +199,30 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
extension := filepath.Ext(path)
if gulu.File.IsImg(extension) {
if util.File.IsImg(extension) {
// image file will be open in a browser tab
data["mode"] = "img"
userId := conf.GetOwner(path)
if "" == userId {
logger.Warnf("The path [%s] has no owner", path)
username := conf.GetOwner(path)
if "" == username {
logger.Warnf("The path [%s] has no owner")
data["path"] = ""
return
}
user := conf.GetUser(uid)
user := conf.GetUser(username)
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.WorkspacePath(), "", 1)
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1)
return
}
content := string(buf)
if gulu.File.IsBinary(content) {
result.Code = -1
if util.File.IsBinary(content) {
result.Succ = false
result.Msg = "Can't open a binary file :("
} else {
data["content"] = content
@ -231,28 +232,22 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
// SaveFileHandler handles request of saving file.
func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -260,7 +255,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string)
sid := args["sid"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -270,7 +265,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -281,7 +276,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
wSession := session.WideSessions.Get(sid)
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
@ -293,29 +288,29 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
// NewFileHandler handles request of creating file or directory.
func NewFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
if util.Go.IsAPI(path) || !session.CanAccess(username, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -327,7 +322,7 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid)
if !createFile(path, fileType) {
result.Code = -1
result.Succ = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't create file " + path}
@ -336,38 +331,38 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
}
if "f" == fileType {
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.Username)
} else {
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.Username)
}
}
// RemoveFileHandler handles request of removing file or directory.
func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
if util.Go.IsAPI(path) || !session.CanAccess(username, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -378,7 +373,7 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid)
if !removeFile(path) {
result.Code = -1
result.Succ = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't remove file " + path}
@ -386,41 +381,41 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
return
}
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.Username)
}
// RenameFileHandler handles request of renaming file or directory.
func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
oldPath := args["oldPath"].(string)
if gulu.Go.IsAPI(oldPath) ||
!session.CanAccess(uid, oldPath) {
if util.Go.IsAPI(oldPath) ||
!session.CanAccess(username, oldPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
newPath := args["newPath"].(string)
if gulu.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
if util.Go.IsAPI(newPath) || !session.CanAccess(username, newPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -431,7 +426,7 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
wSession := session.WideSessions.Get(sid)
if !renameFile(oldPath, newPath) {
result.Code = -1
result.Succ = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't rename file " + oldPath}
@ -439,7 +434,7 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
return
}
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.UserId)
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.Username)
}
// Use to find results sorting.
@ -456,27 +451,27 @@ func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
// FindHandler handles request of find files under the specified directory with the specified filename pattern.
func FindHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
path := args["path"].(string) // path of selected file in file tree
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -484,10 +479,10 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
name := args["name"].(string)
userWorkspace := conf.GetUserWorkspace(uid)
userWorkspace := conf.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
if "" != path && !gulu.File.IsDir(path) {
if "" != path && !util.File.IsDir(path) {
path = filepath.Dir(path)
}
@ -497,7 +492,7 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
rs := find(workspace+conf.PathSeparator+"src", name, []*string{})
for _, r := range rs {
substr := gulu.Str.LCS(path, *r)
substr := util.Str.LCS(path, *r)
founds = append(founds, &foundPath{Path: filepath.ToSlash(*r), score: len(substr)})
}
@ -510,21 +505,21 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
// SearchTextHandler handles request of searching files under the specified directory with the specified keyword.
func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -532,7 +527,7 @@ func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
result.Succ = false
return
}
@ -541,7 +536,7 @@ func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
dir := args["dir"].(string)
if "" == dir {
userWorkspace := conf.GetUserWorkspace(wSession.UserId)
userWorkspace := conf.GetUserWorkspace(wSession.Username)
workspaces := filepath.SplitList(userWorkspace)
dir = workspaces[0]
}
@ -550,7 +545,7 @@ func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
text := args["text"].(string)
founds := []*Snippet{}
if gulu.File.IsDir(dir) {
if util.File.IsDir(dir) {
founds = search(dir, extension, text, []*Snippet{})
} else {
founds = searchInFile(dir, text)
@ -649,7 +644,7 @@ func listFiles(dirname string) []string {
//
// Refers to the zTree document for CSS class names.
func getIconSkin(filenameExtension string) string {
if gulu.File.IsImg(filenameExtension) {
if util.File.IsImg(filenameExtension) {
return "ico-ztree-img "
}
@ -768,7 +763,7 @@ func find(dir, name string, results []*string) []*string {
path := dir + fname
if fileInfo.IsDir() {
if gulu.Str.Contains(fname, defaultExcludesFind) {
if util.Str.Contains(fname, defaultExcludesFind) {
continue
}
@ -841,7 +836,7 @@ func searchInFile(path string, text string) []*Snippet {
}
content := string(bytes)
if gulu.File.IsBinary(content) {
if util.File.IsBinary(content) {
return ret
}

77
file/importer.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"github.com/b3log/wide/util"
)
type fileInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
}
func handleUpload(p *multipart.Part, dir string) (fi *fileInfo) {
fi = &fileInfo{
Name: p.FileName(),
Type: p.Header.Get("Content-Type"),
}
path := filepath.Clean(dir + "/" + fi.Name)
f, _ := os.Create(path)
io.Copy(f, p)
f.Close()
return
}
func handleUploads(r *http.Request, dir string) (fileInfos []*fileInfo) {
fileInfos = make([]*fileInfo, 0)
mr, err := r.MultipartReader()
part, err := mr.NextPart()
for err == nil {
if name := part.FormName(); name != "" {
if part.FileName() != "" {
fileInfos = append(fileInfos, handleUpload(part, dir))
}
}
part, err = mr.NextPart()
}
return
}
// UploadHandler handles request of file upload.
func UploadHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
q := r.URL.Query()
dir := q["path"][0]
result.Data = handleUploads(r, dir)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -23,7 +23,7 @@ import (
"net/http"
"strings"
"github.com/88250/gulu"
"github.com/b3log/wide/util"
)
type element struct {
@ -34,14 +34,14 @@ type element struct {
// GetOutlineHandler gets outfile of a go file.
func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -51,7 +51,7 @@ func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", code, 0)
if err != nil {
result.Code = -1
result.Succ = false
return
}

23
go.mod
View File

@ -1,23 +0,0 @@
module github.com/88250/wide
go 1.20
require (
github.com/88250/gulu v1.1.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/parnurzeal/gorequest v0.2.16
)
require (
github.com/davidebianchi/go-jsonclient v1.5.0 // indirect
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // indirect
golang.org/x/text v0.3.2 // indirect
moul.io/http2curl v1.0.0 // indirect
)

44
go.sum
View File

@ -1,44 +0,0 @@
github.com/88250/gulu v1.1.0 h1:aU8HncW1XNssT1insCOz6rvmTUDrtsDwSnzeqTEqNFA=
github.com/88250/gulu v1.1.0/go.mod h1:a2POIziN+QFeNT4Mj7FHH2lz1HEaFlMRF6wPE0NHM4U=
github.com/davidebianchi/go-jsonclient v1.5.0 h1:PVDunAF/6c30D2SSx711efsrUP3hhml+uGT6oD3lzVY=
github.com/davidebianchi/go-jsonclient v1.5.0/go.mod h1:lXd/hkx23H590Dod74j5GsmpgxF8RIAGZDt1P5+2mqo=
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 h1:TEmChtx8+IeOghiySC8kQIr0JZOdKUmRmmkuRDuYs3E=
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ=
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-2018, b3log.org
* Copyright (c) 2014-2015, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -18,133 +18,118 @@
* @file frontend tool.
*
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
* @version 0.1.0.0, Dec 15, 2015
*/
var gulp = require('gulp')
var concat = require('gulp-concat')
var cleanCSS = require('gulp-clean-css')
var uglify = require('gulp-uglify')
var sourcemaps = require('gulp-sourcemaps')
var gulp = require("gulp");
var concat = require('gulp-concat');
var minifyCSS = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var sourcemaps = require("gulp-sourcemaps");
function minLibCSS () {
// css
var cssLibs = [
'./static/js/lib/jquery-layout/layout-default-latest.css',
'./static/js/lib/codemirror-5.1/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
'./static/js/lib/codemirror-5.1/addon/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
'./static/js/overwrite/codemirror/theme/*.css']
return gulp.src(cssLibs).
pipe(cleanCSS()).
pipe(concat('lib.min.css')).
pipe(gulp.dest('./static/css/'))
}
gulp.task('cc', function () {
// css
var cssLibs = ['./static/js/lib/jquery-layout/layout-default-latest.css',
'./static/js/lib/codemirror-5.1/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
'./static/js/lib/codemirror-5.1/addon/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
'./static/js/overwrite/codemirror/theme/*.css'];
gulp.src(cssLibs)
.pipe(minifyCSS())
.pipe(concat('lib.min.css'))
.pipe(gulp.dest('./static/css/'));
function minZTreeStyleCSS () {
return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
pipe(cleanCSS()).
pipe(concat('zTreeStyle.min.css')).
pipe(gulp.dest('./static/js/lib/ztree/'))
}
gulp.src('./static/js/lib/ztree/zTreeStyle.css')
.pipe(minifyCSS())
.pipe(concat('zTreeStyle.min.css'))
.pipe(gulp.dest('./static/js/lib/ztree/'));
function minWideCSS () {
var cssWide = [
'./static/css/dialog.css',
'./static/css/base.css',
'./static/css/wide.css',
'./static/css/side.css',
'./static/css/start.css',
'./static/css/about.css',
]
var cssWide = ['./static/css/dialog.css',
'./static/css/base.css',
'./static/css/wide.css',
'./static/css/side.css',
'./static/css/start.css',
'./static/css/about.css'
];
return gulp.src(cssWide).
pipe(cleanCSS()).
pipe(concat('wide.min.css')).
pipe(gulp.dest('./static/css/'))
}
gulp.src(cssWide)
.pipe(minifyCSS())
.pipe(concat('wide.min.css'))
.pipe(gulp.dest('./static/css/'));
function minLibJS () {
// js
var jsLibs = [
'./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js',
'./static/js/lib/Autolinker.min.js',
'./static/js/lib/emmet.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js']
return gulp.src(jsLibs).
pipe(uglify()).
pipe(concat('lib.min.js')).
pipe(gulp.dest('./static/js/'))
}
function minWideJS () {
var jsWide = [
'./static/js/tabs.js',
'./static/js/tabs.js',
'./static/js/dialog.js',
'./static/js/editors.js',
'./static/js/notification.js',
'./static/js/tree.js',
'./static/js/wide.js',
'./static/js/session.js',
'./static/js/menu.js',
'./static/js/windows.js',
'./static/js/hotkeys.js',
'./static/js/bottomGroup.js',
]
return gulp.src(jsWide).
pipe(sourcemaps.init()).
pipe(uglify()).
pipe(concat('wide.min.js')).
pipe(sourcemaps.write('.')).
pipe(gulp.dest('./static/js/'))
}
// js
var jsLibs = ['./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js',
'./static/js/lib/Autolinker.min.js',
'./static/js/lib/emmet.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js'];
gulp.src(jsLibs)
.pipe(uglify())
.pipe(concat('lib.min.js'))
.pipe(gulp.dest('./static/js/'));
gulp.task('default',
gulp.series(
gulp.parallel(minLibCSS, minZTreeStyleCSS, minWideCSS, minLibJS, minWideJS)))
var jsWide = ['./static/js/tabs.js',
'./static/js/tabs.js',
'./static/js/dialog.js',
'./static/js/editors.js',
'./static/js/notification.js',
'./static/js/tree.js',
'./static/js/wide.js',
'./static/js/session.js',
'./static/js/menu.js',
'./static/js/windows.js',
'./static/js/hotkeys.js',
'./static/js/bottomGroup.js'
];
gulp.src(jsWide)
.pipe(sourcemaps.init())
.pipe(uglify())
.pipe(concat('wide.min.js'))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest('./static/js/'));
});

View File

@ -1,7 +1,7 @@
{
"colon": ": ",
"wide": "Wide",
"wide_title": "Playing golang, anytime, anywhere",
"wide_title": "Team development, anytime, anywhere",
"cancel": "Cancel",
"file": "File",
"login": "Login",
@ -28,6 +28,7 @@
"close_all_files": "Close All",
"save_all_files": "Save All",
"format": "Format",
"goget": "go get",
"goinstall": "go install",
"build": "Build",
"build_n_run": "Build&Run",
@ -101,6 +102,11 @@
"start-install": "START [go install]",
"install-succ": "[go install] SUCCESS",
"install-error": "[go install] ERROR",
"start-get": "START [go get]",
"get-succ": "[go get] SUCCESS",
"get-error": "[go get] ERROR",
"start-git_clone": "START [git clone]",
"git_clone-done": "[git clone] DONE",
"check_version": "Checking update",
"new_version_available": "new version available",
"go_env": "Go",
@ -114,6 +120,7 @@
"team": "Team",
"sing_up_error": "Sign Up Error",
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
"invalid_email": "Invalid Email",
"password_no_match": "Password doesn't match the confirmation",
"discard": "Discard",
"close": "Close",
@ -132,6 +139,7 @@
"clearOutput": "Clear Output",
"export": "Export",
"refresh": "Refresh",
"import": "Import",
"theme": "Theme",
"tab_size": "Tab Size",
"copy_file_path": "Copy File Path",
@ -150,8 +158,11 @@
"source": "Source",
"toggle_comment": "Toggle Comment",
"find_in_files": "Find in Files",
"email": "Email",
"no_empty": "Can not Empty!",
"change_avatar": "Avatar modify go",
"open": "Open",
"pricing": "Pricing",
"search_no_match": "No matching files were found.",
"outline": "Outline",
"govet": "go vet",
@ -161,11 +172,12 @@
"restore_outline": "Restore Outline",
"share": "Share",
"url": "URL",
"short_url": "Short URL",
"embeded": "Embeded",
"git_clone": "Git Clone",
"terms": "Terms",
"download": "Download",
"decompress": "Decompress",
"keymap": "Keymap",
"resize": "Resize",
"sponsor": "Sponsor"
"resize": "Resize"
}

View File

@ -1,7 +1,7 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "いつでも、どこでもゴランをプレイする",
"wide_title": "チーム開発、いつでも、どこでも",
"cancel": "取消",
"file": "ファイル",
"login": "ログイン",
@ -28,6 +28,7 @@
"close_all_files": "全てのファイルを閉じる",
"save_all_files": "全てを保存",
"format": "フォーマット",
"goget": "go get",
"goinstall": "go install",
"build": "ビルド",
"build_n_run": "ビルド実行",
@ -101,6 +102,11 @@
"start-install": "[go install] 開始",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"start-get": "[go get] 開始",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失敗",
"start-git_clone": "[git clone] 開始",
"git_clone-done": "[git clone] 終わった",
"check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります",
"go_env": "Go",
@ -114,6 +120,7 @@
"team": "チーム",
"sing_up_error": "登録に失敗しました",
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
"invalid_email": "無効な電子メール",
"password_no_match": "一貫性のないパスワード入力",
"discard": "あきらめる",
"close": "クローズ",
@ -132,6 +139,7 @@
"clearOutput": "空の出力",
"export": "輸出",
"refresh": "リフレッシュ",
"import": "インポート",
"theme": "テーマ",
"tab_size": "Tab サイズ",
"copy_file_path": "ファイルパスをコピー",
@ -150,22 +158,26 @@
"source": "ソース",
"toggle_comment": "トグルコメント",
"find_in_files": "ファイルから検索",
"email": "Eメール",
"no_empty": "空ではありません",
"change_avatar": "アバターの変更は行く",
"open": "オープン",
"pricing": "价格",
"search_no_match": "一致するファイルが見つかりませんでした。",
"outline": "アウトライン",
"govet": "go vet",
"start-vet": "[go vet] 開始",
"vet-succ": "[go vet] 成功",
"vet-error": "[go vet] 失敗",
"start-vet": "START [go vet]",
"vet-succ": "[go vet] SUCCESS",
"vet-error": "[go vet] ERROR",
"restore_outline": "アウトラインを復元",
"share": "シェア",
"url": "リンク",
"short_url": "ショートリンク",
"embeded": "埋め込む",
"git_clone": "Git クローン",
"terms": "利用規約",
"download": "ダウンロード",
"decompress": "解凍する",
"keymap": "キーマップ",
"resize": "サイズ変更",
"sponsor": "スポンサー"
"resize": "サイズ変更"
}

View File

@ -1,7 +1,7 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "언제 어디서나 골란 놀기",
"wide_title": "언제 어디서나, 모두의 개발환경.",
"cancel": "취소",
"file": "문서",
"login": "로그인",
@ -28,6 +28,7 @@
"close_all_files": "모두 닫기",
"save_all_files": "일괄저장",
"format": "포멧",
"goget": "go get",
"goinstall": "go install",
"build": "빌드",
"build_n_run": "빌드후실행",
@ -101,6 +102,11 @@
"start-install": "시작 [go install]",
"install-succ": "[go install] 성공",
"install-error": "[go install] 실패",
"start-get": "시작 [go get]",
"get-succ": "[go get] 성공",
"get-error": "[go get] 실패",
"start-git_clone": "시작 [git clone]",
"git_clone-done": "[git clone] 완성",
"check_version": "최신버전검색중",
"new_version_available": "최신업데이트 사용 가능",
"go_env": "Go 환경",
@ -114,6 +120,7 @@
"team": "단체",
"sing_up_error": "가입실패",
"user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,",
"invalid_email": "유효하지 않은 email주소",
"password_no_match": "비밀번호 오류",
"discard": "취소",
"close": "닫기",
@ -132,6 +139,7 @@
"clearOutput": "ouput 클리어",
"export": "내보내기",
"refresh": "새로고침",
"import": "가져오기",
"theme": "주제",
"tab_size": "Tab 크기",
"copy_file_path": "경로복사",
@ -150,22 +158,26 @@
"source": "소스",
"toggle_comment": "주석",
"find_in_files": "문서에서 찾기",
"email": "이메일",
"no_empty": "값을 입력해 주세요.",
"change_avatar": "아이콘변경은 여기로.",
"open": "열기",
"pricing": "가격",
"search_no_match": "해당 문서를 찾지 못하였습니다.",
"outline": "주제",
"govet": "go vet",
"start-vet": "시작 [go vet]",
"vet-succ": "[go vet] 성공",
"vet-error": "[go vet] 실패",
"start-vet": "START [go vet]",
"vet-succ": "[go vet] SUCCESS",
"vet-error": "[go vet] ERROR",
"restore_outline": "주제복구",
"share": "공유",
"url": "하이퍼링크",
"short_url": "짧은 링크",
"embeded": "삽입",
"git_clone": "Git clone",
"terms": "사용계약",
"download": "다운로드",
"decompress": "압축풀기",
"keymap": "단축키",
"resize": "크기조절",
"sponsor": "후원사"
"resize": "크기조절"
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -17,15 +17,16 @@ package i18n
import (
"encoding/json"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/88250/gulu"
"github.com/b3log/wide/log"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Locale.
type locale struct {
@ -43,10 +44,6 @@ func Load() {
names, _ := f.Readdirnames(-1)
f.Close()
if len(Locales) == len(names)-1 {
return
}
for _, name := range names {
if !strings.HasSuffix(name, ".json") {
continue
@ -58,7 +55,7 @@ func Load() {
}
func load(localeStr string) {
bytes, err := os.ReadFile("i18n/" + localeStr + ".json")
bytes, err := ioutil.ReadFile("i18n/" + localeStr + ".json")
if nil != err {
logger.Error(err)

View File

@ -1,7 +1,7 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "随时随地玩 golang",
"wide_title": "团队开发,随时随地",
"cancel": "取消",
"file": "文件",
"login": "登录",
@ -28,6 +28,7 @@
"close_all_files": "关闭所有文件",
"save_all_files": "保存所有文件",
"format": "格式化",
"goget": "go get",
"goinstall": "go install",
"build": "构建",
"build_n_run": "构建并运行",
@ -101,6 +102,11 @@
"start-install": "开始 [go install]",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失败",
"start-get": "开始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失败",
"start-git_clone": "开始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在检查更新",
"new_version_available": "新版本可用",
"go_env": "Go 环境",
@ -114,6 +120,7 @@
"team": "团队",
"sing_up_error": "注册失败",
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成长度为16",
"invalid_email": "无效的电子邮件",
"password_no_match": "密码输入不一致",
"discard": "放弃",
"close": "关闭",
@ -132,6 +139,7 @@
"clearOutput": "清空输出",
"export": "导出",
"refresh": "刷新",
"import": "导入",
"theme": "主题",
"tab_size": "Tab 大小",
"copy_file_path": "复制文件路径",
@ -150,22 +158,26 @@
"source": "源码",
"toggle_comment": "注释",
"find_in_files": "在文件中查找",
"email": "电子邮件",
"no_empty": "不能为空",
"change_avatar": "头像修改请到",
"open": "打开",
"pricing": "价格",
"search_no_match": "没有发现匹配的文件。",
"outline": "大纲",
"govet": "go vet",
"start-vet": "开始 [go vet]",
"vet-succ": "[go vet] 成功",
"vet-error": "[go vet] 失败",
"start-vet": "START [go vet]",
"vet-succ": "[go vet] SUCCESS",
"vet-error": "[go vet] ERROR",
"restore_outline": "恢复大纲",
"share": "分享",
"url": "链接",
"short_url": "短链接",
"embeded": "嵌入",
"git_clone": "Git 克隆",
"terms": "使用条款",
"download": "下载",
"decompress": "解压缩",
"keymap": "快捷键",
"resize": "调整大小",
"sponsor": "赞助"
"resize": "调整大小"
}

View File

@ -1,7 +1,7 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "隨時隨地玩 golang",
"wide_title": "團隊開發,隨時隨地",
"cancel": "取消",
"file": "檔案",
"login": "登入",
@ -28,6 +28,7 @@
"close_all_files": "關閉所有檔案",
"save_all_files": "儲存所有檔案",
"format": "格式化",
"goget": "go get",
"goinstall": "go install",
"build": "編譯",
"build_n_run": "編譯並執行",
@ -101,6 +102,11 @@
"start-install": "開始 [go install]",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"start-get": "開始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失敗",
"start-git_clone": "開始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在檢查更新",
"new_version_available": "可用新版本",
"go_env": "Go 環境",
@ -114,6 +120,7 @@
"team": "團隊",
"sing_up_error": "註冊失敗",
"user_name_ruler": "帳號只能由 az, AZ, 0-9, _ 組成長度為16",
"invalid_email": "無效的電子郵件",
"password_no_match": "密碼輸入不一致",
"discard": "捨棄",
"close": "關閉",
@ -132,6 +139,7 @@
"clearOutput": "清空輸出",
"export": "導出",
"refresh": "刷新",
"import": "導入",
"theme": "主題",
"tab_size": "Tab 大小",
"copy_file_path": "複製檔案位置",
@ -150,8 +158,11 @@
"source": "原始碼",
"toggle_comment": "註解",
"find_in_files": "在文件中尋找",
"email": "電子郵件",
"no_empty": "不能為空",
"change_avatar": "修改頭像請到",
"open": "開啟",
"pricing": "價格",
"search_no_match": "沒有發現匹配的文件。",
"outline": "大綱",
"govet": "go vet",
@ -161,11 +172,12 @@
"restore_outline": "恢復大綱",
"share": "分享",
"url": "連結",
"short_url": "短網址",
"embeded": "嵌入",
"git_clone": "Git Clone",
"terms": "使用條款",
"download": "下載",
"decompress": "解壓縮",
"keymap": "快速鍵",
"resize": "調整大小",
"sponsor": "贊助"
"resize": "調整大小"
}

217
log/logs.go Executable file
View File

@ -0,0 +1,217 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package log includes logging related manipulations.
//
// log.SetLevel("debug")
// logger := log.NewLogger(os.Stdout)
//
// logger.Trace("trace message)
// logger.Debug("debug message")
// logger.Info("info message")
// logger.Warn("warning message")
// logger.Error("error message")
//
// logger.Errorf("formatted %s message", "error")
package log
import (
"fmt"
"io"
stdlog "log"
"strings"
)
// Logging level.
const (
Off = iota
Trace
Debug
Info
Warn
Error
)
// all loggers.
var loggers []*Logger
// the global default logging level, it will be used for creating logger.
var logLevel = Debug
// Logger represents a simple logger with level.
// The underlying logger is the standard Go logging "log".
type Logger struct {
level int
logger *stdlog.Logger
}
// NewLogger creates a logger.
func NewLogger(out io.Writer) *Logger {
ret := &Logger{level: logLevel, logger: stdlog.New(out, "", stdlog.Ldate|stdlog.Ltime|stdlog.Lshortfile)}
loggers = append(loggers, ret)
return ret
}
// SetLevel sets the logging level of all loggers.
func SetLevel(level string) {
logLevel = getLevel(level)
for _, l := range loggers {
l.SetLevel(level)
}
}
// getLevel gets logging level int value corresponding to the specified level.
func getLevel(level string) int {
level = strings.ToLower(level)
switch level {
case "off":
return Off
case "trace":
return Trace
case "debug":
return Debug
case "info":
return Info
case "warn":
return Warn
case "error":
return Error
default:
return Info
}
}
// SetLevel sets the logging level of a logger.
func (l *Logger) SetLevel(level string) {
l.level = getLevel(level)
}
// IsTraceEnabled determines whether the trace level is enabled.
func (l *Logger) IsTraceEnabled() bool {
return l.level <= Trace
}
// IsDebugEnabled determines whether the debug level is enabled.
func (l *Logger) IsDebugEnabled() bool {
return l.level <= Debug
}
// IsWarnEnabled determines whether the debug level is enabled.
func (l *Logger) IsWarnEnabled() bool {
return l.level <= Warn
}
// Trace prints trace level message.
func (l *Logger) Trace(v ...interface{}) {
if Trace < l.level {
return
}
l.logger.SetPrefix("T ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Tracef prints trace level message with format.
func (l *Logger) Tracef(format string, v ...interface{}) {
if Trace < l.level {
return
}
l.logger.SetPrefix("T ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Debug prints debug level message.
func (l *Logger) Debug(v ...interface{}) {
if Debug < l.level {
return
}
l.logger.SetPrefix("D ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Debugf prints debug level message with format.
func (l *Logger) Debugf(format string, v ...interface{}) {
if Debug < l.level {
return
}
l.logger.SetPrefix("D ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Info prints info level message.
func (l *Logger) Info(v ...interface{}) {
if Info < l.level {
return
}
l.logger.SetPrefix("I ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Infof prints info level message with format.
func (l *Logger) Infof(format string, v ...interface{}) {
if Info < l.level {
return
}
l.logger.SetPrefix("I ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Warn prints warning level message.
func (l *Logger) Warn(v ...interface{}) {
if Warn < l.level {
return
}
l.logger.SetPrefix("W ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Warn prints warning level message with format.
func (l *Logger) Warnf(format string, v ...interface{}) {
if Warn < l.level {
return
}
l.logger.SetPrefix("W ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}
// Error prints error level message.
func (l *Logger) Error(v ...interface{}) {
if Error < l.level {
return
}
l.logger.SetPrefix("E ")
l.logger.Output(2, fmt.Sprint(v...))
}
// Errorf prints error level message with format.
func (l *Logger) Errorf(format string, v ...interface{}) {
if Error < l.level {
return
}
l.logger.SetPrefix("E ")
l.logger.Output(2, fmt.Sprintf(format, v...))
}

169
log/logs_test.go Normal file
View File

@ -0,0 +1,169 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"os"
"testing"
)
// Logger.
var logger = NewLogger(os.Stdout)
func TestSetLevel(t *testing.T) {
SetLevel("trace")
}
func TestTrace(t *testing.T) {
logger.SetLevel("trace")
logger.Trace("trace")
logger.SetLevel("off")
logger.Trace("trace")
}
func TestTracef(t *testing.T) {
logger.SetLevel("trace")
logger.Tracef("tracef")
logger.SetLevel("off")
logger.Tracef("tracef")
}
func TestDebug(t *testing.T) {
logger.SetLevel("debug")
logger.Debug("debug")
logger.SetLevel("off")
logger.Debug("debug")
}
func TestDebugf(t *testing.T) {
logger.SetLevel("debug")
logger.Debugf("debugf")
logger.SetLevel("off")
logger.Debug("debug")
}
func TestInfo(t *testing.T) {
logger.SetLevel("info")
logger.Info("info")
logger.SetLevel("off")
logger.Info("info")
}
func TestInfof(t *testing.T) {
logger.SetLevel("info")
logger.Infof("infof")
logger.SetLevel("off")
logger.Infof("infof")
}
func TestWarn(t *testing.T) {
logger.SetLevel("warn")
logger.Warn("warn")
logger.SetLevel("off")
logger.Warn("warn")
}
func TestWarnf(t *testing.T) {
logger.SetLevel("warn")
logger.Warnf("warnf")
logger.SetLevel("off")
logger.Warnf("warnf")
}
func TestError(t *testing.T) {
logger.SetLevel("error")
logger.Error("error")
logger.SetLevel("off")
logger.Error("error")
}
func TestErrorf(t *testing.T) {
logger.SetLevel("error")
logger.Errorf("errorf")
logger.SetLevel("off")
logger.Errorf("errorf")
}
func TestGetLevel(t *testing.T) {
if getLevel("trace") != Trace {
t.FailNow()
return
}
if getLevel("debug") != Debug {
t.FailNow()
return
}
if getLevel("info") != Info {
t.FailNow()
return
}
if getLevel("warn") != Warn {
t.FailNow()
return
}
if getLevel("error") != Error {
t.FailNow()
return
}
}
func TestLoggerSetLevel(t *testing.T) {
logger.SetLevel("trace")
if logger.level != Trace {
t.FailNow()
return
}
}
func TestIsTraceEnabled(t *testing.T) {
logger.SetLevel("trace")
if !logger.IsTraceEnabled() {
t.FailNow()
return
}
}
func TestIsDebugEnabled(t *testing.T) {
logger.SetLevel("debug")
if !logger.IsDebugEnabled() {
t.FailNow()
return
}
}
func TestIsWarnEnabled(t *testing.T) {
logger.SetLevel("warn")
if !logger.IsWarnEnabled() {
t.FailNow()
return
}
}

289
main.go
View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -23,142 +23,171 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/editor"
"github.com/88250/wide/event"
"github.com/88250/wide/file"
"github.com/88250/wide/i18n"
"github.com/88250/wide/notification"
"github.com/88250/wide/output"
"github.com/88250/wide/playground"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/editor"
"github.com/b3log/wide/event"
"github.com/b3log/wide/file"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/notification"
"github.com/b3log/wide/output"
"github.com/b3log/wide/playground"
"github.com/b3log/wide/scm/git"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// Logger
var logger *gulu.Logger
var logger *log.Logger
// The only one init function in Wide.
func init() {
confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
confData := flag.String("data", "", "path of data dir")
confIP := flag.String("ip", "", "this will overwrite Wide.IP if specified")
confPort := flag.String("port", "", "this will overwrite Wide.Port if specified")
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified")
confReadOnly := flag.String("readonly", "", "this will overrite Wide.ReadOnly if specified")
confSiteStatCode := flag.String("site_stat_code", "", "this will overrite Wide.SiteStatCode if specified")
confStaticServer := flag.String("static_server", "", "this will overwrite Wide.StaticServer if specified")
confContext := flag.String("context", "", "this will overwrite Wide.Context if specified")
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel if specified")
confStat := flag.Bool("stat", false, "whether report statistics periodically")
confDocker := flag.Bool("docker", false, "whether run in a docker container")
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
confUsersWorkspaces := flag.String("users_workspaces", "", "this will overwrite Wide.UsersWorkspaces if specified")
flag.Parse()
gulu.Log.SetLevel("warn")
logger = gulu.Log.NewLogger(os.Stdout)
log.SetLevel("warn")
logger = log.NewLogger(os.Stdout)
//wd := gulu.OS.Pwd()
//if strings.HasPrefix(wd, os.TempDir()) {
// logger.Error("Don't run Wide in OS' temp directory or with `go run`")
//
// os.Exit(-1)
//}
wd := util.OS.Pwd()
if strings.HasPrefix(wd, os.TempDir()) {
logger.Error("Don't run Wide in OS' temp directory or with `go run`")
os.Exit(-1)
}
i18n.Load()
event.Load()
conf.Load(*confPath, *confData, *confServer, *confLogLevel, *confReadOnly, template.HTML(*confSiteStatCode))
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
*confPlayground, *confDocker, *confUsersWorkspaces)
conf.FixedTimeCheckEnv()
session.FixedTimeSave()
session.FixedTimeRelease()
session.FixedTimeReport()
logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
if *confStat {
session.FixedTimeReport()
}
logger.Debug("host ["+runtime.Version()+", "+runtime.GOOS+"_"+runtime.GOARCH+"], cross-compilation ",
util.Go.GetCrossPlatforms())
}
// Main.
func main() {
runtime.GOMAXPROCS(conf.Wide.MaxProcs)
initMime()
handleSignal()
// IDE
http.HandleFunc("/", handlerGzWrapper(indexHandler))
http.HandleFunc("/start", handlerWrapper(startHandler))
http.HandleFunc("/about", handlerWrapper(aboutHandler))
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
http.HandleFunc(conf.Wide.Context+"/", handlerGzWrapper(indexHandler))
http.HandleFunc(conf.Wide.Context+"/start", handlerWrapper(startHandler))
http.HandleFunc(conf.Wide.Context+"/about", handlerWrapper(aboutHandler))
http.HandleFunc(conf.Wide.Context+"/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
// static resources
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.Handle("/static/users/", http.StripPrefix("/static/", http.FileServer(http.Dir(conf.Wide.Data+"/static"))))
serveSingle("/favicon.ico", "./static/images/favicon.png")
http.Handle(conf.Wide.Context+"/static/", http.StripPrefix(conf.Wide.Context+"/static/", http.FileServer(http.Dir("static"))))
serveSingle("/favicon.ico", "./static/favicon.ico")
// oauth
http.HandleFunc("/login/redirect", session.LoginRedirectHandler)
http.HandleFunc("/login/callback", session.LoginCallbackHandler)
// workspaces
for _, user := range conf.Users {
http.Handle(conf.Wide.Context+"/workspace/"+user.Name+"/",
http.StripPrefix(conf.Wide.Context+"/workspace/"+user.Name+"/", http.FileServer(http.Dir(user.GetWorkspace()))))
}
// session
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
http.HandleFunc(conf.Wide.Context+"/session/ws", handlerWrapper(session.WSHandler))
http.HandleFunc(conf.Wide.Context+"/session/save", handlerWrapper(session.SaveContentHandler))
// run
http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
http.HandleFunc("/run", handlerWrapper(output.RunHandler))
http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
http.HandleFunc("/go/test", handlerWrapper(output.GoTestHandler))
http.HandleFunc("/go/vet", handlerWrapper(output.GoVetHandler))
http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
http.HandleFunc(conf.Wide.Context+"/build", handlerWrapper(output.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/run", handlerWrapper(output.RunHandler))
http.HandleFunc(conf.Wide.Context+"/stop", handlerWrapper(output.StopHandler))
http.HandleFunc(conf.Wide.Context+"/go/test", handlerWrapper(output.GoTestHandler))
http.HandleFunc(conf.Wide.Context+"/go/vet", handlerWrapper(output.GoVetHandler))
http.HandleFunc(conf.Wide.Context+"/go/get", handlerWrapper(output.GoGetHandler))
http.HandleFunc(conf.Wide.Context+"/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc(conf.Wide.Context+"/output/ws", handlerWrapper(output.WSHandler))
// cross-compilation
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
http.HandleFunc(conf.Wide.Context+"/cross", handlerWrapper(output.CrossCompilationHandler))
// file tree
http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.FindHandler))
// outline
http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutlineHandler))
// file export
http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
// file export/import
http.HandleFunc(conf.Wide.Context+"/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.UploadHandler))
http.HandleFunc(conf.Wide.Context+"/file/decompress", handlerWrapper(file.DecompressHandler))
// editor
http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
http.HandleFunc(conf.Wide.Context+"/editor/ws", handlerWrapper(editor.WSHandler))
http.HandleFunc(conf.Wide.Context+"/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc(conf.Wide.Context+"/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc(conf.Wide.Context+"/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc(conf.Wide.Context+"/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
// shell
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
// notification
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler))
// user
http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
http.HandleFunc(conf.Wide.Context+"/login", handlerWrapper(session.LoginHandler))
http.HandleFunc(conf.Wide.Context+"/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUserHandler))
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
// playground
http.HandleFunc("/playground", handlerWrapper(playground.IndexHandler))
http.HandleFunc("/playground/", handlerWrapper(playground.IndexHandler))
http.HandleFunc("/playground/ws", handlerWrapper(playground.WSHandler))
http.HandleFunc("/playground/save", handlerWrapper(playground.SaveHandler))
http.HandleFunc("/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc("/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc("/playground/stop", handlerWrapper(playground.StopHandler))
http.HandleFunc("/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler))
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler))
http.HandleFunc(conf.Wide.Context+"/playground/short-url", handlerWrapper(playground.ShortURLHandler))
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler))
http.HandleFunc(conf.Wide.Context+"/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server)
// git
http.HandleFunc(conf.Wide.Context+"/git/clone", handlerWrapper(git.CloneHandler))
err := http.ListenAndServe("0.0.0.0:7070", nil)
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil {
logger.Error(err)
}
@ -166,51 +195,57 @@ func main() {
// indexHandler handles request of Wide index.
func indexHandler(w http.ResponseWriter, r *http.Request) {
if "/" != r.RequestURI {
http.Redirect(w, r, "/", http.StatusFound)
if conf.Wide.Context+"/" != r.RequestURI {
http.Redirect(w, r, conf.Wide.Context+"/", http.StatusFound)
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
uid := httpSession.Values["uid"].(string)
if "playground" == uid { // reserved user for Playground
http.Redirect(w, r, "/login", http.StatusFound)
username := httpSession.Values["username"].(string)
if "playground" == username { // reserved user for Playground
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
user := conf.GetUser(uid)
user := conf.GetUser(username)
if nil == user {
http.Redirect(w, r, "/login", http.StatusFound)
logger.Warnf("Not found user [%s]", username)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
locale := user.Locale
wideSessions := session.WideSessions.GetByUserId(uid)
wideSessions := session.WideSessions.GetByUsername(username)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"username": username, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": []string{"darwin_amd64", "linux_amd64", "windows_amd64"}}
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()}
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/index.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -218,22 +253,6 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model)
}
// handleSignal handles system signal for graceful shutdown.
func handleSignal() {
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
s := <-c
logger.Tracef("Got signal [%s]", s)
session.SaveOnlineUsers()
logger.Tracef("Saved all online user, exit")
os.Exit(0)
}()
}
// serveSingle registers the handler function for the given pattern and filename.
func serveSingle(pattern string, filename string) {
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
@ -243,20 +262,22 @@ 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, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/s", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
user := conf.GetUser(uid)
locale := user.Locale
userWorkspace := conf.GetUserWorkspace(uid)
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
userWorkspace := conf.GetUserWorkspace(username)
sid := r.URL.Query()["sid"][0]
wSession := session.WideSessions.Get(sid)
@ -265,13 +286,13 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
}
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"uid": uid, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid, "username": user.Name}
"username": username, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid}
t, err := template.ParseFiles("views/start.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -281,18 +302,21 @@ 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, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
@ -300,7 +324,7 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -310,18 +334,21 @@ 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, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
@ -330,7 +357,7 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
@ -409,7 +436,7 @@ func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http
// panicRecover wraps the panic recover process.
func panicRecover(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
defer gulu.Panic.Recover(nil)
defer util.Recover()
handler(w, r)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -21,12 +21,12 @@ import (
"strconv"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/event"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/88250/wide/util"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/event"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
@ -40,7 +40,7 @@ const (
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Notification represents a notification.
type Notification struct {
@ -62,9 +62,9 @@ func event2Notification(e *event.Event) {
return
}
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, session.CookieName)
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, "wide-session")
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var notification *Notification

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -17,132 +17,94 @@ package output
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// BuildHandler handles request of building.
func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
user := conf.GetUser(uid)
locale := user.Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
curDir := filepath.Dir(filePath)
fout, err := os.Create(filePath)
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
code := args["code"].(string)
if _, err := fout.WriteString(code); nil != err {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
fout.Close()
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go build]" in front-end browser
msg := i18n.Get(locale, "start-build").(string)
msg = strings.Replace(msg, "build]", "build "+fmt.Sprint(user.BuildArgs(runtime.GOOS))+"]", 1)
channelRet["output"] = "<span class='start-build'>" + msg + "</span>\n"
channelRet["cmd"] = "start-build"
wsChannel := session.OutputWS[sid]
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
var goModCmd *exec.Cmd
if !gulu.File.IsExist(filepath.Join(curDir, "go.mod")) {
curDirName := filepath.Base(curDir)
goModCmd = exec.Command("go", "mod", "init", curDirName)
} else {
goModCmd = exec.Command("go", "mod", "tidy")
}
goModCmd.Dir = curDir
setCmdEnv(goModCmd, uid)
outputBytes, err := goModCmd.CombinedOutput()
output := string(outputBytes)
if nil != err && strings.Contains(output, "go.mod already exists") {
logger.Error(err.Error() + ": " + output)
result.Code = -1
return
}
var goBuildArgs []string
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(runtime.GOOS)...)
//if !gulu.Str.Contains("-i", goBuildArgs) {
// goBuildArgs = append(goBuildArgs, "-i")
//}
cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
suffix := ""
if gulu.OS.IsWindows() {
if util.OS.IsWindows() {
suffix = ".exe"
}
cmd := exec.Command("go", "build")
cmd.Dir = curDir
setCmdEnv(cmd, username)
executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -150,166 +112,151 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
if 0 != result.Code {
if !result.Succ {
return
}
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
channelRet := map[string]interface{}{}
return
}
if nil != session.OutputWS[sid] {
// display "START [go build]" in front-end browser
channelRet["cmd"] = "build"
channelRet["executable"] = executable
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
channelRet["cmd"] = "start-build"
outReader := bufio.NewReader(stdout)
/////////
go func() {
defer gulu.Panic.Recover(nil)
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := outReader.ReadString('\n')
if io.EOF == err {
break
}
_, ok := err.(*os.PathError)
if ok {
// 构建时报 “read |0: file already closed” https://github.com/b3log/wide/issues/363
break
}
if nil != err {
logger.Warnf("%#v", err)
break
}
channelRet["output"] = line
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}()
errReader := bufio.NewReader(stderr)
var lines []string
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
line, err := errReader.ReadString('\n')
if io.EOF == err {
break
}
lines = append(lines, line)
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
// path process
errOutWithPath := parsePath(curDir, line)
channelRet["output"] = "<span class='stderr'>" + errOutWithPath + "</span>"
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
logger.Error(err)
return
}
wsChannel.Refresh()
}
if nil == cmd.Wait() {
channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
} else {
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n"
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
// lint process
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
lints := []*Lint{}
return
}
for _, line := range lines {
if len(line) < 1 || !strings.Contains(line, ":") {
continue
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir)
// read all
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "build"
channelRet["executable"] = executable
if 0 == len(buf) { // build success
channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
go func() { // go install, for subsequent gocode lib-path
defer util.Recover()
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, username)
out, _ := cmd.CombinedOutput()
if len(out) > 0 {
logger.Warn(string(out))
}
}()
} else { // build error
// build gutter lint
errOut := string(buf)
lines := strings.Split(errOut, "\n")
// path process
var errOutWithPath string
for _, line := range lines {
errOutWithPath += parsePath(curDir, line) + "\n"
}
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" +
"<span class='stderr'>" + errOutWithPath + "</span>"
lints[last-1].Msg = msg
// lint process
continue
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, err = strconv.Atoi(left[:index])
lints := []*Lint{}
if nil != err {
for _, line := range lines {
if len(line) < 1 {
continue
}
msg = left[index+2:]
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, err = strconv.Atoi(left[:index])
if nil != err {
continue
}
msg = left[index+2:]
}
lint := &Lint{
File: filepath.Join(curDir, file),
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
lint := &Lint{
File: filepath.ToSlash(filepath.Join(curDir, file)),
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
channelRet["lints"] = lints
}
channelRet["lints"] = lints
}
if nil != session.OutputWS[sid] {
// logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
return
}
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -26,37 +26,31 @@ import (
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// CrossCompilationHandler handles request of cross compilation.
func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -64,7 +58,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string)
filePath := args["path"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
@ -81,15 +75,10 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
suffix = ".exe"
}
user := conf.GetUser(uid)
goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
cmd := exec.Command("go", goBuildArgs...)
cmd := exec.Command("go", "build")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
for i, env := range cmd.Env {
if strings.HasPrefix(env, "GOOS=") {
@ -112,7 +101,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -120,12 +109,12 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
if 0 != result.Code {
if !result.Succ {
return
}
@ -152,15 +141,17 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer util.Recover()
defer cmd.Wait()
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir)
// read all
buf, _ := ioutil.ReadAll(reader)
@ -239,6 +230,8 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
}
if nil != session.OutputWS[sid] {
// logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {

146
output/get.go Normal file
View File

@ -0,0 +1,146 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoGetHandler handles request of go get.
func GoGetHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command("go", "get")
cmd.Dir = curDir
setCmdEnv(cmd, username)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
if !result.Succ {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go get]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
channelRet["cmd"] = "start-get"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go get] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get"
// read all
buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) {
logger.Debugf("User [%s, %s] 's [go get] [runningId=%d] has done (with error)", username, sid, runningId)
channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go get] [runningId=%d] has done", username, sid, runningId)
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -26,37 +26,31 @@ import (
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoInstallHandler handles request of go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -69,14 +63,14 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
logger.Debugf("go install %s", curDir)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -84,12 +78,12 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
if 0 != result.Code {
if !result.Succ {
return
}
@ -116,16 +110,16 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir)
// read all
buf, _ := ioutil.ReadAll(reader)
@ -189,7 +183,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
}
if nil != session.OutputWS[sid] {
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir)
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)

25
output/namespace.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !linux
package output
import (
"os/exec"
)
func SetNamespace(cmd *exec.Cmd) {
// do nothing
}

35
output/namespace_linux.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"os/exec"
"syscall"
)
func SetNamespace(cmd *exec.Cmd) {
// XXX: keep move with Go 1.4 and later's
cmd.SysProcAttr = &syscall.SysProcAttr{}
//cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER /*| syscall.CLONE_NEWNS*/ | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC /*| syscall.CLONE_NEWNET*/
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: 0,
Gid: 0,
}
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -25,10 +25,10 @@ import (
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
"github.com/88250/wide/util"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
@ -38,7 +38,7 @@ const (
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Lint represents a code lint.
type Lint struct {
@ -93,7 +93,7 @@ func parsePath(curDir, outputLine string) string {
column = parts[2]
}
tagStart := `<span class="path" data-path="` + filepath.ToSlash(filepath.Join(curDir, file)) + `" data-line="` + line +
tagStart := `<span class="path" data-path="` + filepath.Join(curDir, file) + `" data-line="` + line +
`" data-column="` + column + `">`
text := file + ":" + line
if hasColumn {
@ -104,29 +104,18 @@ func parsePath(curDir, outputLine string) string {
return tagStart + text + tagEnd + msgPart
}
func setCmdEnv(cmd *exec.Cmd, uid string) {
userWorkspace := conf.GetUserWorkspace(uid)
cache, err := os.UserCacheDir()
if nil != err {
logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
cache = os.TempDir()
}
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.GetUserWorkspace(username)
cmd.Env = append(cmd.Env,
"GOPROXY=https://goproxy.cn",
"GO111MODULE=on",
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(),
"GOCACHE="+cache,
"PATH="+os.Getenv("PATH"))
if gulu.OS.IsWindows() {
if util.OS.IsWindows() {
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized.
cmd.Env = append(cmd.Env, os.Environ()...)
} else {
// 编译链接时找不到依赖的动态库 https://github.com/b3log/wide/issues/352
cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+os.Getenv("LD_LIBRARY_PATH"))
}
}

105
output/processes.go Normal file
View File

@ -0,0 +1,105 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"os"
"sync"
"github.com/b3log/wide/session"
)
// Type of process set.
type procs map[string][]*os.Process
// Processse of all users.
//
// <sid, []*os.Process>
var Processes = procs{}
// Exclusive lock.
var mutex sync.Mutex
// Add adds the specified process to the user process set.
func (procs *procs) Add(wSession *session.WideSession, proc *os.Process) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses
// bind process with wide session
wSession.SetProcesses(userProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
}
// Remove removes the specified process from the user process set.
func (procs *procs) Remove(wSession *session.WideSession, proc *os.Process) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
var newProcesses []*os.Process
for i, p := range userProcesses {
if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
return
}
}
}
// Kill kills a process specified by the given pid.
func (procs *procs) Kill(wSession *session.WideSession, pid int) {
mutex.Lock()
defer mutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
for i, p := range userProcesses {
if p.Pid == pid {
if err := p.Kill(); nil != err {
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.Username, sid, err)
} else {
var newProcesses []*os.Process
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.Username, sid)
}
return
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -15,16 +15,272 @@
package output
import (
"github.com/88250/wide/session"
"bufio"
"encoding/json"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
const (
outputBufMax = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) {
session.RunHandler(w, r, session.OutputWS)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
}
filePath := args["executable"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command(filePath)
cmd.Dir = curDir
if conf.Docker {
SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
}
wsChannel := session.OutputWS[sid]
channelRet := map[string]interface{}{}
if !result.Succ {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
go func() {
defer util.Recover()
buf := outputBuf{}
count := 0
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
count++
if nil != err {
// remove the exited process from user's process set
Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ",
wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.OutputWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
}
// StopHandler handles request of stopping a running process.
// StopHandler handles request of stoping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
session.StopHandler(w, r)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
return
}
Processes.Kill(wSession, pid)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -24,31 +24,31 @@ import (
"os/exec"
"path/filepath"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoTestHandler handles request of go test.
func GoTestHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -61,12 +61,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "test", "-v")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -74,12 +74,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
if 0 != result.Code {
if !result.Succ {
return
}
@ -106,15 +106,15 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer util.Recover()
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test"
@ -126,11 +126,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait()
if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", uid, sid, runningId)
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", username, sid, runningId)
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", uid, sid, runningId)
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", username, sid, runningId)
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -24,31 +24,31 @@ import (
"os/exec"
"path/filepath"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// GoVetHandler handles request of go vet.
func GoVetHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -61,12 +61,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "vet", ".")
cmd.Dir = curDir
setCmdEnv(cmd, uid)
setCmdEnv(cmd, username)
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -74,12 +74,12 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
if 0 != result.Code {
if !result.Succ {
return
}
@ -106,15 +106,15 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer util.Recover()
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "go vet"
@ -126,11 +126,11 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait()
if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", uid, sid, runningId)
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId)
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
} else {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", uid, sid, runningId)
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId)
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
}

3773
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

12
pkg.sh
View File

@ -24,7 +24,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
@ -33,7 +33,7 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
@ -45,7 +45,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
@ -54,7 +54,7 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
@ -66,7 +66,7 @@ export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe
@ -75,6 +75,6 @@ export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.zip
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
go build github.com/nsf/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -18,32 +18,26 @@ import (
"bytes"
"encoding/json"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
if conf.Wide.ReadOnly {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
@ -54,28 +48,20 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
line := int(args["cursorLine"].(float64))
ch := int(args["cursorCh"].(float64))
file, err := os.Create("wide_autocomplete_" + gulu.Rand.String(16) + ".go")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
file.WriteString(code)
file.Close()
path := file.Name()
defer os.Remove(path)
offset := getCursorOffset(code, line, ch)
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
gocode := gulu.Go.GetExecutableInGOBIN("gocode")
argv := []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
gocode := util.Go.GetExecutableInGOBIN("gocode")
cmd := exec.Command(gocode, argv...)
stdin, _ := cmd.StdinPipe()
stdin.Write([]byte(code))
stdin.Close()
output, err := cmd.CombinedOutput()
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -22,23 +22,17 @@ import (
"path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// BuildHandler handles request of Playground building.
func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
@ -48,23 +42,23 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
fileName := args["fileName"].(string)
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
suffix := ""
if gulu.OS.IsWindows() {
if util.OS.IsWindows() {
suffix = ".exe"
}
data := map[string]interface{}{}
result.Data = &data
executable := filepath.Clean(conf.Wide.Data + "/playground/" + strings.Replace(fileName, ".go", suffix, -1))
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1))
cmd := exec.Command("go", "build", "-o", executable, filePath)
out, err := cmd.CombinedOutput()
@ -72,7 +66,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
data["output"] = template.HTML(string(out))
if nil != err {
result.Code = -1
result.Succ = false
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -23,24 +23,19 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/session"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// SaveHandler handles request of Playground code save.
func SaveHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
session, _ := session.HTTPSession.Get(r, session.CookieName)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
@ -50,7 +45,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -63,7 +58,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
stdin, err := cmd.StdinPipe()
if nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -90,13 +85,54 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
data["fileName"] = fileName
// Step3. write file
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
fout, err := os.Create(filePath)
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
}
// ShortURLHandler handles request of short URL.
func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
url := args["url"].(string)
resp, _ := http.Post("http://dwz.cn/create.php", "application/x-www-form-urlencoded",
strings.NewReader("url="+url))
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
logger.Error(err)
result.Succ = false
return
}
shortURL := url
if 0 == response["status"].(float64) {
shortURL = response["tinyurl"].(string)
}
result.Data = shortURL
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -26,40 +26,43 @@ import (
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
"github.com/88250/wide/util"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// IndexHandler handles request of Playground index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
// create a HTTP session
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Values["uid"] = "playground"
httpSession.Values["username"] = "playground"
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
locale := conf.Wide.Locale
// try to load file
code := conf.HelloWorld
fileName := "6c5595ec6fbadf4cfce3edbfcfd8c6d0.go" // MD5 of HelloWorld.go
fileName := "8b7cc38b4c12e6fde5c4d15a4f2f32e5.go" // MD5 of HelloWorld.go
if strings.HasSuffix(r.URL.Path, ".go") {
fileNameArg := r.URL.Path[len("/playground/"):]
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileNameArg)
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileNameArg)
bytes, err := ioutil.ReadFile(filePath)
if nil != err {
@ -89,14 +92,15 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"embed": embed, "disqus": disqus, "fileName": fileName}
wideSessions := session.WideSessions.GetByUserId(uid)
wideSessions := session.WideSessions.GetByUsername(username)
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/playground/index.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -15,16 +15,264 @@
package playground
import (
"github.com/88250/wide/session"
"bufio"
"encoding/json"
"math/rand"
"net/http"
"os/exec"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/output"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
const (
outputBufMax = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) {
session.RunHandler(w, r, session.PlaygroundWS)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
}
filePath := args["executable"].(string)
cmd := exec.Command(filePath)
if conf.Docker {
output.SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
}
wsChannel := session.PlaygroundWS[sid]
channelRet := map[string]interface{}{}
if !result.Succ {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
output.Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
go func() {
defer util.Recover()
buf := outputBuf{}
count := 0
for {
wsChannel := session.PlaygroundWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
count++
if nil != err {
// remove the exited process from user process set
output.Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
break
}
oneRuneStr := string(r)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.PlaygroundWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
buf.content += oneRuneStr
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
}
}
}(rand.Int())
}
// StopHandler handles request of stopping a running process.
// StopHandler handles request of stoping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
session.StopHandler(w, r)
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Succ = false
return
}
output.Processes.Kill(wSession, pid)
}

146
scm/git/clone.go Normal file
View File

@ -0,0 +1,146 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package git
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// Logger.
var logger = log.NewLogger(os.Stdout)
// Clone handles request of git clone.
func CloneHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
path := args["path"].(string)
repository := args["repository"].(string)
cmd := exec.Command("git", "clone", repository)
cmd.Dir = path
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
if !result.Succ {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [git clone]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-git_clone").(string) + "</span>\n"
channelRet["cmd"] = "start-git_clone"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [git clone] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "git clone"
// read all
buf, err := ioutil.ReadAll(reader)
if nil != err {
logger.Warn(err)
// TODO: handle clone error
}
logger.Debugf("User [%s, %s] 's running [git clone] [runningId=%d] has done: %s", username, sid, runningId, string(buf))
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "git_clone-done").(string) + "</span>\n"
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,185 +0,0 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package session
import (
"fmt"
"html/template"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/util"
)
var states = map[string]string{}
// LoginRedirectHandler redirects to HacPai auth page.
func LoginRedirectHandler(w http.ResponseWriter, r *http.Request) {
loginAuthURL := conf.Wide.OAuthLoginURL + "?response_type=code&redirect_uri=" + conf.Wide.Server + "/login/callback"
// надо будет добавить ttlcache для state и проверять для предотвращения атак CSRF
state := gulu.Rand.String(16)
states[state] = state
path := loginAuthURL + "&state=" + state + "&client_id=" + conf.Wide.OAuthClientID
http.Redirect(w, r, path, http.StatusSeeOther)
}
func LoginCallbackHandler(w http.ResponseWriter, r *http.Request) {
state := r.URL.Query().Get("state")
if _, exist := states[state]; !exist {
http.Error(w, "Get state param failed", http.StatusBadRequest)
return
}
delete(states, state)
code := r.URL.Query().Get("code")
accessToken, err := util.GetOAuthToken(
conf.Wide.OAuthAccessTokenURL,
conf.Wide.OAuthClientID,
conf.Wide.OAuthClientSecret,
code,
conf.Wide.Server+`/login/callback`)
if err != nil {
http.Error(w, fmt.Sprintf(`get access_token failed. error: %s`, err.Error()), http.StatusBadRequest)
return
}
userInfo, err := util.OpenIdUserInfo(conf.Wide.OAuthUserInfoURL, accessToken)
if err != nil {
http.Error(w, fmt.Sprintf(`get user_info failed. error: %s`, err.Error()), http.StatusBadRequest)
return
}
userId := userInfo["userId"].(string)
userName := userInfo["userName"].(string)
avatar := userInfo["avatar"].(string)
user := conf.GetUser(userId)
if nil == user {
msg := addUser(userId, userName, avatar)
if userCreated != msg {
result := gulu.Ret.NewResult()
result.Code = -1
result.Msg = msg
gulu.Ret.RetResult(w, r, result)
return
}
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Values["uid"] = userId
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// LoginHandler handles request of show login page.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()}
t, err := template.ParseFiles("views/login.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
t.Execute(w, model)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// addUser add a user with the specified user id, username and avatar.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(userId, userName, userAvatar string) string {
if "playground" == userId {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Id) == strings.ToLower(userId) {
return userExists
}
}
workspace := filepath.Join(conf.Wide.Data, "workspaces", userId)
newUser := conf.NewUser(userId, userName, userAvatar, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(userId)
logger.Infof("Created a user [%s]", userId)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
func helloWorld(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}

View File

@ -1,313 +0,0 @@
// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package session
import (
"bytes"
"encoding/json"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/util"
)
// Type of process set.
type procs map[string][]*os.Process
// Processse of all users.
//
// <sid, []*os.Process>
var Processes = procs{}
// Exclusive lock.
var procMutex sync.Mutex
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request, channel map[string]*util.WSChannel) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
}
sid := args["sid"].(string)
wSession := WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
}
filePath := args["executable"].(string)
randInt := rand.Int()
rid := strconv.Itoa(randInt)
var cmd *exec.Cmd
if conf.Docker {
fileName := filepath.Base(filePath)
cmd = exec.Command("docker", "run", "--rm", "--name", rid, "-v", filePath+":/"+fileName,
"--memory", "64M", "--cpus", "0.1",
conf.DockerImageGo, "/"+fileName)
} else {
cmd = exec.Command(filePath)
curDir := filepath.Dir(filePath)
cmd.Dir = curDir
}
outBuf := &bytes.Buffer{}
errBuf := &bytes.Buffer{}
cmd.Stdout = outBuf
cmd.Stderr = errBuf
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
}
wsChannel := channel[sid]
channelRet := map[string]interface{}{}
if 0 != result.Code {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
return
}
done := make(chan error)
go func() { done <- cmd.Wait() }()
channelRet["pid"] = cmd.Process.Pid
Processes.Add(wSession, cmd.Process)
shouldExitBuf := false
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
go func() {
defer gulu.Panic.Recover(nil)
logger.Debugf("User [%s, %s] is running [id=%s, file=%s]", wSession.UserId, sid, rid, filePath)
go func() {
defer gulu.Panic.Recover(nil)
for {
if shouldExitBuf {
break
}
if 1 > outBuf.Len() {
time.Sleep(7 * time.Millisecond)
continue
}
r, _, err := outBuf.ReadRune()
if nil != err {
time.Sleep(7 * time.Millisecond)
continue
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
channelRet["output"] = oneRuneStr
wsChannel := channel[sid]
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
}()
for {
if shouldExitBuf {
break
}
if 1 > errBuf.Len() {
time.Sleep(7 * time.Millisecond)
continue
}
r, _, err := errBuf.ReadRune()
if nil != err {
time.Sleep(7 * time.Millisecond)
continue
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + oneRuneStr + "</span>"
wsChannel := channel[sid]
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
}()
after := time.After(5 * time.Second)
kill := false
select {
case <-after:
if conf.Docker {
killCmd := exec.Command("docker", "rm", "-f", rid)
if err := killCmd.Run(); nil != err {
logger.Errorf("executes [docker rm -f " + rid + "] failed [" + err.Error() + "], this will cause resource leaking")
}
} else {
cmd.Process.Kill()
}
channelRet["output"] = "\n<span class='stderr'>run program timeout in 5s</span>\n"
kill = true
case <-done:
channelRet["output"] = "\n<span class='stderr'>run program complete</span>\n"
}
shouldExitBuf = true
Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] done running [id=%s, file=%s, kill=%v]", wSession.UserId, sid, rid, filePath, kill)
if nil != wsChannel {
channelRet["cmd"] = "run-done"
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}
// StopHandler handles request of stopping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
return
}
Processes.Kill(wSession, pid)
}
// Add adds the specified process to the user process set.
func (procs *procs) Add(wSession *WideSession, proc *os.Process) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses
// bind process with wide session
wSession.SetProcesses(userProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
}
// Remove removes the specified process from the user process set.
func (procs *procs) Remove(wSession *WideSession, proc *os.Process) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
var newProcesses []*os.Process
for i, p := range userProcesses {
if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
return
}
}
}
// Kill kills a process specified by the given pid.
func (procs *procs) Kill(wSession *WideSession, pid int) {
procMutex.Lock()
defer procMutex.Unlock()
sid := wSession.ID
userProcesses := (*procs)[sid]
for i, p := range userProcesses {
if p.Pid == pid {
if err := p.Kill(); nil != err {
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.UserId, sid, err)
} else {
var newProcesses []*os.Process
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// bind process with wide session
wSession.SetProcesses(newProcesses)
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.UserId, sid)
}
return
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -31,15 +31,14 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/event"
"github.com/88250/wide/util"
"github.com/fsnotify/fsnotify"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/event"
"github.com/b3log/wide/log"
"github.com/b3log/wide/util"
"github.com/go-fsnotify/fsnotify"
"github.com/gorilla/sessions"
"github.com/gorilla/websocket"
)
@ -47,12 +46,10 @@ import (
const (
sessionStateActive = iota
sessionStateClosed // (not used so far)
CookieName = "wide-sess"
)
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
var (
// SessionWS holds all session channels. <sid, *util.WSChannel>
@ -77,7 +74,7 @@ var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// WideSession represents a session associated with a browser tab.
type WideSession struct {
ID string // id
UserId string // user id
Username string // username
HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // process set
EventQueue *event.UserEventQueue // event queue
@ -105,7 +102,7 @@ var mutex sync.Mutex
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
func FixedTimeRelease() {
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
for _ = range time.Tick(time.Hour) {
hour, _ := time.ParseDuration("-30m")
@ -113,7 +110,7 @@ func FixedTimeRelease() {
for _, s := range WideSessions {
if s.Updated.Before(threshold) {
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.UserId)
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username)
WideSessions.Remove(s.ID)
}
@ -124,7 +121,7 @@ func FixedTimeRelease() {
// Online user statistic report.
type userReport struct {
userId string
username string
sessionCnt int
processCnt int
updated time.Time
@ -132,14 +129,14 @@ type userReport struct {
// report returns a online user statistics in pretty format.
func (u *userReport) report() string {
return "[" + u.userId + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
}
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
func FixedTimeReport() {
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
for _ = range time.Tick(10 * time.Minute) {
users := userReports{}
@ -149,7 +146,7 @@ func FixedTimeReport() {
processCnt := len(s.Processes)
processSum += processCnt
if report, exists := contains(users, s.UserId); exists {
if report, exists := contains(users, s.Username); exists {
if s.Updated.After(report.updated) {
report.updated = s.Updated
}
@ -157,7 +154,7 @@ func FixedTimeReport() {
report.sessionCnt++
report.processCnt += processCnt
} else {
users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
}
}
@ -176,9 +173,9 @@ func FixedTimeReport() {
}()
}
func contains(reports []*userReport, userId string) (*userReport, bool) {
func contains(reports []*userReport, username string) (*userReport, bool) {
for _, ur := range reports {
if userId == ur.userId {
if username == ur.username {
return ur, true
}
}
@ -222,7 +219,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
return
@ -233,7 +230,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.UserId)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
}
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
@ -265,7 +262,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
for {
if err := wsChan.ReadJSON(&input); err != nil {
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.UserId)
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.Username)
return
}
@ -284,8 +281,8 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
// SaveContentHandler handles request of session content string.
func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
args := struct {
Sid string
@ -294,14 +291,14 @@ func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
wSession := WideSessions.Get(args.Sid)
if nil == wSession {
result.Code = -1
result.Succ = false
return
}
@ -309,7 +306,7 @@ func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
wSession.Content = args.LatestSessionContent
for _, user := range conf.Users {
if user.Id == wSession.UserId {
if user.Name == wSession.Username {
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content
@ -378,9 +375,9 @@ func (sessions *wSessions) Remove(sid string) {
// kill processes
for _, p := range s.Processes {
if err := p.Kill(); nil != err {
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
} else {
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
}
}
@ -412,12 +409,12 @@ func (sessions *wSessions) Remove(sid string) {
cnt := 0 // count wide sessions associated with HTTP session
for _, ses := range *sessions {
if ses.UserId == s.UserId {
if ses.Username == s.Username {
cnt++
}
}
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt)
return
}
@ -425,14 +422,14 @@ func (sessions *wSessions) Remove(sid string) {
}
// GetByUsername gets wide sessions.
func (sessions *wSessions) GetByUserId(userId string) []*WideSession {
func (sessions *wSessions) GetByUsername(username string) []*WideSession {
mutex.Lock()
defer mutex.Unlock()
ret := []*WideSession{}
for _, s := range *sessions {
if s.UserId == userId {
if s.Username == username {
ret = append(ret, s)
}
}
@ -445,12 +442,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
mutex.Lock()
defer mutex.Unlock()
uid := httpSession.Values["uid"].(string)
username := httpSession.Values["username"].(string)
now := time.Now()
ret := &WideSession{
ID: sid,
UserId: uid,
Username: username,
HTTPSession: httpSession,
EventQueue: nil,
State: sessionStateActive,
@ -461,7 +458,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
*sessions = append(*sessions, ret)
if "playground" == uid {
if "playground" == username {
return ret
}
@ -477,12 +474,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
}
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
for {
ch := SessionWS[sid]
if nil == ch {
return // release this goroutine
return // release this gorutine
}
select {
@ -492,7 +489,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
ch = SessionWS[sid]
if nil == ch {
return // release this goroutine
return // release this gorutine
}
logger.Trace(event)
@ -500,7 +497,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
if event.Op&fsnotify.Create == fsnotify.Create {
fileType := "f"
if gulu.File.IsDir(path) {
if util.File.IsDir(path) {
fileType = "d"
if err = watcher.Add(path); nil != err {
@ -508,13 +505,17 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
}
}
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
cmd := map[string]interface{}{"path": path, "dir": dir,
"cmd": "create-file", "type": fileType}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
cmd := map[string]interface{}{"path": path, "dir": dir,
"cmd": "remove-file", "type": ""}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
cmd := map[string]interface{}{"path": path, "dir": dir,
"cmd": "rename-file", "type": ""}
ch.WriteJSON(&cmd)
}
case err := <-watcher.Errors:
@ -526,12 +527,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
}()
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
workspaces := filepath.SplitList(conf.GetUserWorkspace(username))
for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
if ".git" == f.Name() { // XXX: discard other unconcered dirs
return filepath.SkipDir
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, b3log.org
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
// 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,
@ -15,26 +15,33 @@
package session
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"math/rand"
"net/http"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"text/template"
"time"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/util"
)
const (
// TODO: i18n
userExists = "user exists"
userCreated = "user created"
userCreateError = "user create error"
userExists = "user exists"
emailExists = "email exists"
userCreated = "user created"
userCreateError = "user create error"
notAllowRegister = "not allow register"
)
// Exclusive lock for adding user.
@ -42,82 +49,69 @@ var addUserMutex sync.Mutex
// PreferenceHandler handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
uid := httpSession.Values["uid"].(string)
user := conf.GetUser(uid)
username := httpSession.Values["username"].(string)
user := conf.GetUser(username)
if "GET" == r.Method {
tmpLinux := user.GoBuildArgsForLinux
tmpWindows := user.GoBuildArgsForWindows
tmpDarwin := user.GoBuildArgsForDarwin
user.GoBuildArgsForLinux = strings.Replace(user.GoBuildArgsForLinux, `"`, `&quot;`, -1)
user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `&quot;`, -1)
user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `&quot;`, -1)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
"locales": i18n.GetLocalesNames(), "gofmts": gulu.Go.GetGoFormats(),
"locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats(),
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
t, err := template.ParseFiles("views/preference.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), 500)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return
}
t.Execute(w, model)
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
return
}
// non-GET request as save request
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
result := util.NewResult()
defer util.RetResult(w, r, result)
args := struct {
FontFamily string
FontSize string
GoFmt string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
Keymap string
Workspace string
Username string
Locale string
Theme string
EditorFontFamily string
EditorFontSize string
EditorLineHeight string
EditorTheme string
EditorTabSize string
FontFamily string
FontSize string
GoFmt string
Keymap string
Workspace string
Username string
Password string
Email string
Locale string
Theme string
EditorFontFamily string
EditorFontSize string
EditorLineHeight string
EditorTheme string
EditorTabSize string
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
result.Succ = false
return
}
@ -125,12 +119,17 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.FontFamily = args.FontFamily
user.FontSize = args.FontSize
user.GoFormat = args.GoFmt
user.GoBuildArgsForLinux = args.GoBuildArgsForLinux
user.GoBuildArgsForWindows = args.GoBuildArgsForWindows
user.GoBuildArgsForDarwin = args.GoBuildArgsForDarwin
user.Keymap = args.Keymap
// XXX: disallow change workspace at present
// user.Workspace = args.Workspace
if user.Password != args.Password {
user.Password = conf.Salt(args.Password, user.Salt)
}
user.Email = args.Email
hash := md5.New()
hash.Write([]byte(user.Email))
user.Gravatar = hex.EncodeToString(hash.Sum(nil))
user.Locale = args.Locale
user.Theme = args.Theme
@ -140,17 +139,144 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.Editor.Theme = args.EditorTheme
user.Editor.TabSize = args.EditorTabSize
conf.UpdateCustomizedConf(uid)
conf.UpdateCustomizedConf(username)
now := time.Now().UnixNano()
user.Lived = now
user.Updated = now
if user.Save() {
result.Code = 0
} else {
result.Code = -1
result.Succ = user.Save()
}
// LoginHandler handles request of user login.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the login page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()}
t, err := template.ParseFiles("views/login.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as login request
result := util.NewResult()
defer util.RetResult(w, r, result)
args := struct {
Username string
Password string
}{}
args.Username = r.FormValue("username")
args.Password = r.FormValue("password")
result.Succ = false
for _, user := range conf.Users {
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
result.Succ = true
break
}
}
if !result.Succ {
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// SignUpUserHandler handles request of registering user.
func SignUpUserHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": conf.Wide.UsersWorkspaces,
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
t, err := template.ParseFiles("views/sign_up.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as add user request
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
username := args["username"].(string)
password := args["password"].(string)
email := args["email"].(string)
msg := addUser(username, password, email)
if userCreated != msg {
result.Succ = false
result.Msg = msg
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
}
// FixedTimeSave saves online users' configurations periodically (1 minute).
@ -158,19 +284,25 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
func FixedTimeSave() {
go func() {
defer gulu.Panic.Recover(nil)
defer util.Recover()
for _ = range time.Tick(time.Minute) {
SaveOnlineUsers()
users := getOnlineUsers()
for _, u := range users {
if u.Save() {
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
}
}()
}
// CanAccess determines whether the user specified by the given user id can access the specified path.
func CanAccess(userId, path string) bool {
// CanAccess determines whether the user specified by the given username can access the specified path.
func CanAccess(username, path string) bool {
path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(userId)
userWorkspace := conf.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces {
@ -182,33 +314,23 @@ func CanAccess(userId, path string) bool {
return false
}
// SaveOnlineUsers saves online users' configurations at once.
func SaveOnlineUsers() {
users := getOnlineUsers()
for _, u := range users {
if u.Save() {
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
}
func getOnlineUsers() []*conf.User {
ret := []*conf.User{}
uids := map[string]string{} // distinct uid
usernames := map[string]string{} // distinct username
for _, s := range WideSessions {
uids[s.UserId] = s.UserId
usernames[s.Username] = s.Username
}
for _, uid := range uids {
u := conf.GetUser(uid)
for _, username := range usernames {
u := conf.GetUser(username)
if "playground" == uid { // user [playground] is a reserved mock user
if "playground" == username { // user [playground] is a reserved mock user
continue
}
if nil == u {
logger.Warnf("Not found user [%s]", uid)
logger.Warnf("Not found user [%s]", username)
continue
}
@ -218,3 +340,135 @@ func getOnlineUsers() []*conf.User {
return ret
}
// addUser add a user with the specified username, password and email.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(username, password, email string) string {
if !conf.Wide.AllowRegister {
return notAllowRegister
}
if "playground" == username {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Name) == strings.ToLower(username) {
return userExists
}
if strings.ToLower(user.Email) == strings.ToLower(email) {
return emailExists
}
}
workspace := filepath.Join(conf.Wide.UsersWorkspaces, username)
newUser := conf.NewUser(username, password, email, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
logger.Infof("Created a user [%s]", username)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}

189
shell/shells.go Normal file
View File

@ -0,0 +1,189 @@
// Copyright (c) 2014-2016, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shell include shell related mainipulations.
package shell
import (
"html/template"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
// Shell channel.
//
// <sid, *util.WSChannel>>
var ShellWS = map[string]*util.WSChannel{}
// Logger.
var logger = log.NewLogger(os.Stdout)
// IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"sid": session.WideSessions.GenId()}
wideSessions := session.WideSessions.GetByUsername(username)
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/shell.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
}
// WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
sid := r.URL.Query()["sid"][0]
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
ShellWS[sid] = &wsChan
logger.Debugf("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
input := map[string]interface{}{}
for {
if err := wsChan.ReadJSON(&input); err != nil {
logger.Error("Shell WS ERROR: " + err.Error())
return
}
inputCmd := input["cmd"].(string)
cmds := strings.Split(inputCmd, "|")
commands := []*exec.Cmd{}
for _, cmdWithArgs := range cmds {
cmdWithArgs = strings.TrimSpace(cmdWithArgs)
cmdWithArgs := strings.Split(cmdWithArgs, " ")
args := []string{}
if len(cmdWithArgs) > 1 {
args = cmdWithArgs[1:]
}
cmd := exec.Command(cmdWithArgs[0], args...)
commands = append(commands, cmd)
}
output := ""
if !strings.Contains(inputCmd, "clear") {
output = pipeCommands(username, commands...)
}
ret = map[string]interface{}{"output": output, "cmd": "shell-output"}
if err := wsChan.WriteJSON(&ret); err != nil {
logger.Error("Shell WS ERROR: " + err.Error())
return
}
wsChan.Refresh()
}
}
func pipeCommands(username string, commands ...*exec.Cmd) string {
for i, command := range commands[:len(commands)-1] {
setCmdEnv(command, username)
stdout, err := command.StdoutPipe()
if nil != err {
return err.Error()
}
command.Start()
commands[i+1].Stdin = stdout
}
last := commands[len(commands)-1]
setCmdEnv(last, username)
out, err := last.CombinedOutput()
// release resources
for _, command := range commands[:len(commands)-1] {
command.Wait()
}
if err != nil {
return err.Error()
}
return string(out)
}
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.GetUserWorkspace(username)
cmd.Env = append(cmd.Env,
"TERM="+os.Getenv("TERM"),
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(),
"PATH="+os.Getenv("PATH"))
cmd.Dir = userWorkspace
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -18,7 +18,7 @@
* themes for base.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
* @version 0.1.0.0, Dec 6, 2015
*/
/* start reset & function */
::-webkit-scrollbar {
@ -140,24 +140,18 @@ button {
/* start icon */
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?lqk80d');
src: url('fonts/icomoon.eot?lqk80d#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?lqk80d') format('truetype'),
url('fonts/icomoon.woff?lqk80d') format('woff'),
url('fonts/icomoon.svg?lqk80d#icomoon') format('svg');
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;
}
[class^="ico-"], [class*=" ico-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
.font-ico,
[class^="ico-"] {
font-family: 'icomoon';
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@ -166,140 +160,177 @@ button {
line-height: 20px;
}
.ico-qqz:before {
content: "\e900";
}
.ico-find:before {
content: "\e602";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-editor:before {
content: "\e604";
}
.ico-notification:before {
content: "\e607";
}
.ico-price:before {
content: "\e616";
}
.ico-report:before {
content: "\e605";
}
.ico-git:before {
content: "\e624";
}
.ico-book:before {
content: "\e623";
}
.ico-price:before {
content: "\e616";
}
.ico-start:before {
content: "\e9d7";
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
}
.ico-tree:before {
content: "\e600";
.ico-share:before {
content: "\e61f";
}
.ico-build:before {
content: "\e601";
.ico-github:before {
content: "\f00a";
}
.ico-export:before {
content: "\f0ed";
.ico-git:before {
content: "\e624";
}
.ico-import:before {
content: "\f0ee";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
.ico-tencent:before {
content: "\e622";
}
.ico-weibo:before {
content: "\e621";
}
.ico-uniE608:before {
content: "\e608";
.ico-googleplus:before {
content: "\e61a";
}
.ico-max:before {
content: "\e609";
.ico-twitter:before {
content: "\e61c";
}
.ico-remove:before {
content: "\e60b";
.ico-email:before {
content: "\e619";
}
.ico-buildrun:before {
content: "\e60c";
.ico-facebook:before {
content: "\e61b";
}
.ico-about:before {
content: "\e60d";
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-find:before {
content: "\e602";
}
.ico-editor:before {
content: "\e604";
}
.ico-tree:before {
content: "\e600";
}
.ico-build:before {
content: "\e601";
}
.ico-notification:before {
content: "\e607";
}
.ico-report:before {
content: "\e605";
}
.ico-comment:before {
content: "\e620";
}
.ico-goline:before {
content: "\e61e";
}
.ico-info:before {
content: "\e61d";
}
.ico-signup:before {
content: "\e606";
}
.ico-signout:before {
content: "\e618";
}
.ico-redo:before {
content: "\e615";
}
.ico-undo:before {
content: "\e60e";
}
.ico-stop:before {
content: "\e60f";
.ico-about:before {
content: "\e60d";
}
.ico-close:before {
content: "\e611";
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
.ico-import:before {
content: "\f0ee";
}
.ico-export:before {
content: "\f0ed";
}
.ico-refresh:before {
content: "\f021";
}
.ico-remove:before {
content: "\e60b";
}
.ico-save:before {
content: "\f0c7";
}
.ico-max:before {
content: "\e609";
}
.ico-format:before {
content: "\e612";
}
.ico-buildrun:before {
content: "\e60c";
}
.ico-stop:before {
content: "\e60f";
}
.ico-restore:before {
content: "\e613";
}
.toolbars .ico-restore:before {
content: "\e60a";
}
.ico-min:before {
content: "\e614";
position: absolute;
right: 5px;
}
.ico-redo:before {
content: "\e615";
}
.ico-uniE617:before {
content: "\e617";
}
.ico-signout:before {
content: "\e618";
}
.ico-email:before {
content: "\e619";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-info:before {
content: "\e61d";
}
.ico-goline:before {
content: "\e61e";
}
.ico-share:before {
content: "\e61f";
}
.ico-comment:before {
content: "\e620";
}
.ico-github:before {
content: "\f00a";
}
.ico-refresh:before {
content: "\f021";
}
.ico-save:before {
content: "\f0c7";
.ico-close:before {
content: "\e611";
}
/* end ico */

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -17,7 +17,7 @@
/**
* dialig style
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="mailto:LLY219@gmail.com">Liyuan Li</a>
* @version 0.0.0.6, Jun 3, 2012
*/

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

Binary file not shown.

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

@ -6,50 +6,52 @@
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe600;" glyph-name="tree" horiz-adv-x="1170" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" />
<glyph unicode="&#xe601;" glyph-name="build" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" />
<glyph unicode="&#xe602;" glyph-name="find" d="M632.576 661.376c46.080-10.666 82.752-49.92 93.418-92.8 3.648-20.054 21.12-35.2 42.048-35.2 23.466 0 42.666 19.2 42.666 42.666 0 30.72-25.814 77.44-61.014 112.426-34.56 34.134-75.734 58.24-109.654 58.24-23.466 0-42.666-19.84-42.666-43.306 0.002-20.884 15.148-38.4 35.202-42.026zM46.122 102.016c-33.066-33.046-33.066-86.826 0-119.872 33.066-33.088 86.826-33.088 119.68 0l245.098 244.906c57.194-35.67 124.586-56.342 197.12-56.342 206.294 0 373.334 167.040 373.334 373.334s-167.040 373.334-373.334 373.334-373.334-167.040-373.334-373.334c0-72.32 20.714-139.946 56.342-197.12l-244.906-244.906zM341.376 544.042c0 147.2 119.488 266.666 266.666 266.666 147.2 0 266.666-119.466 266.666-266.666s-119.466-266.666-266.666-266.666c-147.178 0.022-266.666 119.488-266.666 266.666z" />
<glyph unicode="&#xe603;" glyph-name="findfiles" d="M0 21.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 341.332h128v-341.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 554.668c0 47.146-38.186 85.334-85.334 85.334h-42.666v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-256v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-42.666c-47.146 0-85.334-38.186-85.334-85.334v-554.666zM554.666 469.334h-85.334c-23.466 0-42.666 19.2-42.666 42.666s19.2 42.666 42.666 42.666h85.334c23.466 0 42.666-19.2 42.666-42.666s-19.198-42.666-42.666-42.666z" />
<glyph unicode="&#xe604;" glyph-name="editor" d="M118.634 528.918c-20.886-20.886-30.934-50.774-36.032-81.066l-81.322-384.428c-11.52-68.288 56.96-136.982 126.102-126.314l384.874 80.234c28.16 5.098 58.666 13.866 79.574 34.774l407.68 408.128c32.64 32.832 32.64 85.952 0 118.806l-356.054 356.48c-32.854 32.64-85.974 32.64-118.634 0l-406.188-406.614zM509.888 118.678l-199.254-38.208-165.12 165.334 37.952 199.488 296.768 297.174c16.426 16.448 42.88 16.448 59.306 0 16.448-16.426 16.448-43.094 0-59.52l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.218 20.48-20.502 53.546-20.502 74.026 0l274.582 274.774c16.448 16.426 42.88 16.426 59.306 0 16.448-16.448 16.448-42.902 0-59.306l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.24 20.48-20.48 53.568-20.48 74.026 0l274.582 274.774c16.234 16.234 42.902 16.234 59.328 0 16.426-16.426 16.426-43.094 0-59.498l-296.766-297.006z" />
<glyph unicode="&#xe605;" glyph-name="report" d="M622.858 799.808l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" />
<glyph unicode="&#xe606;" glyph-name="signup" d="M697.83 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 401.542c-108.479 0-325.203-54.355-325.203-162.601v-116.144h650.404v116.144c0 108.245-216.724 162.601-325.203 162.601zM697.83 401.542c-13.472 0-28.571-0.929-44.831-2.555 53.89-38.792 91.289-91.057 91.289-160.047v-116.144h278.744v116.144c0 108.245-216.724 162.601-325.203 162.601z" />
<glyph unicode="&#xe607;" glyph-name="notification" d="M977.76 152.992l-400.608 685.952c-33.408 33.408-87.616 33.408-121.024 0l-400.64-685.952c-33.408-33.376-33.408-87.552 0-120.992h922.24c33.472 33.44 33.472 87.616 0.032 120.992zM479.744 592.704c0 26.528 21.504 48 48 48s48-21.472 48-48v-224c0-26.496-21.504-48-48-48s-48 21.504-48 48v224zM528.032 160.448c-26.496 0-48 21.44-48 48 0 26.496 21.504 48 48 48s48-21.504 48-48c0-26.56-21.504-48-48-48z" />
<glyph unicode="&#xe608;" glyph-name="uniE608" horiz-adv-x="952" d="M438.928 896.072q14.856 0 25.714-10.856t10.858-25.714v-128h168l98.856 98.856q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714-10.856-25.714l-98.856-98.856v-482.286l98.856-98.856q10.856-10.856 10.856-25.714t-10.856-25.714-25.714-10.856-25.714 10.856l-98.856 98.856h-168v-128q0-14.856-10.856-25.714t-25.714-10.856-25.714 10.856-10.856 25.714v128q-97.714 0-165.714 38.286l-119.43-118.856q-10.856-10.856-25.714-10.856t-25.714 10.856q-10.856 10.286-10.856 25.714t10.856 25.714l112.57 113.144q-2.856 2.856-7.43 8.572t-16.286 24-20.856 37.144-16.57 46.856-7.43 55.428h512v73.144h-512q0 29.144 7.714 58t18.856 49.714 22.286 37.714 18.57 24.856l8 8.572-118.286 104.57q-12 11.43-12 27.43 0 13.714 9.144 24.57 10.286 10.858 25.43 11.714t26.57-8.856l129.714-115.428q65.144 33.142 156.57 33.142v128q0 14.858 10.858 25.714t25.714 10.856zM768.072 621.786q76 0 129.428-53.428t53.428-129.428-53.428-129.428-129.428-53.428v365.714z" />
<glyph unicode="&#xe609;" glyph-name="max" d="M1024 960v-384l-138.26 138.26-212-212-107.48 107.48 212 212-138.26 138.26zM245.74 821.74l212-212-107.48-107.48-212 212-138.26-138.26v384h384zM885.74 181.74l138.26 138.26v-384h-384l138.26 138.26-212 212 107.48 107.48zM457.74 286.26l-212-212 138.26-138.26h-384v384l138.26-138.26 212 212z" />
<glyph unicode="&#xe60a;" glyph-name="uniE60A" d="M64 384h384v-384l-138.26 138.26-202-202-107.48 107.48 202 202zM821.74 245.74l202-202-107.48-107.48-202 202-138.26-138.26v384h384zM960 512h-384v384l138.26-138.26 202 202 107.48-107.48-202-202zM309.74 757.74l138.26 138.26v-384h-384l138.26 138.26-202 202 107.48 107.48z" />
<glyph unicode="&#xe60b;" glyph-name="remove" d="M800 832h-576c-53.020 0-96-42.98-96-96v-32h768v32c0 53.020-42.98 96-96 96zM632.32 896l14.116-101h-268.872l14.114 101h240.642zM640 960h-256c-26.4 0-50.99-21.392-54.642-47.538l-18.714-133.924c-3.654-26.146 14.956-47.538 41.356-47.538h320c26.4 0 45.010 21.392 41.358 47.538l-18.714 133.924c-3.654 26.146-28.244 47.538-54.644 47.538v0zM816 640h-608c-35.2 0-61.392-28.682-58.206-63.738l52.412-576.526c3.186-35.054 34.594-63.736 69.794-63.736h480c35.2 0 66.608 28.682 69.794 63.736l52.41 576.526c3.188 35.056-23.004 63.738-58.204 63.738zM384 64h-96l-32 448h128v-448zM576 64h-128v448h128v-448zM736 64h-96v448h128l-32-448z" />
<glyph unicode="&#xe60c;" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe60d;" glyph-name="about" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" />
<glyph unicode="&#xe60e;" glyph-name="undo" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
<glyph unicode="&#xe60f;" glyph-name="stop" d="M128 832h768v-768h-768z" />
<glyph unicode="&#xe611;" glyph-name="close" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" />
<glyph unicode="&#xe612;" glyph-name="format" d="M448 165.49l-205.254 237.254 58.508 58.51 146.746-114.744 274.744 242.744 58.512-58.508zM831.772 832c0.078-0.066 0.162-0.15 0.228-0.23v-767.542c-0.066-0.078-0.15-0.162-0.228-0.228h-639.544c-0.080 0.066-0.162 0.15-0.228 0.228v767.544c0.066 0.080 0.15 0.162 0.23 0.228h-128.23v-768c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v768h-128.228zM640 832v64c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-64h-128v-128h512v128h-128zM576 832h-128v64h128v-64z" />
<glyph unicode="&#xe613;" glyph-name="restore" d="M128 682.666v128h896v-640h-128v-128h-896v640h128zM896 256h42.668v469.334h-725.332v-42.666h682.668v-426.668zM85.332 128h725.332v469.334h-725.332v-469.334z" />
<glyph unicode="&#xe614;" glyph-name="min" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
<glyph unicode="&#xe615;" glyph-name="redo" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
<glyph unicode="&#xe616;" glyph-name="price" d="M501.443 506.062c-119.819 31.143-158.353 63.078-158.353 113.223 0 57.535 53.047 97.916 142.518 97.916 93.957 0 128.794-44.867 131.961-110.848h116.654c-3.431 91.053-59.119 173.925-169.439 201.109v115.599h-158.353v-114.015c-102.402-22.433-184.746-88.413-184.746-190.553 0-121.933 101.082-182.636 248.086-217.999 132.225-31.671 158.353-77.856 158.353-127.474 0-36.159-25.601-94.219-142.518-94.219-108.736 0-151.755 48.826-157.297 110.848h-116.389c6.597-115.599 92.901-180.26 194.511-202.165v-114.544h158.353v113.487c102.667 19.794 184.746 79.178 184.746 187.65 0 149.38-128.266 200.581-248.086 231.987z" />
<glyph unicode="&#xe617;" glyph-name="uniE617" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
<glyph unicode="&#xe618;" glyph-name="signout" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
<glyph unicode="&#xe619;" glyph-name="email" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM256 704h512c9.138 0 18.004-1.962 26.144-5.662l-282.144-329.168-282.144 329.17c8.14 3.696 17.006 5.66 26.144 5.66zM192 256v384c0 1.34 0.056 2.672 0.14 4l187.664-218.942-185.598-185.598c-1.444 5.336-2.206 10.886-2.206 16.54zM768 192h-512c-5.654 0-11.202 0.762-16.54 2.208l182.118 182.118 90.422-105.498 90.424 105.494 182.116-182.12c-5.34-1.44-10.886-2.202-16.54-2.202zM832 256c0-5.654-0.762-11.2-2.206-16.54l-185.6 185.598 187.666 218.942c0.084-1.328 0.14-2.66 0.14-4v-384z" />
<glyph unicode="&#xe61a;" glyph-name="googleplus" d="M437.006 141.838c0-75.068-46.39-134.392-177.758-139.176-76.984 43.786-141.49 106.952-186.908 182.866 23.69 58.496 97.692 103.046 182.316 102.114 24.022-0.252 46.41-4.114 66.744-10.7 55.908-38.866 101-63.152 112.324-107.448 2.114-8.964 3.282-18.206 3.282-27.656zM512 960c-147.94 0-281.196-62.77-374.666-163.098 36.934 20.452 80.538 32.638 126.902 32.638 67.068 0 256.438 0 256.438 0l-57.304-60.14h-67.31c47.496-27.212 72.752-83.248 72.752-145.012 0-56.692-31.416-102.38-75.78-137.058-43.28-33.802-51.492-47.966-51.492-76.734 0-24.542 51.722-61.098 75.5-78.936 82.818-62.112 99.578-101.184 99.578-178.87 0-78.726-68.936-157.104-185.866-183.742 56.348-21.338 117.426-33.048 181.248-33.048 282.77 0 512 229.23 512 512s-229.23 512-512 512zM768 576v-128h-64v128h-128v64h128v128h64v-128h128v-64h-128zM365.768 620.528c11.922-90.776-27.846-149.19-96.934-147.134-69.126 2.082-134.806 65.492-146.74 156.242-11.928 90.788 34.418 160.254 103.53 158.196 69.090-2.074 128.22-76.542 140.144-167.304zM220.886 317.932c-74.68 0-138.128-25.768-182.842-63.864-24.502 59.82-38.044 125.29-38.044 193.932 0 56.766 9.256 111.368 26.312 162.396 7.374-99.442 77.352-176.192 192.97-176.192 8.514 0 16.764 0.442 24.874 1.022-7.95-15.23-13.622-32.19-13.622-49.982 0-29.97 16.488-47.070 36.868-66.894-15.402 0-30.27-0.418-46.516-0.418z" />
<glyph unicode="&#xe61b;" glyph-name="facebook" d="M512 960c282.77 0 512-229.23 512-512 0-261.094-195.438-476.53-448-508.026v380.026h176l16 128h-192v64c0 35.346 28.654 64 64 64h128v128h-128c-106.040 0-192-85.96-192-192v-64h-96v-128h96v-380.026c-252.562 31.496-448 246.932-448 508.026 0 282.77 229.23 512 512 512z" />
<glyph unicode="&#xe61c;" glyph-name="twitter" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM806.242 598.912c0.292-6.508 0.442-13.056 0.442-19.638 0-200.622-152.708-431.964-431.958-431.964-85.736 0-165.536 25.136-232.726 68.214 11.876-1.408 23.962-2.126 36.216-2.126 71.13 0 136.59 24.276 188.55 64.994-66.434 1.22-122.5 45.122-141.824 105.432 9.274-1.768 18.784-2.722 28.566-2.722 13.846 0 27.258 1.856 39.998 5.324-69.452 13.952-121.786 75.312-121.786 148.868 0 0.64 0 1.278 0.016 1.91 20.47-11.37 43.878-18.2 68.764-18.988-40.74 27.222-67.54 73.692-67.54 126.368 0 27.822 7.488 53.904 20.556 76.324 74.878-91.854 186.748-152.292 312.924-158.628-2.588 11.118-3.93 22.702-3.93 34.604 0 83.84 67.98 151.812 151.818 151.812 43.666 0 83.124-18.436 110.818-47.94 34.58 6.808 67.074 19.442 96.412 36.84-11.336-35.454-35.41-65.206-66.752-83.994 30.71 3.668 59.968 11.832 87.194 23.904-20.35-30.448-46.090-57.186-75.758-78.594z" />
<glyph unicode="&#xe61d;" glyph-name="info" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" />
<glyph unicode="&#xe61e;" glyph-name="goline" d="M384 128h640v-128h-640zM384 512h640v-128h-640zM384 896h640v-128h-640zM192 960v-256h-64v192h-64v64zM128 434v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM256 256v-320h-192v64h128v64h-128v64h128v64h-128v64z" />
<glyph unicode="&#xe61f;" glyph-name="share" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z" />
<glyph unicode="&#xe620;" glyph-name="comment" d="M0 343.808l321.344-206.624v149.152l-191.328 118.144 191.328 118.144v150.176l-321.344-207.68v-121.312zM389.312 64h110.592l162.048 768h-111.328l-161.312-768zM702.688 672.8v-150.176l191.264-118.144-191.264-118.112v-149.152l321.312 206.592v121.312l-321.312 207.68z" />
<glyph unicode="&#xe621;" glyph-name="weibo" d="M428.222 493.19c-144.194-6.656-260.742-83.888-260.742-180.224 0-96.202 116.574-168.528 260.742-161.792 144.302 6.576 261.094 96.876 261.094 193.078 0 96.12-116.79 155.594-261.094 148.938zM526.472 250.88c-44.168-56.994-131.53-84.802-216.36-38.858-40.394 21.908-38.884 64.916-38.884 64.916s-16.76 135.842 128.27 152.818c145.192 16.816 171.116-121.856 126.976-178.876zM429.19 341.854c-9.298-6.736-11.182-19.618-6.144-27.62 4.85-8.22 16.142-9.162 25.276-2.318 8.974 7.086 12.45 19.428 7.572 27.62-4.798 7.976-15.952 10.294-26.704 2.318zM360.448 323.154c-27.108-2.802-46.484-26.408-46.484-48.99 0-22.636 21.826-38.264 48.882-35.086 26.974 3.072 48.908 23.928 48.908 46.484 0.054 22.636-20.184 40.582-51.308 37.592zM868.082 960h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22c0 86.096-69.768 155.89-155.89 155.89zM818.202 280.386c-59.446-126.274-255.462-187.716-400.734-176.344-138.050 10.86-315.526 56.724-333.878 223.798 0 0-9.7 75.668 63.65 173.568 0 0 105.498 147.322 228.378 189.36 122.988 41.85 137.35-28.968 137.35-70.818-6.548-35.49-18.782-56.374 27.38-42.038 0 0 120.886 56.076 170.658 6.332 40.126-40.152 6.63-95.42 6.63-95.42s-16.654-18.404 17.624-25.034c34.358-6.872 142.362-56.914 82.944-183.404zM698.988 629.274c-13.15 0-23.714 10.644-23.714 23.688 0 13.286 10.562 23.956 23.714 23.956 0 0 148.212 27.404 130.48-131.852 0-0.942-0.108-1.698-0.322-2.534-1.672-11.29-11.586-19.94-23.256-19.94-13.204 0-23.956 10.562-23.956 23.74 0-0.026 23.498 106.416-82.944 82.944zM949.518 501.894h-0.216c-3.908-26.948-17.274-29.104-33.198-29.104-19.052 0-34.438 11.964-34.438 31.044 0 16.52 6.846 33.308 6.846 33.308 2.020 6.952 18.136 50.176-10.644 114.796-52.708 88.522-158.856 89.816-171.384 84.776-12.638-4.958-31.286-7.436-31.286-7.436-19.188 0-34.548 15.602-34.548 34.572 0 15.926 10.644 29.4 25.196 33.524 0 0 0.322 0.538 0.808 0.62 1.052 0.216 2.13 1.268 3.26 1.374 14.794 2.83 67.476 13.178 118.702 1.186 91.648-21.396 217.52-110.026 160.904-298.658z" />
<glyph unicode="&#xe623;" glyph-name="book" d="M512 768c0 0-128 128-512 128v-768c388 0 512-128 512-128s124 128 512 128v768c-384 0-512-128-512-128zM128 768c162.688-13.632 262.496-51.264 320-81.76v-515.488c-57.504 30.368-157.312 68-320 81.76v515.488zM896 252.512c-162.752-13.76-262.496-51.328-320-81.76v515.488c57.504 30.496 157.248 68.128 320 81.76v-515.488z" />
<glyph unicode="&#xe624;" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
<glyph unicode="&#xe900;" glyph-name="qqz" d="M761.562 521.546l-305.314-229.928s122.534-19.085 307.335-16.875l-8.413 38.021 263.313 239.505c4.84 4.404 6.694 11.413 4.708 17.804-1.974 6.394-7.405 10.93-13.783 11.494l-347.126 31.289-135.62 336.932c-2.479 6.184-8.268 10.213-14.657 10.213-6.397 0-12.184-4.029-14.665-10.213l-135.621-336.932-347.128-31.289c-6.374-0.563-11.801-5.1-13.781-11.494-1.987-6.392-0.13-13.401 4.713-17.804l263.31-239.505-78.925-356.295c-1.445-6.521 0.962-13.347 6.142-17.317 5.196-3.9 12.107-4.202 17.608-0.752l298.345 188.885 298.347-188.885c2.554-1.655 5.395-2.401 8.235-2.401 3.299 0 6.583 1.049 9.366 3.146 5.182 3.98 7.593 10.803 6.149 17.325l-62.412 281.762c26.77 14.131 56.254 38.051 56.254 38.051s-116.838-59.919-536.289-30.65l303.994 231.311s-11.952 19.514-392.923 33.442c-25.494 0.938 310.6 66.909 558.84 11.159z" />
<glyph unicode="&#xe9d7;" glyph-name="start" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xf00a;" glyph-name="github" d="M512 960c-282.75 0-512-229.25-512-512 0-226.25 146.688-418.126 350.156-485.812 25.594-4.688 34.938 11.126 34.938 24.626 0 12.188-0.468 52.562-0.718 95.312-142.376-30.938-172.468 60.376-172.468 60.376-23.312 59.126-56.844 74.876-56.844 74.876-46.532 31.75 3.53 31.126 3.53 31.126 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.876-55.626 149-42.5 4.654 33 17.904 55.626 32.5 68.376-113.656 12.938-233.218 56.876-233.218 253.062 0 55.938 19.968 101.562 52.656 137.406-5.218 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.032 128.126 17.22 43.5-0.188 87.312-5.876 128.188-17.28 97.688 66.312 140.688 52.5 140.688 52.5 28-70.532 10.376-122.562 5.126-135.5 32.812-35.844 52.626-81.47 52.626-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.876 34.75-47 34.75-94.75 0-68.438-0.688-123.626-0.688-140.5 0-13.626 9.312-29.562 35.25-24.562 203.312 67.812 349.876 259.688 349.876 485.812 0 282.75-229.25 512-512 512z" />
<glyph unicode="&#xf021;" glyph-name="refresh" d="M863.428 356.572q0-2.856-0.572-4-36.572-153.144-153.144-248.286t-273.142-95.144q-83.43 0-161.43 31.428t-139.144 89.714l-73.714-73.714q-10.856-10.856-25.714-10.856t-25.714 10.856-10.856 25.714v256q0 14.856 10.856 25.714t25.714 10.856h256q14.856 0 25.714-10.856t10.858-25.714-10.856-25.714l-78.286-78.286q40.57-37.714 92-58.286t106.858-20.572q76.572 0 142.856 37.144t106.286 102.286q6.286 9.714 30.286 66.856 4.572 13.144 17.144 13.144h109.714q7.428 0 12.856-5.428t5.428-12.856zM877.714 813.714v-256q0-14.856-10.856-25.714t-25.714-10.856h-256q-14.856 0-25.714 10.856t-10.856 25.714 10.856 25.714l78.856 78.856q-84.572 78.286-199.428 78.286-76.572 0-142.856-37.142t-106.286-102.286q-6.286-9.714-30.286-66.858-4.57-13.144-17.144-13.144h-113.714q-7.43 0-12.856 5.428t-5.43 12.856v4q37.144 153.144 154.286 248.286t274.286 95.142q83.428 0 162.286-31.714t140-89.428l74.286 73.714q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714z" />
<glyph unicode="&#xf0c7;" glyph-name="save" d="M219.43 82.286h438.858v219.428h-438.858v-219.428zM731.428 82.286h73.144v512q0 8-5.714 22t-11.428 19.714l-160.572 160.57q-5.714 5.714-19.428 11.43t-22.286 5.714v-237.714q0-22.858-16-38.858t-38.856-16h-329.142q-22.856 0-38.856 16t-16 38.856v237.714h-73.144v-731.43h73.144v237.714q0 22.856 16 38.856t38.856 16h475.43q22.856 0 38.856-16t16-38.856v-237.714zM512 612.572v182.856q0 7.428-5.428 12.858t-12.856 5.428h-109.714q-7.428 0-12.858-5.428t-5.428-12.858v-182.856q0-7.428 5.428-12.856t12.856-5.428h109.714q7.428 0 12.858 5.428t5.428 12.856zM877.714 594.286v-530.286q0-22.856-16-38.856t-38.856-16h-768q-22.856 0-38.856 16t-16 38.856v768q0 22.856 16 38.856t38.856 16h530.286q22.856 0 50.286-11.428t43.428-27.428l160-160q16-16 27.428-43.428t11.428-50.286z" />
<glyph unicode="&#xf0ed;" glyph-name="export" horiz-adv-x="1097" d="M731.429 429.714q0 8-5.143 13.143t-13.143 5.143h-128v201.143q0 7.429-5.429 12.857t-12.857 5.429h-109.714q-7.429 0-12.857-5.429t-5.429-12.857v-201.143h-128q-7.429 0-12.857-5.429t-5.429-12.857q0-8 5.143-13.143l201.143-201.143q5.143-5.143 13.143-5.143t13.143 5.143l200.571 200.571q5.714 6.857 5.714 13.714zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf0ee;" glyph-name="import" horiz-adv-x="1097" d="M731.429 466.286q0 8-5.143 13.143l-201.143 201.143q-5.143 5.143-13.143 5.143t-13.143-5.143l-200.571-200.571q-5.714-6.857-5.714-13.714 0-8 5.143-13.143t13.143-5.143h128v-201.143q0-7.429 5.429-12.857t12.857-5.429h109.714q7.429 0 12.857 5.429t5.429 12.857v201.143h128q7.429 0 12.857 5.429t5.429 12.857zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf11c;" glyph-name="keyboard" horiz-adv-x="1097" d="M219.429 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM292.571 438.857v-54.857q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h128q9.143 0 9.143-9.143zM219.429 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 292.571v-54.857q0-9.143-9.143-9.143h-493.714q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h493.714q9.143 0 9.143-9.143zM438.857 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM365.714 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM585.143 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM512 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM731.429 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM658.286 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 585.143v-201.143q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h64v137.143q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM1024 155.429v512h-950.857v-512h950.857zM1097.143 667.429v-512q0-30.286-21.429-51.714t-51.714-21.429h-950.857q-30.286 0-51.714 21.429t-21.429 51.714v512q0 30.286 21.429 51.714t51.714 21.429h950.857q30.286 0 51.714-21.429t21.429-51.714z" />
<glyph unicode="&#xf148;" glyph-name="moveup" horiz-adv-x="585" d="M580.891 578.932q-10.269-21.109-33.090-21.109h-109.537v-492.917q0-7.987-5.135-13.122t-13.122-5.135h-401.637q-11.981 0-16.544 10.269-4.564 11.411 2.282 19.968l91.282 109.537q5.135 6.276 14.263 6.276h182.562v365.124h-109.537q-22.82 0-33.090 21.109-9.698 21.109 5.135 38.794l182.562 219.075q10.269 12.551 27.955 12.551t27.955-12.551l182.562-219.075q15.404-18.257 5.135-38.794z" />
<glyph unicode="&#xf149;" glyph-name="movedown" horiz-adv-x="585" d="M18.286 813.714h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" />
<glyph unicode="&#xe600;" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" horiz-adv-x="1170" />
<glyph unicode="&#xe601;" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" />
<glyph unicode="&#xe602;" d="M632.576 661.376c46.080-10.666 82.752-49.92 93.418-92.8 3.648-20.054 21.12-35.2 42.048-35.2 23.466 0 42.666 19.2 42.666 42.666 0 30.72-25.814 77.44-61.014 112.426-34.56 34.134-75.734 58.24-109.654 58.24-23.466 0-42.666-19.84-42.666-43.306 0.002-20.884 15.148-38.4 35.202-42.026zM46.122 102.016c-33.066-33.046-33.066-86.826 0-119.872 33.066-33.088 86.826-33.088 119.68 0l245.098 244.906c57.194-35.67 124.586-56.342 197.12-56.342 206.294 0 373.334 167.040 373.334 373.334s-167.040 373.334-373.334 373.334-373.334-167.040-373.334-373.334c0-72.32 20.714-139.946 56.342-197.12l-244.906-244.906zM341.376 544.042c0 147.2 119.488 266.666 266.666 266.666 147.2 0 266.666-119.466 266.666-266.666s-119.466-266.666-266.666-266.666c-147.178 0.022-266.666 119.488-266.666 266.666z" />
<glyph unicode="&#xe603;" d="M0 21.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 341.332h128v-341.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 554.668c0 47.146-38.186 85.334-85.334 85.334h-42.666v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-256v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-42.666c-47.146 0-85.334-38.186-85.334-85.334v-554.666zM554.666 469.334h-85.334c-23.466 0-42.666 19.2-42.666 42.666s19.2 42.666 42.666 42.666h85.334c23.466 0 42.666-19.2 42.666-42.666s-19.198-42.666-42.666-42.666z" />
<glyph unicode="&#xe604;" d="M118.634 528.918c-20.886-20.886-30.934-50.774-36.032-81.066l-81.322-384.428c-11.52-68.288 56.96-136.982 126.102-126.314l384.874 80.234c28.16 5.098 58.666 13.866 79.574 34.774l407.68 408.128c32.64 32.832 32.64 85.952 0 118.806l-356.054 356.48c-32.854 32.64-85.974 32.64-118.634 0l-406.188-406.614zM509.888 118.678l-199.254-38.208-165.12 165.334 37.952 199.488 296.768 297.174c16.426 16.448 42.88 16.448 59.306 0 16.448-16.426 16.448-43.094 0-59.52l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.218 20.48-20.502 53.546-20.502 74.026 0l274.582 274.774c16.448 16.426 42.88 16.426 59.306 0 16.448-16.448 16.448-42.902 0-59.306l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.24 20.48-20.48 53.568-20.48 74.026 0l274.582 274.774c16.234 16.234 42.902 16.234 59.328 0 16.426-16.426 16.426-43.094 0-59.498l-296.766-297.006z" />
<glyph unicode="&#xe605;" d="M622.858 778.475l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" />
<glyph unicode="&#xe606;" d="M697.83 473.125c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 473.125c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 380.209c-108.479 0-325.203-54.355-325.203-162.601v-116.144h650.404v116.144c0 108.245-216.724 162.601-325.203 162.601zM697.83 380.209c-13.472 0-28.571-0.929-44.831-2.555 53.89-38.792 91.289-91.057 91.289-160.047v-116.144h278.744v116.144c0 108.245-216.724 162.601-325.203 162.601z" />
<glyph unicode="&#xe607;" d="M977.76 152.992l-400.608 685.952c-33.408 33.408-87.616 33.408-121.024 0l-400.64-685.952c-33.408-33.376-33.408-87.552 0-120.992h922.24c33.472 33.44 33.472 87.616 0.032 120.992zM479.744 592.704c0 26.528 21.504 48 48 48s48-21.472 48-48v-224c0-26.496-21.504-48-48-48s-48 21.504-48 48v224zM528.032 160.448c-26.496 0-48 21.44-48 48 0 26.496 21.504 48 48 48s48-21.504 48-48c0-26.56-21.504-48-48-48z" />
<glyph unicode="&#xe608;" d="M438.928 896.072q14.856 0 25.714-10.856t10.858-25.714v-128h168l98.856 98.856q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714-10.856-25.714l-98.856-98.856v-482.286l98.856-98.856q10.856-10.856 10.856-25.714t-10.856-25.714-25.714-10.856-25.714 10.856l-98.856 98.856h-168v-128q0-14.856-10.856-25.714t-25.714-10.856-25.714 10.856-10.856 25.714v128q-97.714 0-165.714 38.286l-119.43-118.856q-10.856-10.856-25.714-10.856t-25.714 10.856q-10.856 10.286-10.856 25.714t10.856 25.714l112.57 113.144q-2.856 2.856-7.43 8.572t-16.286 24-20.856 37.144-16.57 46.856-7.43 55.428h512v73.144h-512q0 29.144 7.714 58t18.856 49.714 22.286 37.714 18.57 24.856l8 8.572-118.286 104.57q-12 11.43-12 27.43 0 13.714 9.144 24.57 10.286 10.858 25.43 11.714t26.57-8.856l129.714-115.428q65.144 33.142 156.57 33.142v128q0 14.858 10.858 25.714t25.714 10.856zM768.072 621.786q76 0 129.428-53.428t53.428-129.428-53.428-129.428-129.428-53.428v365.714z" horiz-adv-x="952" />
<glyph unicode="&#xe609;" d="M1024 960v-384l-138.26 138.26-212-212-107.48 107.48 212 212-138.26 138.26zM245.74 821.74l212-212-107.48-107.48-212 212-138.26-138.26v384h384zM885.74 181.74l138.26 138.26v-384h-384l138.26 138.26-212 212 107.48 107.48zM457.74 286.26l-212-212 138.26-138.26h-384v384l138.26-138.26 212 212z" />
<glyph unicode="&#xe60a;" d="M64 384h384v-384l-138.26 138.26-202-202-107.48 107.48 202 202zM821.74 245.74l202-202-107.48-107.48-202 202-138.26-138.26v384h384zM960 512h-384v384l138.26-138.26 202 202 107.48-107.48-202-202zM309.74 757.74l138.26 138.26v-384h-384l138.26 138.26-202 202 107.48 107.48z" />
<glyph unicode="&#xe60b;" d="M800 832h-576c-53.020 0-96-42.98-96-96v-32h768v32c0 53.020-42.98 96-96 96zM632.32 896l14.116-101h-268.872l14.114 101h240.642zM640 960h-256c-26.4 0-50.99-21.392-54.642-47.538l-18.714-133.924c-3.654-26.146 14.956-47.538 41.356-47.538h320c26.4 0 45.010 21.392 41.358 47.538l-18.714 133.924c-3.654 26.146-28.244 47.538-54.644 47.538v0zM816 640h-608c-35.2 0-61.392-28.682-58.206-63.738l52.412-576.526c3.186-35.054 34.594-63.736 69.794-63.736h480c35.2 0 66.608 28.682 69.794 63.736l52.41 576.526c3.188 35.056-23.004 63.738-58.204 63.738zM384 64h-96l-32 448h128v-448zM576 64h-128v448h128v-448zM736 64h-96v448h128l-32-448z" />
<glyph unicode="&#xe60c;" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe60d;" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" />
<glyph unicode="&#xe60e;" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
<glyph unicode="&#xe60f;" d="M128 832h768v-768h-768z" />
<glyph unicode="&#xe610;" d="M363.722 237.948l41.298 57.816-45.254 45.256-57.818-41.296c-10.722 5.994-22.204 10.774-34.266 14.192l-11.682 70.084h-64l-11.68-70.086c-12.062-3.418-23.544-8.198-34.266-14.192l-57.818 41.298-45.256-45.256 41.298-57.816c-5.994-10.72-10.774-22.206-14.192-34.266l-70.086-11.682v-64l70.086-11.682c3.418-12.060 8.198-23.544 14.192-34.266l-41.298-57.816 45.254-45.256 57.818 41.296c10.722-5.994 22.204-10.774 34.266-14.192l11.682-70.084h64l11.68 70.086c12.062 3.418 23.544 8.198 34.266 14.192l57.818-41.296 45.254 45.256-41.298 57.816c5.994 10.72 10.774 22.206 14.192 34.266l70.088 11.68v64l-70.086 11.682c-3.418 12.060-8.198 23.544-14.192 34.266zM224 96c-35.348 0-64 28.654-64 64s28.652 64 64 64 64-28.654 64-64-28.652-64-64-64zM1024 576v64l-67.382 12.25c-1.242 8.046-2.832 15.978-4.724 23.79l57.558 37.1-24.492 59.128-66.944-14.468c-4.214 6.91-8.726 13.62-13.492 20.13l39.006 56.342-45.256 45.254-56.342-39.006c-6.512 4.766-13.22 9.276-20.13 13.494l14.468 66.944-59.128 24.494-37.1-57.558c-7.812 1.892-15.744 3.482-23.79 4.724l-12.252 67.382h-64l-12.252-67.382c-8.046-1.242-15.976-2.832-23.79-4.724l-37.098 57.558-59.128-24.492 14.468-66.944c-6.91-4.216-13.62-8.728-20.13-13.494l-56.342 39.006-45.254-45.254 39.006-56.342c-4.766-6.51-9.278-13.22-13.494-20.13l-66.944 14.468-24.492-59.128 57.558-37.1c-1.892-7.812-3.482-15.742-4.724-23.79l-67.384-12.252v-64l67.382-12.25c1.242-8.046 2.832-15.978 4.724-23.79l-57.558-37.1 24.492-59.128 66.944 14.468c4.216-6.91 8.728-13.618 13.494-20.13l-39.006-56.342 45.254-45.256 56.342 39.006c6.51-4.766 13.22-9.276 20.13-13.492l-14.468-66.944 59.128-24.492 37.102 57.558c7.81-1.892 15.742-3.482 23.788-4.724l12.252-67.384h64l12.252 67.382c8.044 1.242 15.976 2.832 23.79 4.724l37.1-57.558 59.128 24.492-14.468 66.944c6.91 4.216 13.62 8.726 20.13 13.492l56.342-39.006 45.256 45.256-39.006 56.342c4.766 6.512 9.276 13.22 13.492 20.13l66.944-14.468 24.492 59.13-57.558 37.1c1.892 7.812 3.482 15.742 4.724 23.79l67.382 12.25zM672 468.8c-76.878 0-139.2 62.322-139.2 139.2s62.32 139.2 139.2 139.2 139.2-62.322 139.2-139.2c0-76.878-62.32-139.2-139.2-139.2z" />
<glyph unicode="&#xe611;" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" />
<glyph unicode="&#xe612;" d="M448 165.49l-205.254 237.254 58.508 58.51 146.746-114.744 274.744 242.744 58.512-58.508zM831.772 832c0.078-0.066 0.162-0.15 0.228-0.23v-767.542c-0.066-0.078-0.15-0.162-0.228-0.228h-639.544c-0.080 0.066-0.162 0.15-0.228 0.228v767.544c0.066 0.080 0.15 0.162 0.23 0.228h-128.23v-768c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v768h-128.228zM640 832v64c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-64h-128v-128h512v128h-128zM576 832h-128v64h128v-64z" />
<glyph unicode="&#xe613;" d="M128 682.666v128h896v-640h-128v-128h-896v640h128zM896 256h42.668v469.334h-725.332v-42.666h682.668v-426.668zM85.332 128h725.332v469.334h-725.332v-469.334z" />
<glyph unicode="&#xe614;" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
<glyph unicode="&#xe615;" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
<glyph unicode="&#xe616;" d="M501.443 484.729c-119.819 31.143-158.353 63.078-158.353 113.223 0 57.535 53.047 97.916 142.518 97.916 93.957 0 128.794-44.867 131.961-110.848h116.654c-3.431 91.053-59.119 173.925-169.439 201.109v115.599h-158.353v-114.015c-102.402-22.433-184.746-88.413-184.746-190.553 0-121.933 101.082-182.636 248.086-217.999 132.225-31.671 158.353-77.856 158.353-127.474 0-36.159-25.601-94.219-142.518-94.219-108.736 0-151.755 48.826-157.297 110.848h-116.389c6.597-115.599 92.901-180.26 194.511-202.165v-114.544h158.353v113.487c102.667 19.794 184.746 79.178 184.746 187.65 0 149.38-128.266 200.581-248.086 231.987z" />
<glyph unicode="&#xe617;" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
<glyph unicode="&#xe618;" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
<glyph unicode="&#xe619;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM256 704h512c9.138 0 18.004-1.962 26.144-5.662l-282.144-329.168-282.144 329.17c8.14 3.696 17.006 5.66 26.144 5.66zM192 256v384c0 1.34 0.056 2.672 0.14 4l187.664-218.942-185.598-185.598c-1.444 5.336-2.206 10.886-2.206 16.54zM768 192h-512c-5.654 0-11.202 0.762-16.54 2.208l182.118 182.118 90.422-105.498 90.424 105.494 182.116-182.12c-5.34-1.44-10.886-2.202-16.54-2.202zM832 256c0-5.654-0.762-11.2-2.206-16.54l-185.6 185.598 187.666 218.942c0.084-1.328 0.14-2.66 0.14-4v-384z" />
<glyph unicode="&#xe61a;" d="M437.006 141.838c0-75.068-46.39-134.392-177.758-139.176-76.984 43.786-141.49 106.952-186.908 182.866 23.69 58.496 97.692 103.046 182.316 102.114 24.022-0.252 46.41-4.114 66.744-10.7 55.908-38.866 101-63.152 112.324-107.448 2.114-8.964 3.282-18.206 3.282-27.656zM512 960c-147.94 0-281.196-62.77-374.666-163.098 36.934 20.452 80.538 32.638 126.902 32.638 67.068 0 256.438 0 256.438 0l-57.304-60.14h-67.31c47.496-27.212 72.752-83.248 72.752-145.012 0-56.692-31.416-102.38-75.78-137.058-43.28-33.802-51.492-47.966-51.492-76.734 0-24.542 51.722-61.098 75.5-78.936 82.818-62.112 99.578-101.184 99.578-178.87 0-78.726-68.936-157.104-185.866-183.742 56.348-21.338 117.426-33.048 181.248-33.048 282.77 0 512 229.23 512 512s-229.23 512-512 512zM768 576v-128h-64v128h-128v64h128v128h64v-128h128v-64h-128zM365.768 620.528c11.922-90.776-27.846-149.19-96.934-147.134-69.126 2.082-134.806 65.492-146.74 156.242-11.928 90.788 34.418 160.254 103.53 158.196 69.090-2.074 128.22-76.542 140.144-167.304zM220.886 317.932c-74.68 0-138.128-25.768-182.842-63.864-24.502 59.82-38.044 125.29-38.044 193.932 0 56.766 9.256 111.368 26.312 162.396 7.374-99.442 77.352-176.192 192.97-176.192 8.514 0 16.764 0.442 24.874 1.022-7.95-15.23-13.622-32.19-13.622-49.982 0-29.97 16.488-47.070 36.868-66.894-15.402 0-30.27-0.418-46.516-0.418z" />
<glyph unicode="&#xe61b;" d="M512 960c282.77 0 512-229.23 512-512 0-261.094-195.438-476.53-448-508.026v380.026h176l16 128h-192v64c0 35.346 28.654 64 64 64h128v128h-128c-106.040 0-192-85.96-192-192v-64h-96v-128h96v-380.026c-252.562 31.496-448 246.932-448 508.026 0 282.77 229.23 512 512 512z" />
<glyph unicode="&#xe61c;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM806.242 598.912c0.292-6.508 0.442-13.056 0.442-19.638 0-200.622-152.708-431.964-431.958-431.964-85.736 0-165.536 25.136-232.726 68.214 11.876-1.408 23.962-2.126 36.216-2.126 71.13 0 136.59 24.276 188.55 64.994-66.434 1.22-122.5 45.122-141.824 105.432 9.274-1.768 18.784-2.722 28.566-2.722 13.846 0 27.258 1.856 39.998 5.324-69.452 13.952-121.786 75.312-121.786 148.868 0 0.64 0 1.278 0.016 1.91 20.47-11.37 43.878-18.2 68.764-18.988-40.74 27.222-67.54 73.692-67.54 126.368 0 27.822 7.488 53.904 20.556 76.324 74.878-91.854 186.748-152.292 312.924-158.628-2.588 11.118-3.93 22.702-3.93 34.604 0 83.84 67.98 151.812 151.818 151.812 43.666 0 83.124-18.436 110.818-47.94 34.58 6.808 67.074 19.442 96.412 36.84-11.336-35.454-35.41-65.206-66.752-83.994 30.71 3.668 59.968 11.832 87.194 23.904-20.35-30.448-46.090-57.186-75.758-78.594z" />
<glyph unicode="&#xe61d;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" />
<glyph unicode="&#xe61e;" d="M384 128h640v-128h-640zM384 512h640v-128h-640zM384 896h640v-128h-640zM192 960v-256h-64v192h-64v64zM128 434v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM256 256v-320h-192v64h128v64h-128v64h128v64h-128v64z" />
<glyph unicode="&#xe61f;" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z" />
<glyph unicode="&#xe620;" d="M0 343.808l321.344-206.624v149.152l-191.328 118.144 191.328 118.144v150.176l-321.344-207.68v-121.312zM389.312 64h110.592l162.048 768h-111.328l-161.312-768zM702.688 672.8v-150.176l191.264-118.144-191.264-118.112v-149.152l321.312 206.592v121.312l-321.312 207.68z" />
<glyph unicode="&#xe621;" d="M428.222 493.19c-144.194-6.656-260.742-83.888-260.742-180.224 0-96.202 116.574-168.528 260.742-161.792 144.302 6.576 261.094 96.876 261.094 193.078 0 96.12-116.79 155.594-261.094 148.938zM526.472 250.88c-44.168-56.994-131.53-84.802-216.36-38.858-40.394 21.908-38.884 64.916-38.884 64.916s-16.76 135.842 128.27 152.818c145.192 16.816 171.116-121.856 126.976-178.876zM429.19 341.854c-9.298-6.736-11.182-19.618-6.144-27.62 4.85-8.22 16.142-9.162 25.276-2.318 8.974 7.086 12.45 19.428 7.572 27.62-4.798 7.976-15.952 10.294-26.704 2.318zM360.448 323.154c-27.108-2.802-46.484-26.408-46.484-48.99 0-22.636 21.826-38.264 48.882-35.086 26.974 3.072 48.908 23.928 48.908 46.484 0.054 22.636-20.184 40.582-51.308 37.592zM868.082 960h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22c0 86.096-69.768 155.89-155.89 155.89zM818.202 280.386c-59.446-126.274-255.462-187.716-400.734-176.344-138.050 10.86-315.526 56.724-333.878 223.798 0 0-9.7 75.668 63.65 173.568 0 0 105.498 147.322 228.378 189.36 122.988 41.85 137.35-28.968 137.35-70.818-6.548-35.49-18.782-56.374 27.38-42.038 0 0 120.886 56.076 170.658 6.332 40.126-40.152 6.63-95.42 6.63-95.42s-16.654-18.404 17.624-25.034c34.358-6.872 142.362-56.914 82.944-183.404zM698.988 629.274c-13.15 0-23.714 10.644-23.714 23.688 0 13.286 10.562 23.956 23.714 23.956 0 0 148.212 27.404 130.48-131.852 0-0.942-0.108-1.698-0.322-2.534-1.672-11.29-11.586-19.94-23.256-19.94-13.204 0-23.956 10.562-23.956 23.74 0-0.026 23.498 106.416-82.944 82.944zM949.518 501.894h-0.216c-3.908-26.948-17.274-29.104-33.198-29.104-19.052 0-34.438 11.964-34.438 31.044 0 16.52 6.846 33.308 6.846 33.308 2.020 6.952 18.136 50.176-10.644 114.796-52.708 88.522-158.856 89.816-171.384 84.776-12.638-4.958-31.286-7.436-31.286-7.436-19.188 0-34.548 15.602-34.548 34.572 0 15.926 10.644 29.4 25.196 33.524 0 0 0.322 0.538 0.808 0.62 1.052 0.216 2.13 1.268 3.26 1.374 14.794 2.83 67.476 13.178 118.702 1.186 91.648-21.396 217.52-110.026 160.904-298.658z" />
<glyph unicode="&#xe622;" d="M1023.972 804.11c0 86.096-69.794 155.89-155.89 155.89h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22zM133.524 60.794c1.024-12.316-8.218-43.952-20.562-43.952h-2.264c-11.426 0-21.18 28.24-22.284 39.962-17.058 190.14 42.982 344.172 96.66 423.692 19.32 28.916 39.128 52.708 57.074 71.626-4.474 10.24-6.98 21.558-6.98 33.524 0 46.216 37.646 83.592 83.86 83.592 46.484 0 83.7-37.376 83.7-83.592 0-46.51-37.24-83.86-83.7-83.86-17.838 0-34.276 5.632-47.94 15.092-15.872-16.95-33.064-47.884-49.746-73.054-71.114-106.442-100.892-241.986-87.82-383.030zM315.31 328.004c-19.080 0-38.4 2.102-56.832 6.198-12.18 2.884-19.43 15.010-16.924 26.894 2.938 12.26 14.794 19.832 26.892 16.976 15.226-3.504 31.016-5.202 46.86-5.202 114.878 0 208.194 93.32 208.194 207.926 0 114.634-93.32 207.952-208.194 207.952-114.606 0-208.088-93.318-208.088-207.952 0-33.254 7.6-65.268 22.824-94.856 5.848-10.94 1.482-24.522-9.862-30.1-11.076-5.902-24.414-1.536-30.154 9.566-18.296 35.382-27.756 75.508-27.756 115.362 0 139.534 113.474 253.036 253.036 253.036 139.642 0 253.196-113.502 253.196-253.036-0.028-139.292-113.582-252.766-253.198-252.766zM785.866 622.162c-22.42-0.486-45.218 4.204-65.698 13.932-71.464 34.008-101.996 119.726-67.988 191.272 33.9 71.49 119.726 101.996 191.272 67.988 71.49-33.874 102.024-119.836 67.852-191.112-4.958-9.836-10.428-19.212-17.14-27.674-4.23-5.47-12.45-6.466-17.948-2.102s-6.36 12.45-1.994 17.948c5.496 6.846 10.212 14.712 13.958 22.798 28.024 58.718 3.1 129.346-55.808 156.968-58.798 28.024-129.186 3.126-156.968-55.942-28.294-58.664-3.26-129.052 55.7-157.076 17.058-8.004 35.462-11.992 54.056-11.236 7.086 0.134 13.068-5.336 13.204-12.45 0.322-7.114-5.308-12.934-12.504-13.312zM1001.284 534.448c-0.162-0.648-0.432-0.862-0.432-1.24-2.938-5.9-9.674-8.73-16.062-6.466-101.538 37.484-155.972 101.026-183.646 147.86-9.944 16.95-17.164 33.038-22.42 46.7-6.576 0.162-12.854 1.886-18.782 4.5-24.064 11.454-34.062 39.614-22.69 63.408 11.21 23.688 39.478 33.792 63.272 22.42 23.688-11.318 33.658-39.64 22.582-63.272-4.366-9-11.346-16.194-19.482-20.696 5.012-12.476 11.48-26.302 20.318-40.986 37.16-62.41 94.316-108.168 169.58-135.924 6.71-2.614 9.972-9.702 7.76-16.302z" />
<glyph unicode="&#xe623;" d="M512 768c0 0-128 128-512 128v-768c388 0 512-128 512-128s124 128 512 128v768c-384 0-512-128-512-128zM128 768c162.688-13.632 262.496-51.264 320-81.76v-515.488c-57.504 30.368-157.312 68-320 81.76v515.488zM896 252.512c-162.752-13.76-262.496-51.328-320-81.76v515.488c57.504 30.496 157.248 68.128 320 81.76v-515.488z" />
<glyph unicode="&#xe624;" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
<glyph unicode="&#xe9d7;" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xf00a;" d="M512 960c-282.75 0-512-229.25-512-512 0-226.25 146.688-418.126 350.156-485.812 25.594-4.688 34.938 11.126 34.938 24.626 0 12.188-0.468 52.562-0.718 95.312-142.376-30.938-172.468 60.376-172.468 60.376-23.312 59.126-56.844 74.876-56.844 74.876-46.532 31.75 3.53 31.126 3.53 31.126 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.876-55.626 149-42.5 4.654 33 17.904 55.626 32.5 68.376-113.656 12.938-233.218 56.876-233.218 253.062 0 55.938 19.968 101.562 52.656 137.406-5.218 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.032 128.126 17.22 43.5-0.188 87.312-5.876 128.188-17.28 97.688 66.312 140.688 52.5 140.688 52.5 28-70.532 10.376-122.562 5.126-135.5 32.812-35.844 52.626-81.47 52.626-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.876 34.75-47 34.75-94.75 0-68.438-0.688-123.626-0.688-140.5 0-13.626 9.312-29.562 35.25-24.562 203.312 67.812 349.876 259.688 349.876 485.812 0 282.75-229.25 512-512 512z" />
<glyph unicode="&#xf021;" d="M863.428 356.572q0-2.856-0.572-4-36.572-153.144-153.144-248.286t-273.142-95.144q-83.43 0-161.43 31.428t-139.144 89.714l-73.714-73.714q-10.856-10.856-25.714-10.856t-25.714 10.856-10.856 25.714v256q0 14.856 10.856 25.714t25.714 10.856h256q14.856 0 25.714-10.856t10.858-25.714-10.856-25.714l-78.286-78.286q40.57-37.714 92-58.286t106.858-20.572q76.572 0 142.856 37.144t106.286 102.286q6.286 9.714 30.286 66.856 4.572 13.144 17.144 13.144h109.714q7.428 0 12.856-5.428t5.428-12.856zM877.714 813.714v-256q0-14.856-10.856-25.714t-25.714-10.856h-256q-14.856 0-25.714 10.856t-10.856 25.714 10.856 25.714l78.856 78.856q-84.572 78.286-199.428 78.286-76.572 0-142.856-37.142t-106.286-102.286q-6.286-9.714-30.286-66.858-4.57-13.144-17.144-13.144h-113.714q-7.43 0-12.856 5.428t-5.43 12.856v4q37.144 153.144 154.286 248.286t274.286 95.142q83.428 0 162.286-31.714t140-89.428l74.286 73.714q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714z" />
<glyph unicode="&#xf05b;" d="M684 374.856h-62.286q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h62.286q-18.286 61.714-64.286 107.714t-107.714 64.286v-62.286q0-14.858-10.858-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v62.286q-61.714-18.286-107.714-64.286t-64.286-107.714h62.286q14.858 0 25.714-10.856t10.856-25.714v-73.142q0-14.856-10.856-25.714t-25.714-10.856h-62.286q18.286-61.714 64.286-107.714t107.714-64.286v62.286q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-62.286q61.714 18.286 107.714 64.286t64.286 107.714zM877.714 484.572v-73.144q0-14.856-10.856-25.714t-25.714-10.856h-81.714q-21.144-92-88.286-159.144t-159.144-88.286v-81.714q0-14.856-10.856-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v81.714q-92 21.144-159.144 88.286t-88.286 159.144h-81.714q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h81.714q21.144 92 88.286 159.144t159.144 88.286v81.714q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-81.714q92-21.142 159.144-88.286t88.286-159.144h81.714q14.856 0 25.714-10.856t10.856-25.714z" />
<glyph unicode="&#xf0c7;" d="M219.43 82.286h438.858v219.428h-438.858v-219.428zM731.428 82.286h73.144v512q0 8-5.714 22t-11.428 19.714l-160.572 160.57q-5.714 5.714-19.428 11.43t-22.286 5.714v-237.714q0-22.858-16-38.858t-38.856-16h-329.142q-22.856 0-38.856 16t-16 38.856v237.714h-73.144v-731.43h73.144v237.714q0 22.856 16 38.856t38.856 16h475.43q22.856 0 38.856-16t16-38.856v-237.714zM512 612.572v182.856q0 7.428-5.428 12.858t-12.856 5.428h-109.714q-7.428 0-12.858-5.428t-5.428-12.858v-182.856q0-7.428 5.428-12.856t12.856-5.428h109.714q7.428 0 12.858 5.428t5.428 12.856zM877.714 594.286v-530.286q0-22.856-16-38.856t-38.856-16h-768q-22.856 0-38.856 16t-16 38.856v768q0 22.856 16 38.856t38.856 16h530.286q22.856 0 50.286-11.428t43.428-27.428l160-160q16-16 27.428-43.428t11.428-50.286z" />
<glyph unicode="&#xf0ed;" d="M731.429 420.571q0 8-5.143 13.143t-13.143 5.143h-128v201.143q0 7.429-5.429 12.857t-12.857 5.429h-109.714q-7.429 0-12.857-5.429t-5.429-12.857v-201.143h-128q-7.429 0-12.857-5.429t-5.429-12.857q0-8 5.143-13.143l201.143-201.143q5.143-5.143 13.143-5.143t13.143 5.143l200.571 200.571q5.714 6.857 5.714 13.714zM1097.143 292.571q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" horiz-adv-x="1097" />
<glyph unicode="&#xf0ee;" d="M731.429 457.143q0 8-5.143 13.143l-201.143 201.143q-5.143 5.143-13.143 5.143t-13.143-5.143l-200.571-200.571q-5.714-6.857-5.714-13.714 0-8 5.143-13.143t13.143-5.143h128v-201.143q0-7.429 5.429-12.857t12.857-5.429h109.714q7.429 0 12.857 5.429t5.429 12.857v201.143h128q7.429 0 12.857 5.429t5.429 12.857zM1097.143 292.571q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" horiz-adv-x="1097" />
<glyph unicode="&#xf11c;" d="M219.429 283.428v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM292.571 429.714v-54.857q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h128q9.143 0 9.143-9.143zM219.429 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 283.428v-54.857q0-9.143-9.143-9.143h-493.714q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h493.714q9.143 0 9.143-9.143zM438.857 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM365.714 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM585.143 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM512 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM731.429 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 283.428v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM658.286 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 576v-201.143q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h64v137.143q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM1024 146.286v512h-950.857v-512h950.857zM1097.143 658.286v-512q0-30.286-21.429-51.714t-51.714-21.429h-950.857q-30.286 0-51.714 21.429t-21.429 51.714v512q0 30.286 21.429 51.714t51.714 21.429h950.857q30.286 0 51.714-21.429t21.429-51.714z" horiz-adv-x="1097" />
<glyph unicode="&#xf148;" d="M581.714 606.286q-10.286-21.143-33.143-21.143h-109.714v-493.714q0-8-5.143-13.143t-13.143-5.143h-402.286q-12 0-16.571 10.286-4.571 11.429 2.286 20l91.429 109.714q5.143 6.286 14.286 6.286h182.857v365.714h-109.714q-22.857 0-33.143 21.143-9.714 21.143 5.143 38.857l182.857 219.429q10.286 12.571 28 12.571t28-12.571l182.857-219.429q15.429-18.286 5.143-38.857z" horiz-adv-x="585" />
<glyph unicode="&#xf149;" d="M18.286 804.571h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" horiz-adv-x="585" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 31 KiB

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

Binary file not shown.

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

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,12 @@
@@ -1,100 +0,0 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -14,16 +15,8 @@
* limitations under the License.
*/
body {
display: flex;
flex-direction: column;
max-height: 100vh;
}
.main {
flex: 1;
min-height: 1px;
display: flex;
body{
overflow: auto;
}
.header {
@ -54,7 +47,7 @@ body {
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
width: 226px;
padding: 5px 0px;
right: 0px;
line-height: normal;
@ -69,7 +62,7 @@ body {
}
.share-panel .font-ico:hover {
transform: rotate(360deg);
transform:rotate(360deg);
}
.footer {
@ -82,12 +75,8 @@ body {
height: 70%;
}
.bottom-window-group {
height: 30%;
}
#output {
height: 100%;
height: 30%;
width: 100%;
border-width: 0;
margin: 0;
@ -126,10 +115,6 @@ body {
border-left: 1px solid #919191;
}
#goNews::-webkit-scrollbar {
display: none;
}
#goNews li a {
display: block;
padding: 8px 10px;
@ -138,12 +123,6 @@ body {
color: #666;
}
#goNews li a.fn-right {
padding: 0;
border: 0;
color: #4285f4;
}
#goNews li a:hover {
text-decoration: none;
background-color: #f9f9f9;

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -14,17 +14,9 @@
* limitations under the License.
*/
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.wrapper {
margin: 0 auto;
width: 980px;
display: flex;
align-items: center;
}
.header .logo {
@ -52,20 +44,15 @@ body {
}
.header a:hover {
color: #4285f4;
}
.fn-left {
flex: 1;
color: #4183C4;
}
.content {
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #919191;
background-color: #3b3e43;
flex: 1;
display: flex;
align-items: center;
background-color: #202021;
height: 632px;
padding-top: 222px;
}
.content h2 {
@ -75,104 +62,94 @@ body {
}
.content h3 {
color: #4285f4;
color: #4183c4;
font-size: 21px;
}
.content .form {
padding: 24px 15px;
background-color: #fff;
border-radius: 6px;
width: 320px;
margin-top: -18px;
position: relative;
}
.content a {
color: #4285f4;
.content .form input {
border: 1px solid #ccc;
border-radius: 3px;
width: 100%;
background-color: #fafafa;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset;
color: #333;
min-height: 34px;
outline: medium none;
vertical-align: middle;
font-size: 16px;
border: 1px solid #FFF;
padding: 10px;
margin-top: 20px;
}
.login__icon {
width: 200px;
transition: all .15s ease-in-out;
padding-right: 24px;
color: #3b3e43;
fill: currentColor;
}
.login__icon:hover {
transform: scale(1.1);
.content .form input:focus {
background-color: #FFF;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
}
.btn {
width: 100%;
color: #fff;
background-color: #2ebc4f;
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;
border-radius: 3px;
cursor: pointer;
border: 0;
display: block;
text-align: center;
}
.btn:hover {
text-decoration: none;
background-color: #28a745;
}
.btn:focus {
box-shadow: 0 0 0 0.2em rgba(40, 167, 69, .3);
}
.btn-blue {
background-color: #4285f4;
}
.btn-blue:hover {
background-color: #2a75f3;
background-color: #569e3d;
background-image: linear-gradient(#79d858, #569e3d);
border-color: #4a993e;
}
.btn.btn-white,
.btn.btn-red {
color: #333;
background-color: #fff;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
background-color: #eee;
background-image: linear-gradient(#fcfcfc, #eee);
border-color: #d5d5d5;
}
.btn.btn-red {
color: #d23f31;
color: #9d0000;
}
.btn.btn-white:hover {
color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
background-color: #ddd;
background-image: linear-gradient(#eee, #ddd);
border-color: #ccc;
}
.btn.btn-red:hover {
background-color: #b33630;
background-image: linear-gradient(#dc5f59, #b33630);
background-repeat: repeat-x;
border-color: #cd504a;
color: #fff;
background-color: #d23f31;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
}
.desc {
color: #6a737d;
font-size: 12px;
}
.more-detail {
display: none;
margin: 0 0 8px 60px;
}
.checkbox {
margin-top: 8px;
display: block;
}
.view-more {
cursor: pointer;
display: block;
text-align: center;
color: #4285f4;
margin-top: 8px;
#msg {
background-color: #fcdede;
border: 1px solid #d2b2b2;
padding: 15px;
font-size: 14px;
color: #911;
position: absolute;
width: 100%;
top: -48px;
}
.footer {
@ -185,7 +162,7 @@ body {
.footer a {
text-decoration: none;
color: #4285f4;
color: #4183c4;
}
.footer a:hover {
@ -201,7 +178,7 @@ body {
/* start sign up */
.dir {
color: #4285f4;
color: #4183c4;
font-size: 18px;
word-wrap: break-word;
margin-top: 20px;
@ -221,21 +198,4 @@ body {
margin-top: 20px;
font-size: 16px;
}
.start {
text-align: center;
}
.start .btn {
color: #fff;
}
.start svg {
fill: currentColor;
}
.start__aciton {
display: flex;
align-items: center;
}
/* end sign up */

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -39,6 +39,7 @@
margin-bottom: 10px;
padding: 5px 10px;
color: #FFF;
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
}
#startPage .details {

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -136,7 +136,7 @@
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
width: 226px;
padding: 5px 0;
right: 0px;
top: 21px;
@ -259,19 +259,22 @@
.bottom-window-group .output .start-build,
.bottom-window-group .output .start-test, .start-vet,
.bottom-window-group .output .start-install {
.bottom-window-group .output .start-install,
.bottom-window-group .output .start-get {
color: #999;
}
.bottom-window-group .output .build-succ,
.bottom-window-group .output .test-succ, .vet-succ,
.bottom-window-group .output .install-succ {
.bottom-window-group .output .install-succ,
.bottom-window-group .output .get-succ {
color: rgb(0,153,0);
}
.bottom-window-group .output .build-error,
.bottom-window-group .output .test-error, .vet-error,
.bottom-window-group .output .install-error {
.bottom-window-group .output .install-error,
.bottom-window-group .output .get-error {
color: #9d0000;
}

View File

@ -1,6 +1,6 @@
.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px;text-decoration:none}.dialog-close-icon:hover{text-decoration:none}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#fff;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px}
::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:solid 0 transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;margin:0;color:#000;overflow:hidden;font-family:Helvetica}ul{padding:0;margin:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}button,input{font-family:Helvetica}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#fff}.list li.selected .ft-small,.list li:hover .ft-small{color:#fff}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?lqk80d);src:url(fonts/icomoon.eot?lqk80d#iefix) format('embedded-opentype'),url(fonts/icomoon.ttf?lqk80d) format('truetype'),url(fonts/icomoon.woff?lqk80d) format('woff'),url(fonts/icomoon.svg?lqk80d#icomoon) format('svg');font-weight:400;font-style:normal}[class*=" ico-"],[class^=ico-]{font-family:icomoon!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-qqz:before{content:"\e900"}.ico-find:before{content:"\e602"}.ico-findfiles:before{content:"\e603"}.ico-editor:before{content:"\e604"}.ico-notification:before{content:"\e607"}.ico-price:before{content:"\e616"}.ico-report:before{content:"\e605"}.ico-git:before{content:"\e624"}.ico-book:before{content:"\e623"}.ico-start:before{content:"\e9d7";text-shadow:0 0 rgba(0,0,0,.4)}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-export:before{content:"\f0ed"}.ico-import:before{content:"\f0ee"}.ico-keyboard:before{content:"\f11c"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-weibo:before{content:"\e621"}.ico-uniE608:before{content:"\e608"}.ico-max:before{content:"\e609"}.ico-remove:before{content:"\e60b"}.ico-buildrun:before{content:"\e60c"}.ico-about:before{content:"\e60d"}.ico-undo:before{content:"\e60e"}.ico-stop:before{content:"\e60f"}.ico-close:before{content:"\e611";text-shadow:0 0 rgba(0,0,0,.4)}.ico-format:before{content:"\e612"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-redo:before{content:"\e615"}.ico-uniE617:before{content:"\e617"}.ico-signout:before{content:"\e618"}.ico-email:before{content:"\e619"}.ico-googleplus:before{content:"\e61a"}.ico-facebook:before{content:"\e61b"}.ico-twitter:before{content:"\e61c"}.ico-info:before{content:"\e61d"}.ico-goline:before{content:"\e61e"}.ico-share:before{content:"\e61f"}.ico-comment:before{content:"\e620"}.ico-github:before{content:"\f00a"}.ico-refresh:before{content:"\f021"}.ico-save:before{content:"\f0c7"}
.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{padding:0 5px;line-height:25px;cursor:pointer}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#fff}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{float:left;line-height:20px;height:20px;padding:0 5px;cursor:pointer}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.menu{display:block!important}.menu>ul>li{float:left}.menu>ul>li>span{font-size:12px;line-height:21px;cursor:pointer;padding:4px 7px}.menu .split{float:left;border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6db14c;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:190px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{position:absolute;left:20%;width:60%;height:70%;overflow:hidden;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);float:left;height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.footer .cursor{cursor:pointer}.notification-count{float:right;display:none;cursor:pointer;background-color:#9d0000;color:#fff;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px}
.side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0 5px}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px}
#startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#bbb;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#fff}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px;border-bottom:1px solid #919191}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .news li{border-bottom:1px solid #919191}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap}
#dialogAboutDialog .dialog-main{background-color:#fff}#dialogAbout{margin:35px 20px;line-height:28px}#dialogAbout .item{margin:0 10px}#dialogAbout a{color:#4183c4;text-decoration:none}#dialogAbout a:hover{text-decoration:underline}#dialogAbout label{color:#666}#dialogAbout img{width:100px;float:left;margin-right:60px}#dialogAbout .space{margin-bottom:6px;border-bottom:1px solid #919191;padding-bottom:6px}#dialogAbout .thx ul{margin-left:50px}#dialogAbout .thx a{width:80px;display:inline-block}#dialogAbout .license{color:#999;font-size:12px;line-height:normal;height:85px;overflow-x:hidden;word-wrap:break-word}
.dialog-close-icon,.dialog-close-icon:hover{text-decoration:none}.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;filter:alpha(opacity=30);display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#FFF;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px}
body,ul{margin:0}body,button,input{font-family:Helvetica}.list li,body{overflow:hidden}::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:0 solid transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;color:#000}ul{padding:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#FFF}.list li.selected .ft-small,.list li:hover .ft-small{color:#FFF}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?35cb2z);src:url(fonts/icomoon.eot?#iefix35cb2z) format('embedded-opentype'),url(fonts/icomoon.woff?35cb2z) format('woff'),url(fonts/icomoon.ttf?35cb2z) format('truetype'),url(fonts/icomoon.svg?35cb2z#icomoon) format('svg');font-weight:400;font-style:normal}.font-ico,[class^=ico-]{font-family:icomoon;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-book:before{content:"\e623"}.ico-price:before{content:"\e616"}.ico-start:before{content:"\e9d7"}.ico-share:before{content:"\e61f"}.ico-github:before{content:"\f00a"}.ico-git:before{content:"\e624"}.ico-tencent:before{content:"\e622"}.ico-weibo:before{content:"\e621"}.ico-googleplus:before{content:"\e61a"}.ico-twitter:before{content:"\e61c"}.ico-email:before{content:"\e619"}.ico-facebook:before{content:"\e61b"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-keyboard:before{content:"\f11c"}.ico-findfiles:before{content:"\e603"}.ico-find:before{content:"\e602"}.ico-editor:before{content:"\e604"}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-notification:before{content:"\e607"}.ico-report:before{content:"\e605"}.ico-comment:before{content:"\e620"}.ico-goline:before{content:"\e61e"}.ico-info:before{content:"\e61d"}.ico-signup:before{content:"\e606"}.ico-signout:before{content:"\e618"}.ico-redo:before{content:"\e615"}.ico-undo:before{content:"\e60e"}.ico-about:before{content:"\e60d"}.ico-import:before{content:"\f0ee"}.ico-export:before{content:"\f0ed"}.ico-refresh:before{content:"\f021"}.ico-remove:before{content:"\e60b"}.ico-save:before{content:"\f0c7"}.ico-max:before{content:"\e609"}.ico-format:before{content:"\e612"}.ico-buildrun:before{content:"\e60c"}.ico-stop:before{content:"\e60f"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-close:before{content:"\e611"}
.frame li,.tabs>div{padding:0 5px;cursor:pointer}.footer .cursor,.frame li,.menu>ul>li>span,.notification-count,.tabs>div{cursor:pointer}.ico,.menu .split,.menu>ul>li,.tabs>div{float:left}.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{line-height:25px}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#FFF}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{line-height:20px;height:20px}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.edit-exprinfo,.edit-panel{position:absolute;overflow:hidden}.menu{display:block!important}.menu>ul>li>span{font-size:12px;line-height:21px;padding:4px 7px}.menu .split{border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6DB14C;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:226px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{left:20%;width:60%;height:70%;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{z-index:10;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-get,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .get-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .get-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.notification-count{float:right;display:none;background-color:#9d0000;color:#FFF;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px}
.side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px}
#startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#BBB;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#FFF;text-shadow:0 1px rgba(0,0,0,.4)}#startPage .details li.border,#startPage .news li{border-bottom:1px solid #919191}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap}
#dialogAboutDialog .dialog-main{background-color:#FFF}#dialogAbout{margin:35px 20px;line-height:28px}#dialogAbout .item{margin:0 10px}#dialogAbout a{color:#4183c4;text-decoration:none}#dialogAbout a:hover{text-decoration:underline}#dialogAbout label{color:#666}#dialogAbout img{width:100px;float:left;margin-right:60px}#dialogAbout .space{margin-bottom:6px;border-bottom:1px solid #919191;padding-bottom:6px}#dialogAbout .thx ul{margin-left:50px}#dialogAbout .thx a{width:80px;display:inline-block}#dialogAbout .license{color:#999;font-size:12px;line-height:normal;height:85px;overflow-x:hidden;word-wrap:break-word}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -19,7 +19,7 @@
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.1.1, Mar 15, 2017
* @version 1.1.0.1, Dec 8, 2015
*/
var bottomGroup = {
tabs: undefined,
@ -82,7 +82,6 @@ var bottomGroup = {
fillOutput: function (data) {
var $output = $('.bottom-window-group .output');
data = data.replace(/\r/g, '');
data = data.replace(/\n/g, '<br/>');
if (-1 !== data.indexOf("<br/>")) {

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -24,7 +24,7 @@
$.fn.extend({
dialog: {
version: "0.0.1.7",
author: "v@b3log.org"
author: "lly219@gmail.com"
}
});

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -178,7 +178,7 @@ var editors = {
}
if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install',
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']);
@ -246,9 +246,9 @@ var editors = {
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
content: '<div id="startPage"></div>',
after: function () {
$("#startPage").load('/start?sid=' + config.wideSessionId);
$("#startPage").load(config.context + '/start?sid=' + config.wideSessionId);
$.ajax({
url: "https://ld246.com/apis/articles?tags=wide,golang&p=1&size=20",
url: "https://hacpai.com/apis/articles?tags=wide,golang&p=1&size=20",
type: "GET",
dataType: "jsonp",
jsonp: "callback",
@ -264,8 +264,7 @@ var editors = {
length = 9;
}
var listHTML = "<ul><li class='title'>" + config.label.community +
"<a href='https://ld246.com/article/1437497122181' target='_blank' class='fn-right'>边看边练</li>";
var listHTML = "<ul><li class='title'>" + config.label.community + "</li>";
for (var i = 0; i < length; i++) {
var article = articles[i];
listHTML += "<li>"
@ -329,7 +328,7 @@ var editors = {
$.ajax({
async: false, // 同步执行
type: 'POST',
url: '/autocomplete',
url: config.context + '/autocomplete',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
@ -435,11 +434,11 @@ var editors = {
$.ajax({
type: 'POST',
url: '/exprinfo',
url: config.context + '/exprinfo',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (0 != result.code) {
if (!result.succ) {
return;
}
@ -583,11 +582,11 @@ var editors = {
$.ajax({
type: 'POST',
url: '/find/decl',
url: config.context + '/find/decl',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (0 != result.code) {
if (!result.succ) {
return;
}
@ -613,11 +612,11 @@ var editors = {
$.ajax({
type: 'POST',
url: '/find/usages',
url: config.context + '/find/usages',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (0 != result.code) {
if (!result.succ) {
return;
}
@ -732,7 +731,7 @@ var editors = {
content: '<textarea id="editor' + id + '"></textarea>'
});
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-get', 'go-install',
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
'edit']);

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

124
static/js/lib.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,7 @@ xquery version &quot;1.0-ml&quot;;
: you may not use this file except in compliance with the License.
: You may obtain a copy of the License at
:
: https://www.apache.org/licenses/LICENSE-2.0
: 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 &quot;AS IS&quot; BASIS,

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@ -19,7 +19,7 @@
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.2, Jun 23, 2019
* @version 1.0.0.1, Dec 8, 2015
*/
var notification = {
init: function () {
@ -35,7 +35,7 @@ var notification = {
var notificationWS = new ReconnectingWebSocket(config.channel + '/notification/ws?sid=' + config.wideSessionId);
notificationWS.onopen = function () {
// console.log('[notification onopen] connected');
console.log('[notification onopen] connected');
};
notificationWS.onmessage = function (e) {
@ -44,7 +44,7 @@ var notification = {
notificationHTML = '';
if (data.cmd && "init-notification" === data.cmd) {
// console.log('[notification onmessage]' + e.data);
console.log('[notification onmessage]' + e.data);
return;
}
@ -58,11 +58,11 @@ var notification = {
};
notificationWS.onclose = function (e) {
// console.log('[notification onclose] disconnected (' + e.code + ')');
console.log('[notification onclose] disconnected (' + e.code + ')');
};
notificationWS.onerror = function (e) {
console.log('[notification onerror]', e);
console.log('[notification onerror]');
};
}
};

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

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

View File

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

61
static/js/shell.js Normal file
View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @file shell.js
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015
*/
var shell = {
_shellWS: undefined,
_initWS: function () {
shell.shellWS = new ReconnectingWebSocket(config.channel + '/shell/ws?sid=' + config.wideSessionId);
shell.shellWS.onopen = function () {
console.log('[shell onopen] connected');
};
shell.shellWS.onmessage = function (e) {
console.log('[shell onmessage]' + e.data);
var data = JSON.parse(e.data);
if ('init-shell' !== data.cmd) {
$('#shellOutput').val(data.output);
}
};
shell.shellWS.onclose = function (e) {
console.log('[shell onclose] disconnected (' + e.code + ')');
};
shell.shellWS.onerror = function (e) {
console.log('[shell onerror] ' + e);
};
},
init: function () {
this._initWS();
$('#shellInput').keydown(function (event) {
if (13 === event.which) {
var input = {
cmd: $('#shellInput').val()
};
shell.shellWS.send(JSON.stringify(input));
$('#shellInput').val('');
}
});
}
};
$(document).ready(function () {
shell.init();
});

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, b3log.org
* Copyright (c) 2014-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,

View File

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

View File

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

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