Compare commits

..

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

484 changed files with 15862 additions and 62272 deletions

3
.gitattributes vendored
View File

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

9
.gitignore vendored
View File

@ -1,12 +1,7 @@
/wide.exe
/wide
/static/user/admin/style.css
/header
/header.exe
/**/.DS_Store
/node_modules
/nbproject
/workspaces
.idea

21
.gobuild.yml Normal file
View File

@ -0,0 +1,21 @@
author: DL88250@gmail.com
description: A Web-based IDE for Teams using Golang.
filesets:
depth: 10
includes:
- conf
- doc
- i18n
- static
- views
- README.md
- LICENSE
excludes:
- \.git
settings:
targetdir: ""
build: |
test -d Godeps && go(){ godep go "$@";} ; go install -v
outfiles:
- wide

View File

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

View File

@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may 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

16
Dockerfile Normal file
View File

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

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,

168
README.md
View File

@ -1,18 +1,156 @@
* [Wide 用户指南](https://ld246.com/article/1538873544275)
* [Wide 开发指南](https://ld246.com/article/1538876422995)
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
# 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-~1K-red.svg?style=flat)](http://pan.baidu.com/s/1dD3XwOT)
![Theme](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.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!_
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
先试试我们搭建好的[**在线服务**](http://wide.b3log.org/signup),好用的话你可以在这里[下载](http://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请你的小伙伴们加入吧!
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
## Intro
A <b>W</b>eb-based <b>IDE</b> for Teams using Golang.
![Hello, 世界](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png)
## Motivation
* **Team** IDE:
* _Safe and reliable_: the project source code stored on the server in real time, the developer's machine crashes without losing any source code
* _Unified environment_: server unified development environment configuration, the developer machine without any additional configuration
* _Out of the box_: 5 minutes to setup a server then open browser to develop, debug
* _Version Control_: each developer has its own source code repository, easy sync with the trunk
* **Web-based** IDE:
* Developer needs a browser only
* Cross-platform, even on mobile devices
* Easy to extend
* Easy to integrate with other systems
* For the geeks
* A try for commercial-open source: versions customized for enterprises, close to their development work flows respectively
* Currently more popular Go IDE has some defects or regrets:
* Text editor (vim/emacs/sublime/Atom, etc.): For the Go newbie is too complex
* Plug-in (goclipse, etc.): the need for the original IDE support, not professional
* LiteIDE: no modern user interface :p
* No team development experience
* There are a few of GO IDEs, and no one developed by Go itself, this is a nice try
## Features
* [X] Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc.
* [X] Autocomplete: Go/HTML etc.
* [X] Format: Go/HTML/JSON etc.
* [X] Build & Run
* [X] Multiplayer: a real team development experience
* [X] Navigation, Jump to declaration, Find usages, File search etc.
* [X] Shell: run command on the server
* [X] Web development: Frontend devlopment (HTML/JS/CSS) all in one
* [X] Go tool: go get/install/fmt etc.
* [X] File Import & Export
* [X] Themes
* [ ] Debug
* [ ] Git integration: git command on the web
## Screenshots
* **Overview**
![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)
* **About**
![About](https://cloud.githubusercontent.com/assets/873584/5521709/87ce85a8-89e3-11e4-87b2-4870a426dfcd.png)
## 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````<br/>
3.1 Sets environment variables (e.g. ${GOPATH})<br/>
3.2 ````gocode```` with ````lib-path```` parameter
## Documents
* [用户指南](http://88250.gitbooks.io/wide-user-guide)
* [开发指南](http://88250.gitbooks.io/wide-dev-guide)
## Setup
### Download Binary
We have provided OS-specific executable binary as follows:
* linux-amd64/386
* windows-amd64/386
* darwin-amd64/386
Download [HERE](http://pan.baidu.com/s/1dD3XwOT)!
### Build Wide for yourself
1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone`
2. Get dependencies with
* `go get`
* `go get github.com/88250/ide_stub`
* `go get github.com/nsf/gocode`
3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -p 127.0.0.1:7070:7070 88250/wide:latest ./wide -docker=true -channel=ws://127.0.0.1:7070`
3. Open browser: http://127.0.0.1:7070
## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
## Terms
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any pages, even the pages are developed by you
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for request a commercial license
* Copyright (c) b3log.org, all rights reserved
## Credits
* [golang](http://golang.org)
* [CodeMirror](https://github.com/marijnh/CodeMirror)
* [zTree](https://github.com/zTree/zTree_v3)
* [LiteIDE](https://github.com/visualfc/liteide)
* [gocode](https://github.com/nsf/gocode)
* [Gorilla](https://github.com/gorilla)
* [GoBuild](http://gobuild.io)
* [Docker](https://docker.com)
----
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />

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-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,
@ -15,56 +15,54 @@
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.
type Panel struct {
State string `json:"state"` // panel state, "min"/"max"/"normal"
Size uint16 `json:"size"` // panel size
}
// Layout represents the UI layout.
// Layout represents the layot of a window.
type Layout struct {
Side *Panel `json:"side"` // Side panel
SideRight *Panel `json:"sideRight"` // Right-Side panel
Bottom *Panel `json:"bottom"` // Bottom panel
State string // min/max/normal
}
// LatestSessionContent represents the latest session content.
type LatestSessionContent struct {
FileTree []string `json:"fileTree"` // paths of expanding nodes of file tree
Files []string `json:"files"` // paths of files of opening editor tabs
CurrentFile string `json:"currentFile"` // path of file of the current focused editor tab
Layout *Layout `json:"layout"` // UI Layout
FileTree []string // paths of expanding nodes of file tree
Files []string // paths of files of opening editor tabs
CurrentFile string // path of file of the current focused editor tab
FileTreeLayout *Layout
EditorLayout *Layout
OutlineLayout *Layout
BottomLayout *Layout
}
// 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
Locale string
GoFormat string
FontFamily string
FontSize string
Theme string
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 +74,25 @@ 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",
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, "", " ")
@ -86,13 +102,7 @@ func (u *User) Save() bool {
return false
}
if "" == string(bytes) {
logger.Error("Truncated user [" + u.Id + "]")
return false
}
if err = ioutil.WriteFile(filepath.Join(Wide.Data, "users", u.Id+".json"), bytes, 0644); nil != err {
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err {
logger.Error(err)
return false
@ -101,62 +111,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))
}

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

@ -0,0 +1,32 @@
{
"Name": "admin",
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
"Salt": "dnoyeb",
"Email": "",
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Theme": "default",
"Created": 1414080000000000000,
"Updated": 1414080000000000000,
"Lived": 1414080000000000000,
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "13px",
"LineHeight": "17px",
"Theme": "wide",
"TabSize": "4"
},
"LatestSessionContent": {
"FileTree": [],
"Files": [],
"CurrentFile": "",
"FileTreeLayout": null,
"EditorLayout": null,
"OutlineLayout": null,
"BottomLayout": null
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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 (
@ -36,44 +38,41 @@ const (
// PathListSeparator holds the OS-specific path list separator.
PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current Wide's version.
WideVersion = "1.6.0"
// WideVersion holds the current wide version.
WideVersion = "1.2.0"
// CodeMirrorVer holds the current editor version.
CodeMirrorVer = "5.1"
// UserAgent represents HTTP client user agent.
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
CodeMirrorVer = "4.10"
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
}
// Logger.
var logger = gulu.Log.NewLogger(os.Stdout)
var logger = log.NewLogger(os.Stdout)
// Wide configurations.
var Wide *conf
@ -81,28 +80,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) {
// 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)
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)
@ -122,36 +114,17 @@ func initUsers() {
continue
}
if ".json" != filepath.Ext(name) { // such as backup (*.json~) not be created by Wide
continue
}
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)
logger.Errorf("Parses [%s] error: %v", name, err)
continue
os.Exit(-1)
}
// Compatibility upgrade (1.3.0): https://github.com/b3log/wide/issues/83
if "" == user.Keymap {
user.Keymap = "wide"
}
// Compatibility upgrade (1.5.3): https://github.com/b3log/wide/issues/308
//if "" == user.GoBuildArgsForLinux {
// user.GoBuildArgsForLinux = "-i"
//}
//if "" == user.GoBuildArgsForWindows {
// user.GoBuildArgsForWindows = "-i"
//}
//if "" == user.GoBuildArgsForDarwin {
// user.GoBuildArgsForDarwin = "-i"
//}
Users = append(Users, user)
}
@ -159,8 +132,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) {
bytes, err := ioutil.ReadFile(confPath)
if nil != err {
logger.Error(err)
@ -176,65 +150,92 @@ 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 Driectory
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)
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)
}
}
// IP
ip, err := util.Net.LocalIP()
if err != nil {
logger.Error(err)
os.Exit(-1)
}
if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
logger.Debugf("${ip} [%s]", ip)
Docker = confDocker
if "" != confIP {
ip = confIP
}
if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
logger.Errorf("Create data directory [%s] error", err)
os.Exit(-1)
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
if "" != confPort {
Wide.Port = confPort
}
// Server
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
Wide.Server = strings.Replace(Wide.Server, "{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)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", strconv.FormatInt(time.Now().UnixNano(), 10), 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,8 +253,6 @@ func FixedTimeCheckEnv() {
}
func checkEnv() {
defer gulu.Panic.Recover(nil)
cmd := exec.Command("go", "version")
buf, err := cmd.CombinedOutput()
if nil != err {
@ -269,30 +268,30 @@ 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]", gocode)
}
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
cmd = exec.Command(ideStub, "version")
_, err = cmd.Output()
if nil != err {
event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound}
logger.Warnf("Not found gotools [%s], please install it with this command: go get github.com/visualfc/gotools", ideStub)
logger.Warnf("Not found ide_stub [%s]", ideStub)
}
}
// 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 +299,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 +317,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 +336,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 +364,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 +395,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 +418,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,16 @@
{
"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": "info",
"Channel": "ws://{IP}:{Port}",
"HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "${time}",
"MaxProcs": 4,
"RuntimeMode": "dev",
"WD": "${pwd}",
"Locale": "en_US",
"Playground": "${home}/playground"
}

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

2
doc/README.md Normal file
View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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,17 +158,22 @@ 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
exec.Command(gocode, []string{"set", "lib-path", libPath}...).Run()
gocode := util.Go.GetExecutableInGOBIN("gocode")
argv := []string{"set", "lib-path", libPath}
exec.Command(gocode, argv...).Run()
argv := []string{"-f=json", "--in=" + path, "autocomplete", strconv.Itoa(offset)}
argv = []string{"-f=json", "autocomplete", strconv.Itoa(offset)}
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
}
@ -113,29 +184,29 @@ 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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
}
path := args["path"].(string)
curDir := filepath.Dir(path)
filename := filepath.Base(path)
curDir := path[:strings.LastIndex(path, conf.PathSeparator)]
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:]
fout, err := os.Create(path)
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -145,7 +216,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -157,61 +228,61 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", 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
data["succ"] = false
return
}
result.Data = exprInfo
data["info"] = exprInfo
}
// 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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
}
path := args["path"].(string)
curDir := filepath.Dir(path)
filename := filepath.Base(path)
curDir := path[:strings.LastIndex(path, conf.PathSeparator)]
filename := path[strings.LastIndex(path, conf.PathSeparator)+1:]
fout, err := os.Create(path)
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -221,7 +292,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -233,24 +304,24 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", 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
data["succ"] = false
return
}
@ -258,49 +329,45 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
part := found[:strings.LastIndex(found, ":")]
cursorSep := strings.LastIndex(part, ":")
path = found[:cursorSep]
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
data := map[string]interface{}{}
result.Data = &data
data["path"] = filepath.ToSlash(path)
data["path"] = path
data["cursorLine"] = cursorLine
data["cursorCh"] = cursorCh
}
// 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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
}
filePath := args["path"].(string)
curDir := filepath.Dir(filePath)
filename := filepath.Base(filePath)
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
filename := filePath[strings.LastIndex(filePath, conf.PathSeparator)+1:]
fout, err := os.Create(filePath)
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -310,7 +377,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -321,36 +388,36 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch)
logger.Tracef("offset [%d]", offset)
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
ideStub := util.Go.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", 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 := strings.TrimSpace(string(output))
if "" == result {
data["succ"] = false
return
}
founds := strings.Split(out, "\n")
founds := strings.Split(result, "\n")
usages := []*file.Snippet{}
for _, found := range founds {
found = strings.TrimSpace(found)
part := found[:strings.LastIndex(found, ":")]
cursorSep := strings.LastIndex(part, ":")
path := filepath.ToSlash(found[:cursorSep])
path := found[:cursorSep]
cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")])
cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:])
@ -358,7 +425,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
usages = append(usages, usage)
}
result.Data = usages
data["founds"] = usages
}
// getCursorOffset calculates the cursor offset.
@ -387,8 +454,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-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,
@ -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,37 +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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["succ"] = false
return
}
filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) {
result.Code = -1
if util.Go.IsAPI(filePath) {
// ignore it
return
}
@ -69,7 +62,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -79,19 +72,12 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
data := map[string]interface{}{}
result.Data = &data
data["code"] = code
result.Data = data
fmt := conf.GetGoFmt(uid)
fmt := conf.GetGoFmt(username)
argv := []string{filePath}
cmd := exec.Command(fmt, argv...)
@ -100,7 +86,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
output := string(bytes)
if "" == output {
// format error, returns the original content
result.Code = 0
data["succ"] = true
data["code"] = code
return
}
@ -112,7 +99,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
fout.WriteString(code)
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,7 +18,7 @@ package event
import (
"os"
"github.com/88250/gulu"
"github.com/b3log/wide/log"
)
const (
@ -28,7 +28,7 @@ const (
EvtCodeGOROOTNotFound
// EvtCodeGocodeNotFound indicates an event: not found gocode
EvtCodeGocodeNotFound
// EvtCodeIDEStubNotFound indicates an event: not found gotools
// EvtCodeIDEStubNotFound indicates an event: not found ide_stub
EvtCodeIDEStubNotFound
// EvtCodeServerInternalError indicates an event: server internal error
EvtCodeServerInternalError
@ -38,7 +38,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,8 +69,6 @@ var UserEventQueues = queues{}
// Load initializes the event handling.
func Load() {
go func() {
defer gulu.Panic.Recover(nil)
for event := range EventQueue {
logger.Debugf("Received a global event [code=%d]", event.Code)
@ -86,19 +84,21 @@ func Load() {
// AddHandler adds the specified handlers to user event queues.
func (uq *UserEventQueue) AddHandler(handlers ...Handler) {
uq.Handlers = append(uq.Handlers, handlers...)
for _, handler := range handlers {
uq.Handlers = append(uq.Handlers, handler)
}
}
// New initializes a user event queue with the specified wide session id.
func (ueqs queues) New(sid string) *UserEventQueue {
if q, ok := ueqs[sid]; ok {
q := ueqs[sid]
if nil != q {
logger.Warnf("Already exist a user queue in session [%s]", sid)
return q
}
q := &UserEventQueue{
q = &UserEventQueue{
Sid: sid,
Queue: make(chan *Event, maxQueueLength),
}
@ -106,8 +106,6 @@ func (ueqs queues) New(sid string) *UserEventQueue {
ueqs[sid] = q
go func() { // start listening
defer gulu.Panic.Recover(nil)
for evt := range q.Queue {
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
@ -123,11 +121,12 @@ func (ueqs queues) New(sid string) *UserEventQueue {
// Close closes a user event queue with the specified wide session id.
func (ueqs queues) Close(sid string) {
if q, ok := ueqs[sid]; ok {
close(q.Queue)
delete(ueqs, sid)
q := ueqs[sid]
if nil == q {
return
}
delete(ueqs, sid)
}
// Handler represents an event handler.

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -20,11 +20,11 @@ import (
"os"
"path/filepath"
"github.com/88250/gulu"
"github.com/b3log/wide/util"
)
// GetZipHandler handles request of retrieving zip file.
func GetZipHandler(w http.ResponseWriter, r *http.Request) {
// GetZip handles request of retrieving zip file.
func GetZip(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
path := q["path"][0]
@ -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
@ -49,54 +49,41 @@ func GetZipHandler(w http.ResponseWriter, r *http.Request) {
os.Remove(path)
}
// 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)
// CreateZip handles request of creating zip.
func CreateZip(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data.Code = -1
data["succ"] = false
return
}
path := args["path"].(string)
var name string
base := filepath.Base(path)
if nil != args["name"] {
name = args["name"].(string)
} else {
name = base
}
dir := filepath.Dir(path)
if !gulu.File.IsExist(path) {
data.Code = -1
data.Msg = "Can't find file [" + path + "]"
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(path + ".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)
}
data.Data = zipPath
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -24,22 +24,21 @@ 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 {
Id string `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
IconSkin string `json:"iconSkin"` // Value should be end with a space
IsParent bool `json:"isParent"`
IconSkin string `json:"iconSkin"` // Value should be end with a space
Type string `json:"type"` // "f": file, "d": directory
Creatable bool `json:"creatable"` // whether can create file in this file node
Removable bool `json:"removable"` // whether can remove this file node
@ -60,7 +59,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{}}
@ -68,26 +67,26 @@ func initAPINode() {
walk(apiPath, apiNode, false, false, true)
}
// GetFilesHandler handles request of constructing user workspace file tree.
// GetFiles handles request of constructing user workspace file tree.
//
// 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)
if httpSession.IsNew {
func GetFiles(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetGzJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
username := session.Values["username"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.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{}}
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
if nil == apiNode { // lazy init
initAPINode()
@ -97,16 +96,9 @@ func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
for _, workspace := range workspaces {
workspacePath := workspace + conf.PathSeparator + "src"
workspaceNode := Node{
Id: filepath.ToSlash(workspacePath), // jQuery API can't accept "\", so we convert it to "/"
Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
Path: filepath.ToSlash(workspacePath),
IconSkin: "ico-ztree-dir-workspace ",
Type: "d",
Creatable: true,
Removable: false,
IsGoAPI: false,
Children: []*Node{}}
workspaceNode := Node{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d",
Creatable: true, Removable: false, IsGoAPI: false, Children: []*Node{}}
walk(workspacePath, &workspaceNode, true, true, false)
@ -117,28 +109,14 @@ func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
// add Go API node
root.Children = append(root.Children, apiNode)
result.Data = root
data["root"] = root
}
// 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)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
// RefreshDirectory handles request of refresh a directory of file tree.
func RefreshDirectory(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
path := r.FormValue("path")
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
node := Node{Name: "root", Path: path, IconSkin: "ico-ztree-dir ", Type: "d", Children: []*Node{}}
walk(path, &node, true, true, false)
@ -153,106 +131,76 @@ func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
// GetFileHandler handles request of opening file by editor.
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// GetFile handles request of opening file by editor.
func GetFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
path := args["path"].(string)
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, 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.Msg = "This file is too large to open :("
data["succ"] = false
data["msg"] = "This file is too large to open :("
return
}
data := map[string]interface{}{}
result.Data = &data
buf, _ := ioutil.ReadFile(path)
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
result.Msg = "Can't open a binary file :("
if util.File.IsBinary(content) {
data["succ"] = false
data["msg"] = "Can't open a binary file :("
} else {
data["content"] = content
data["mode"] = getEditorMode(extension)
data["path"] = path
}
}
// SaveFileHandler handles request of saving file.
func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
// SaveFile handles request of saving file.
func SaveFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -260,17 +208,11 @@ 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) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fout, err := os.Create(filePath)
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -281,7 +223,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
if err := fout.Close(); nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
wSession := session.WideSessions.Get(sid)
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
@ -291,43 +233,28 @@ 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)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// NewFile handles request of creating file or directory.
func NewFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fileType := args["fileType"].(string)
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if !createFile(path, fileType) {
result.Code = -1
data["succ"] = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't create file " + path}
@ -336,110 +263,64 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
}
if "f" == fileType {
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
} else {
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
extension := filepath.Ext(path)
data["mode"] = getEditorMode(extension)
}
}
// RemoveFileHandler handles request of removing file or directory.
func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// RemoveFile handles request of removing file or directory.
func RemoveFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
path := args["path"].(string)
if gulu.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if !removeFile(path) {
result.Code = -1
data["succ"] = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't remove file " + path}
return
}
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
}
// RenameFileHandler handles request of renaming file or directory.
func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// RenameFile handles request of renaming file or directory.
func RenameFile(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
oldPath := args["oldPath"].(string)
if gulu.Go.IsAPI(oldPath) ||
!session.CanAccess(uid, oldPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
newPath := args["newPath"].(string)
if gulu.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if !renameFile(oldPath, newPath) {
result.Code = -1
data["succ"] = false
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't rename file " + oldPath}
return
}
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.UserId)
}
// Use to find results sorting.
@ -454,40 +335,34 @@ func (f foundPaths) Len() int { return len(f) }
func (f foundPaths) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
// 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)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// Find handles request of find files under the specified directory with the specified filename pattern.
func Find(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
path := args["path"].(string) // path of selected file in file tree
if !gulu.Go.IsAPI(path) && !session.CanAccess(uid, path) {
name := args["name"].(string)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := session.Values["username"].(string)
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,34 +372,27 @@ 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)})
founds = append(founds, &foundPath{Path: *r, score: len(substr)})
}
}
sort.Sort(founds)
result.Data = founds
data["founds"] = founds
}
// 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)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
// SearchText handles request of searching files under the specified directory with the specified keyword.
func SearchText(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -532,7 +400,7 @@ func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
result.Code = -1
data["succ"] = false
return
}
@ -541,7 +409,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,13 +418,13 @@ 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)
}
result.Data = founds
data["founds"] = founds
}
// walk traverses the specified path to build a file tree.
@ -568,13 +436,7 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
fio, _ := os.Lstat(fpath)
child := Node{
Id: filepath.ToSlash(fpath), // jQuery API can't accept "\", so we convert it to "/"
Name: filename,
Path: filepath.ToSlash(fpath),
Removable: removable,
IsGoAPI: isGOAPI,
Children: []*Node{}}
child := Node{Name: filename, Path: fpath, Removable: removable, IsGoAPI: isGOAPI, Children: []*Node{}}
node.Children = append(node.Children, &child)
if nil == fio {
@ -587,7 +449,6 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
child.Type = "d"
child.Creatable = creatable
child.IconSkin = "ico-ztree-dir "
child.IsParent = true
walk(fpath, &child, creatable, removable, isGOAPI)
} else {
@ -596,6 +457,7 @@ func walk(path string, node *Node, creatable, removable, isGOAPI bool) {
ext := filepath.Ext(fpath)
child.IconSkin = getIconSkin(ext)
child.Mode = getEditorMode(ext)
}
}
@ -626,18 +488,13 @@ func listFiles(dirname string) []string {
}
if fio.IsDir() {
// exclude the .git, .svn, .hg direcitory
if ".git" == fio.Name() || ".svn" == fio.Name() || ".hg" == fio.Name() {
// exclude the .git direcitory
if ".git" == fio.Name() {
continue
}
dirs = append(dirs, name)
} else {
// exclude the .DS_Store directory on Mac OS X
if ".DS_Store" == fio.Name() {
continue
}
files = append(files, name)
}
}
@ -649,7 +506,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 "
}
@ -677,6 +534,34 @@ func getIconSkin(filenameExtension string) string {
}
}
// getEditorMode gets editor mode with the specified filename extension.
//
// Refers to the CodeMirror document for modes.
func getEditorMode(filenameExtension string) string {
switch filenameExtension {
case ".go":
return "text/x-go"
case ".html":
return "text/html"
case ".md":
return "text/x-markdown"
case ".js":
return "text/javascript"
case ".json":
return "application/json"
case ".css":
return "text/css"
case ".xml":
return "application/xml"
case ".sh":
return "text/x-sh"
case ".sql":
return "text/x-sql"
default:
return "text/plain"
}
}
// createFile creates file on the specified path.
//
// fileType:
@ -695,7 +580,7 @@ func createFile(path, fileType string) bool {
defer file.Close()
logger.Tracef("Created file [%s]", path)
logger.Debugf("Created file [%s]", path)
return true
case "d":
@ -707,7 +592,7 @@ func createFile(path, fileType string) bool {
return false
}
logger.Tracef("Created directory [%s]", path)
logger.Debugf("Created directory [%s]", path)
return true
default:
@ -725,7 +610,7 @@ func removeFile(path string) bool {
return false
}
logger.Tracef("Removed [%s]", path)
logger.Debugf("Removed [%s]", path)
return true
}
@ -738,7 +623,7 @@ func renameFile(oldPath, newPath string) bool {
return false
}
logger.Tracef("Renamed [%s] to [%s]", oldPath, newPath)
logger.Debugf("Renamed [%s] to [%s]", oldPath, newPath)
return true
}
@ -768,7 +653,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 +726,7 @@ func searchInFile(path string, text string) []*Snippet {
}
content := string(bytes)
if gulu.File.IsBinary(content) {
if util.File.IsBinary(content) {
return ret
}
@ -851,8 +736,7 @@ func searchInFile(path string, text string) []*Snippet {
ch := strings.Index(strings.ToLower(line), strings.ToLower(text))
if -1 != ch {
snippet := &Snippet{Path: filepath.ToSlash(path),
Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
snippet := &Snippet{Path: path, Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
ret = append(ret, snippet)
}

77
file/importer.go Normal file
View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -23,7 +23,7 @@ import (
"net/http"
"strings"
"github.com/88250/gulu"
"github.com/b3log/wide/util"
)
type element struct {
@ -32,16 +32,16 @@ type element struct {
Ch int
}
// 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)
// GetOutline gets outfile of a go file.
func GetOutline(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
@ -51,14 +51,11 @@ func GetOutlineHandler(w http.ResponseWriter, r *http.Request) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", code, 0)
if err != nil {
result.Code = -1
data["succ"] = false
return
}
data := map[string]interface{}{}
result.Data = &data
// ast.Print(fset, f)
line, ch := getCursor(code, int(f.Name.Pos()))

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,150 +0,0 @@
/*
* Copyright (c) 2014-2018, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file frontend tool.
*
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
*/
var gulp = require('gulp')
var concat = require('gulp-concat')
var cleanCSS = require('gulp-clean-css')
var uglify = require('gulp-uglify')
var sourcemaps = require('gulp-sourcemaps')
function minLibCSS () {
// css
var cssLibs = [
'./static/js/lib/jquery-layout/layout-default-latest.css',
'./static/js/lib/codemirror-5.1/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
'./static/js/lib/codemirror-5.1/addon/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
'./static/js/overwrite/codemirror/theme/*.css']
return gulp.src(cssLibs).
pipe(cleanCSS()).
pipe(concat('lib.min.css')).
pipe(gulp.dest('./static/css/'))
}
function minZTreeStyleCSS () {
return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
pipe(cleanCSS()).
pipe(concat('zTreeStyle.min.css')).
pipe(gulp.dest('./static/js/lib/ztree/'))
}
function minWideCSS () {
var cssWide = [
'./static/css/dialog.css',
'./static/css/base.css',
'./static/css/wide.css',
'./static/css/side.css',
'./static/css/start.css',
'./static/css/about.css',
]
return gulp.src(cssWide).
pipe(cleanCSS()).
pipe(concat('wide.min.css')).
pipe(gulp.dest('./static/css/'))
}
function minLibJS () {
// js
var jsLibs = [
'./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js',
'./static/js/lib/Autolinker.min.js',
'./static/js/lib/emmet.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js']
return gulp.src(jsLibs).
pipe(uglify()).
pipe(concat('lib.min.js')).
pipe(gulp.dest('./static/js/'))
}
function minWideJS () {
var jsWide = [
'./static/js/tabs.js',
'./static/js/tabs.js',
'./static/js/dialog.js',
'./static/js/editors.js',
'./static/js/notification.js',
'./static/js/tree.js',
'./static/js/wide.js',
'./static/js/session.js',
'./static/js/menu.js',
'./static/js/windows.js',
'./static/js/hotkeys.js',
'./static/js/bottomGroup.js',
]
return gulp.src(jsWide).
pipe(sourcemaps.init()).
pipe(uglify()).
pipe(concat('wide.min.js')).
pipe(sourcemaps.write('.')).
pipe(gulp.dest('./static/js/'))
}
gulp.task('default',
gulp.series(
gulp.parallel(minLibCSS, minZTreeStyleCSS, minWideCSS, minLibJS, minWideJS)))

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,9 @@
"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",
"check_version": "Checking update",
"new_version_available": "new version available",
"go_env": "Go",
@ -114,12 +118,13 @@
"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",
"close_other": "Close Other",
"clear": "Clear",
"preference": "Preference",
"perference": "Perference",
"appearence": "Appearence",
"gotool": "Go Tool",
"user": "User",
@ -132,6 +137,7 @@
"clearOutput": "Clear Output",
"export": "Export",
"refresh": "Refresh",
"import": "Import",
"theme": "Theme",
"tab_size": "Tab Size",
"copy_file_path": "Copy File Path",
@ -150,8 +156,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 +170,6 @@
"restore_outline": "Restore Outline",
"share": "Share",
"url": "URL",
"embeded": "Embeded",
"terms": "Terms",
"download": "Download",
"decompress": "Decompress",
"keymap": "Keymap",
"resize": "Resize",
"sponsor": "Sponsor"
"short_url": "Short URL",
"embeded": "Embeded"
}

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,9 @@
"start-install": "[go install] 開始",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"start-get": "[go get] 開始",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失敗",
"check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります",
"go_env": "Go",
@ -114,17 +118,18 @@
"team": "チーム",
"sing_up_error": "登録に失敗しました",
"user_name_ruler": "16の長さからなる_ AZ、AZ、0-9、によってユーザ名のみ",
"invalid_email": "無効な電子メール",
"password_no_match": "一貫性のないパスワード入力",
"discard": "あきらめる",
"close": "クローズ",
"close_other": "閉じるその他",
"clear": "空の",
"preference": "環境設定",
"perference": "偏好设定",
"appearence": "エクステリア",
"gotool": "Go ツール",
"user": "ユーザー",
"font": "フォント",
"font_size": "フォントサイズ",
"font_size": "フォント·サイズ",
"line_height": "行の高さ",
"go_format": "Go フォーマット",
"locale": "ロケール",
@ -132,8 +137,9 @@
"clearOutput": "空の出力",
"export": "輸出",
"refresh": "リフレッシュ",
"import": "インポート",
"theme": "テーマ",
"tab_size": "Tab サイズ",
"tab_size": "Tab·サイズ",
"copy_file_path": "ファイルパスをコピー",
"file_tree": "ファイルツリー",
"select": "選択する",
@ -142,7 +148,7 @@
"edit": "編集",
"undo": "元に戻す",
"redo": "やり直し",
"cut": "切り取り",
"cut": "Cut",
"copy": "コピー",
"paste": "貼り付け",
"select_all": "すべて選択",
@ -150,22 +156,20 @@
"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": "リンク",
"embeded": "埋め込む",
"terms": "利用規約",
"download": "ダウンロード",
"decompress": "解凍する",
"keymap": "キーマップ",
"resize": "サイズ変更",
"sponsor": "スポンサー"
"short_url": "ショートリンク",
"embeded": "埋め込む"
}

View File

@ -1,171 +0,0 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "언제 어디서나 골란 놀기",
"cancel": "취소",
"file": "문서",
"login": "로그인",
"username": "아이디",
"current_user": "현제 접속자 ",
"current_session": "현제 세션",
"password": "비밀번호",
"login_error": "로그인 실패",
"run": "실행",
"debug": "디버깅",
"help": "도움말",
"check_update": "업데이트 확인",
"issues": "문제",
"wide_doc": "Wide 문서",
"about": "about",
"start_page": "시작 페이지",
"create_file": "새문서",
"create": "새로만들기",
"create_dir": "새폴더",
"delete": "삭제",
"rename": "이름변경",
"save": "저장",
"exit": "끝내기",
"close_all_files": "모두 닫기",
"save_all_files": "일괄저장",
"format": "포멧",
"goinstall": "go install",
"build": "빌드",
"build_n_run": "빌드후실행",
"editor": "편집기",
"max_editor": "최대화",
"restore_editor": "복구",
"unread_notification": "읽지않은 공지",
"notification_2": "[gocode] 를 찾지 못하였습니다. 자동완성기능이 동작하지 않습니다. ",
"notification_3": "[ide_stub] 를 찾지 못하였습니다. 찾기 기능이 동작하지 않습니다. ",
"notification_4": "서버 오류",
"goto_line": "라인이동",
"goto_file": "문서오픈",
"go": "이동",
"tip": "팁",
"confirm": "확인",
"stop": "정지",
"output": "출력",
"search": "검색",
"notification": "알림",
"min": "최소화",
"restore_side": "좌측창 복구",
"search_text": "문서검색",
"find": "검색",
"find_next": "다음검색",
"find_previous": "이전검색",
"replace": "바꾸기",
"replace_all": "전부바꾸기",
"restore_bottom": "아래창 복구",
"file_format": "확장자",
"keyword": "키워드",
"user_guide": "이용가이드",
"dev_guide": "개발 가이드",
"keyboard_shortcuts": "단축키",
"ver": "버전",
"current_ver": "현재버전",
"dev_team": "개발단체",
"donate": "기부",
"confirm_save": "정장했는지 확인해 주세요. ",
"workspace": "작업창",
"project_address": "항목주소",
"community": "커뮤니티",
"autocomplete": "자동완성",
"jump_to_decl": "알림으로 이동",
"show_expr_info": "Expression 보기",
"find_usages": "사용법찾기",
"delete_line": "현재 줄 삭제",
"copy_lines_up": "위로 복사",
"copy_lines_down": "아래로 복사",
"move_lines_up": "위로이동",
"move_lines_down": "아래로 이동",
"save_editor_file": "현재 편집하고있던 문서 저장",
"save_all_editors_files": "일괄저장",
"close_editor": "편집창 닫기",
"full_screen": "편집기 전체화면",
"auto_indent": "자동 정렬",
"indent": "안쪽이동",
"unindent": "밖으로 이동",
"focus": "포커스",
"switch_tab": "편집기편집/창편집 tab",
"focus_editor": "편집창으로 포커스",
"focus_file_tree": "프로젝트트리로 포커스이동",
"focus_output": "output 으로 포커스 이동",
"focus_search": "검색창으로 포커스 이동",
"focus_notification": "알림창으로 포커스 이동",
"start-build": "시작 [go build]",
"build-succ": "[go build] 성공",
"build-error": "[go build] 실패",
"start-test": "시작 [go test]",
"test-succ": "[go test] 성공",
"test-error": "[go test] 실패",
"start-install": "시작 [go install]",
"install-succ": "[go install] 성공",
"install-error": "[go install] 실패",
"check_version": "최신버전검색중",
"new_version_available": "최신업데이트 사용 가능",
"go_env": "Go 환경",
"os": "운영체제",
"project": "항목",
"license": "라이센스",
"credits": "감사합니다.",
"uptodate": "최신버전입니다.",
"test": "테스트",
"sign_up": "회원가입",
"team": "단체",
"sing_up_error": "가입실패",
"user_name_ruler": "아이디는 16글자 이하이며 a-z, A-Z, 0-9, _ 만 가능합니다,",
"password_no_match": "비밀번호 오류",
"discard": "취소",
"close": "닫기",
"close_other": "현재창 남기고 닫기",
"clear": "청소",
"preference": "설정",
"appearence": "외관",
"gotool": "Go 도구",
"user": "유저",
"font": "폰트",
"font_size": "폰트크기",
"line_height": "줄간격",
"go_format": "Go 포멧",
"locale": "언어설정",
"apply": "적용",
"clearOutput": "ouput 클리어",
"export": "내보내기",
"refresh": "새로고침",
"theme": "주제",
"tab_size": "Tab 크기",
"copy_file_path": "경로복사",
"file_tree": "트리",
"select": "선택",
"expand": "확장",
"collapse": "축소",
"edit": "편집",
"undo": "취소",
"redo": "복원",
"cut": "잘라내기",
"copy": "복사",
"paste": "붙여넣기",
"select_all": "전체선택",
"select_identifier": "표식선택",
"source": "소스",
"toggle_comment": "주석",
"find_in_files": "문서에서 찾기",
"no_empty": "값을 입력해 주세요.",
"open": "열기",
"search_no_match": "해당 문서를 찾지 못하였습니다.",
"outline": "주제",
"govet": "go vet",
"start-vet": "시작 [go vet]",
"vet-succ": "[go vet] 성공",
"vet-error": "[go vet] 실패",
"restore_outline": "주제복구",
"share": "공유",
"url": "하이퍼링크",
"embeded": "삽입",
"terms": "사용계약",
"download": "다운로드",
"decompress": "압축풀기",
"keymap": "단축키",
"resize": "크기조절",
"sponsor": "후원사"
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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,9 @@
"start-install": "开始 [go install]",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失败",
"start-get": "开始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失败",
"check_version": "正在检查更新",
"new_version_available": "新版本可用",
"go_env": "Go 环境",
@ -114,12 +118,13 @@
"team": "团队",
"sing_up_error": "注册失败",
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成长度为16",
"invalid_email": "无效的电子邮件",
"password_no_match": "密码输入不一致",
"discard": "放弃",
"close": "关闭",
"close_other": "关闭其它",
"clear": "清空",
"preference": "偏好设定",
"perference": "偏好设定",
"appearence": "外观",
"gotool": "Go 工具",
"user": "用户",
@ -132,6 +137,7 @@
"clearOutput": "清空输出",
"export": "导出",
"refresh": "刷新",
"import": "导入",
"theme": "主题",
"tab_size": "Tab 大小",
"copy_file_path": "复制文件路径",
@ -150,22 +156,20 @@
"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": "链接",
"embeded": "嵌入",
"terms": "使用条款",
"download": "下载",
"decompress": "解压缩",
"keymap": "快捷键",
"resize": "调整大小",
"sponsor": "赞助"
"short_url": "短链接",
"embeded": "嵌入"
}

View File

@ -1,106 +1,110 @@
{
"colon": "",
"wide": "Wide",
"wide_title": "隨時隨地玩 golang",
"wide_title": "團隊開發,隨時隨地",
"cancel": "取消",
"file": "",
"login": "登",
"username": "使用者",
"current_user": "當前使用者",
"file": "文件檔",
"login": "登",
"username": "用户名字",
"current_user": "當前用户名",
"current_session": "當前會話",
"password": "密碼",
"login_error": "登入失敗",
"login_error": "登錄失败",
"run": "執行",
"debug": "Debug",
"debug": "除蟲",
"help": "說明書",
"check_update": "檢查更新?",
"check_update": "程式更新?",
"issues": "問題",
"wide_doc": "Wide 說明",
"wide_doc": "Wide 說明文件",
"about": "關於",
"start_page": "開始頁面",
"create_file": "開新檔案",
"create": "建",
"create_dir": "新增資料夾",
"start_page": "起始頁",
"create_file": "建立文件",
"create": "",
"create_dir": "建立目錄",
"delete": "删除",
"rename": "重命名",
"rename": "重命名",
"save": "儲存",
"exit": "離開",
"close_all_files": "關閉所有檔案",
"save_all_files": "儲存所有檔案",
"exit": "退出",
"close_all_files": "關閉所有文件",
"save_all_files": "保存所有文件",
"format": "格式化",
"goget": "go get",
"goinstall": "go install",
"build": "編譯",
"build_n_run": "編譯並執行",
"build": "編譯軟件",
"build_n_run": "編譯軟件並執行",
"editor": "編輯器",
"max_editor": "編輯器最大化",
"restore_editor": "編輯器還原",
"max_editor": "編輯器窗口最大化",
"restore_editor": "編輯器窗口還原",
"unread_notification": "未讀通知",
"notification_2": "没有檢查到 gocode這將會導致「自動完成」失效",
"notification_3": "没有檢查到 ide_stub這將會導致「跳轉到聲明」、「查找使用」失效",
"notification_4": "服器內部錯誤",
"notification_2": "没有檢查到 gocode這將會導致 [自動完成] 失效",
"notification_3": "没有檢查到 ide_stub這將會導致 [跳轉到聲明]、[查找使用] 失效",
"notification_4": "器內部錯誤",
"goto_line": "跳轉到行",
"goto_file": "開啟舊檔",
"goto_file": "打開文件",
"go": "跳到",
"tip": "提示",
"confirm": "確定",
"stop": "停止",
"output": "出",
"search": "搜",
"output": "出",
"search": "搜",
"notification": "通知",
"min": "縮到最小",
"restore_side": "左側窗還原",
"search_text": "尋找",
"min": "最小",
"restore_side": "左側還原",
"search_text": "尋找文本",
"find": "尋找",
"find_next": "尋找下一個",
"find_previous": "尋找上一個",
"replace": "取代",
"replace_all": "取代全部",
"restore_bottom": "底部窗還原",
"replace": "替換",
"replace_all": "替換全部",
"restore_bottom": "底部還原",
"file_format": "文件格式",
"keyword": "關鍵字",
"user_guide": "使用者說明文件",
"dev_guide": "開發說明文件",
"user_guide": "用户指南",
"dev_guide": "開發指南",
"keyboard_shortcuts": "鍵盤快捷鍵",
"ver": "版本",
"current_ver": "當前版本",
"dev_team": "開發團隊",
"donate_us": "愛心捐",
"confirm_save": "請確認所有檔案都已儲存",
"donate_us": "愛心捐",
"confirm_save": "請確認所有文件已保存",
"workspace": "工作空間",
"project_address": "項目地址",
"community": "社區",
"autocomplete": "自動完成",
"autocomplete": "自動補全",
"jump_to_decl": "跳轉到聲明",
"show_expr_info": "查看表達式信息",
"find_usages": "尋找使用",
"delete_line": "删除當前行",
"copy_lines_up": "複製到上一行",
"copy_lines_down": "複製到下一行",
"move_lines_up": "移動到上一行",
"move_lines_down": "移動到下一行",
"save_editor_file": "儲存當前編輯檔案",
"save_all_editors_files": "儲存所有檔案",
"copy_lines_up": "複製到上",
"copy_lines_down": "複製到下",
"move_lines_up": "移動到上",
"move_lines_down": "移動到下",
"save_editor_file": "保存當前編輯器文件",
"save_all_editors_files": "保存所有編輯器文件",
"close_editor": "關閉當前編輯器",
"full_screen": "全螢幕",
"full_screen": "編輯器全螢幕",
"auto_indent": "自動縮進",
"indent": "縮進",
"unindent": "縮進還原",
"focus": "焦點",
"switch_tab": "切換編輯器/視窗组 tab",
"focus_editor": "切換至編輯器",
"focus_file_tree": "切換至檔案樹",
"focus_output": "切換至输出視窗",
"focus_search": "切換至搜索視窗",
"focus_notification": "切換至通知視窗",
"switch_tab": "切換編輯器/窗口组 tab",
"focus_editor": "焦點切換到編輯器",
"focus_file_tree": "焦點切換到文件樹",
"focus_output": "焦點切換到输出窗口",
"focus_search": "焦點切換到搜索窗口",
"focus_notification": "焦點切換到通知窗口",
"start-build": "開始 [go build]",
"build-succ": "[go build] 成功",
"build-error": "[go build] 失",
"build-error": "[go build] 失",
"start-test": "開始 [go test]",
"test-succ": "[go test] 成功",
"test-error": "[go test] 失",
"test-error": "[go test] 失",
"start-install": "開始 [go install]",
"install-succ": "[go install] 成功",
"install-error": "[go install] 失敗",
"install-error": "[go install] 失败",
"start-get": "開始 [go get]",
"get-succ": "[go get] 成功",
"get-error": "[go get] 失败",
"check_version": "正在檢查更新",
"new_version_available": "可用新版本",
"go_env": "Go 環境",
@ -113,17 +117,18 @@
"sign_up": "註冊",
"team": "團隊",
"sing_up_error": "註冊失敗",
"user_name_ruler": "帳號只能由 az, AZ, 0-9, _ 組成長度為16",
"user_name_ruler": "用戶名只能由az, AZ, 0-9, _ 組成長度為16",
"invalid_email": "無效的電子郵件",
"password_no_match": "密碼輸入不一致",
"discard": "棄",
"discard": "棄",
"close": "關閉",
"close_other": "關閉其它",
"clear": "清空",
"preference": "偏好設定",
"perference": "偏好設定",
"appearence": "外觀",
"gotool": "Go 工具",
"user": "使用者",
"font": "字",
"user": "用戶",
"font": "字",
"font_size": "字體大小",
"line_height": "行高",
"go_format": "Go 格式化",
@ -132,40 +137,39 @@
"clearOutput": "清空輸出",
"export": "導出",
"refresh": "刷新",
"import": "導入",
"theme": "主題",
"tab_size": "Tab 大小",
"copy_file_path": "複製檔案位置",
"copy_file_path": "複製文件路徑",
"file_tree": "文件樹",
"select": "選擇",
"expand": "展開",
"collapse": "收起",
"edit": "編輯",
"undo": "復原",
"redo": "回復",
"cut": "剪",
"undo": "撤消",
"redo": "重做",
"cut": "剪",
"copy": "複製",
"paste": "天上",
"paste": "粘貼",
"select_all": "全選",
"select_identifier": "選擇標識符",
"source": "原始碼",
"toggle_comment": "註解",
"find_in_files": "在文件中尋找",
"source": "源代碼",
"toggle_comment": "註釋",
"find_in_files": "在文件中查找",
"email": "電子郵件",
"no_empty": "不能為空",
"open": "開啟",
"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": "連結",
"embeded": "嵌入",
"terms": "使用條款",
"download": "下載",
"decompress": "解壓縮",
"keymap": "快速鍵",
"resize": "調整大小",
"sponsor": "贊助"
"url": "鏈接",
"short_url": "短鏈接",
"embeded": "嵌入"
}

217
log/logs.go Executable file
View File

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

169
log/logs_test.go Normal file
View File

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

288
main.go
View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -19,146 +19,165 @@ import (
"flag"
"html/template"
"io"
"math/rand"
"mime"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"strconv"
"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/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")
confLogLevel := flag.String("log_level", "debug", "this will overwrite Wide.LogLevel 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")
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)
conf.FixedTimeCheckEnv()
session.FixedTimeSave()
session.FixedTimeRelease()
session.FixedTimeReport()
logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
if *confStat {
session.FixedTimeReport()
}
}
// 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.SaveContent))
// 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))
// cross-compilation
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
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))
// 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.GetFiles))
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectory))
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFile))
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFile))
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFile))
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFile))
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFile))
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchText))
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.Find))
// outline
http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutline))
// 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.CreateZip))
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZip))
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.Upload))
// 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.SignUpUser))
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)
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
err := http.ListenAndServe("0.0.0.0:7070", nil)
err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil {
logger.Error(err)
}
@ -167,50 +186,61 @@ 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)
http.NotFound(w, r)
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)
// create a Wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
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,
"session": wideSession, "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()}
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 +248,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 +257,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 +281,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, "session": wSession}
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 +297,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 +319,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 +329,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 +352,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 +431,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-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,
@ -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-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,
@ -17,132 +17,87 @@ 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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["succ"] = false
return
}
sid := args["sid"].(string)
filePath := args["file"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
curDir := filepath.Dir(filePath)
fout, err := os.Create(filePath)
if nil != err {
logger.Error(err)
result.Code = -1
data["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
data["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
data["succ"] = false
return
}
@ -150,166 +105,149 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
data["succ"] = false
return
}
if 0 != result.Code {
if !data["succ"].(bool) {
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)
data["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
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.Error(err)
}
wsChannel.Refresh()
wsChannel.Refresh()
}
}(rand.Int())
}

View File

@ -1,252 +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 output
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/wide/conf"
"github.com/88250/wide/i18n"
"github.com/88250/wide/session"
)
// CrossCompilationHandler handles request of cross compilation.
func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
result := gulu.Ret.NewResult()
defer gulu.Ret.RetResult(w, r, result)
if conf.Wide.ReadOnly {
result.Code = -1
result.Msg = "readonly mode"
return
}
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(uid).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Code = -1
return
}
sid := args["sid"].(string)
filePath := args["path"].(string)
if gulu.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
platform := args["platform"].(string)
goos := strings.Split(platform, "_")[0]
goarch := strings.Split(platform, "_")[1]
curDir := filepath.Dir(filePath)
suffix := ""
if "windows" == goos {
suffix = ".exe"
}
user := conf.GetUser(uid)
goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir
setCmdEnv(cmd, uid)
for i, env := range cmd.Env {
if strings.HasPrefix(env, "GOOS=") {
cmd.Env[i] = "GOOS=" + goos
continue
}
if strings.HasPrefix(env, "GOARCH=") {
cmd.Env[i] = "GOARCH=" + goarch
continue
}
}
executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable)
name := filepath.Base(curDir) + "-" + goos + "-" + goarch
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Code = -1
return
}
if 0 != result.Code {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [go build]" in front-end browser
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
channelRet["cmd"] = "start-build"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Code = -1
return
}
go func(runningId int) {
defer gulu.Panic.Recover(nil)
defer cmd.Wait()
// read all
buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "cross-build"
channelRet["executable"] = executable
channelRet["name"] = name
if 0 == len(buf) { // build success
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
} else { // build error
// build gutter lint
errOut := string(buf)
lines := strings.Split(errOut, "\n")
// path process
var errOutWithPath string
for _, line := range lines {
errOutWithPath += parsePath(curDir, line) + "\n"
}
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" +
"<span class='stderr'>" + errOutWithPath + "</span>"
// lint process
if lines[0][0] == '#' {
lines = lines[1:] // skip the first line
}
lints := []*Lint{}
for _, line := range lines {
if len(line) < 1 {
continue
}
if line[0] == '\t' {
// append to the last lint
last := len(lints)
msg := lints[last-1].Msg
msg += line
lints[last-1].Msg = msg
continue
}
file := line[:strings.Index(line, ":")]
left := line[strings.Index(line, ":")+1:]
index := strings.Index(left, ":")
lineNo := 0
msg := left
if index >= 0 {
lineNo, err = strconv.Atoi(left[:index])
if nil != err {
continue
}
msg = left[index+2:]
}
lint := &Lint{
File: filepath.Join(curDir, file),
LineNo: lineNo - 1,
Severity: lintSeverityError,
Msg: msg,
}
lints = append(lints, lint)
}
channelRet["lints"] = lints
}
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

148
output/get.go Normal file
View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["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
data["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
data["succ"] = false
return
}
if 0 != result.Code {
if !data["succ"].(bool) {
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
data["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,12 +183,12 @@ 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)
if nil != err {
logger.Warn(err)
logger.Error(err)
}
wsChannel.Refresh()

25
output/namespace.go Normal file
View File

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

35
output/namespace_linux.go Normal file
View File

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

View File

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

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -15,16 +15,243 @@
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 = 128 // 128 string(rune)
outputTimeout = 100 // 100ms
)
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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
}
filePath := args["executable"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command(filePath)
cmd.Dir = curDir
if conf.Docker {
SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
data["succ"] = false
}
wsChannel := session.OutputWS[sid]
channelRet := map[string]interface{}{}
if !data["succ"].(bool) {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
go func() {
buf := outputBuf{}
for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
if nil != err {
// remove the exited process from user process set
Processes.Remove(wSession, cmd.Process)
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
break
}
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.OutputWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&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.Error(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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
return
}
Processes.Kill(wSession, pid)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["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
data["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
data["succ"] = false
return
}
if 0 != result.Code {
if !data["succ"].(bool) {
return
}
@ -95,7 +95,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
logger.Error(err)
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
data["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)
}
@ -140,7 +140,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
logger.Error(err)
}
wsChannel.Refresh()

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["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
data["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
data["succ"] = false
return
}
if 0 != result.Code {
if !data["succ"].(bool) {
return
}
@ -95,7 +95,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
logger.Error(err)
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
data["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)
}
@ -140,7 +140,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
logger.Error(err)
}
wsChannel.Refresh()

3773
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

60
pkg.sh
View File

@ -1,15 +1,15 @@
#!/bin/bash
# Wide package tool.
#
#
# Command:
# ./pkg.sh ${version} ${target}
# Example:
# ./pkg.sh 1.0.0 /home/daniel/1.0.0/
# ./pkg.sh 1.0.1 /home/daniel/1.0.1/
ver=$1
target=$2
list="conf doc i18n static views README.md TERMS.md LICENSE"
list="conf doc i18n static views README.md LICENSE"
mkdir -p ${target}
@ -20,61 +20,43 @@ echo target=${target}
os=darwin
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
export GOARCH=386
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
rm -f wide
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
export GOARCH=amd64
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
rm -f wide
## linux
os=linux
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
export GOARCH=386
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
rm -f wide
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
export GOARCH=amd64
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
tar zcf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} gotools gocode wide --exclude-vcs --exclude='conf/*.go' --exclude='i18n/*.go'
rm -f wide gotools gocode
tar zcvf ${target}/wide-${ver}-${GOOS}-${GOARCH}.tar.gz ${list} wide --exclude-vcs --exclude conf/*.go --exclude i18n/*.go
rm -f wide
## windows
os=windows
export GOOS=${os}
export GOARCH=amd64
echo wide-${ver}-${GOOS}-${GOARCH}.zip
export GOARCH=386
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe
export GOOS=${os}
export GOARCH=386
echo wide-${ver}-${GOOS}-${GOARCH}.zip
export GOARCH=amd64
go build
go build github.com/visualfc/gotools
go build github.com/stamblerre/gocode
zip -r -q ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} gotools.exe gocode.exe wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe gotools.exe gocode.exe
zip -r ${target}/wide-${ver}-${GOOS}-${GOARCH}.zip ${list} wide.exe --exclude=conf/*.go --exclude=i18n/*.go
rm -f wide.exe

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,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-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,
@ -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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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,31 +42,28 @@ 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
data["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()
out, err := cmd.CombinedOutput()
data["output"] = template.HTML(string(out))
if nil != err {
result.Code = -1
data["succ"] = false
return
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
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
data["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
data["succ"] = false
return
}
@ -77,9 +72,6 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
code = string(output)
}
data := map[string]interface{}{}
result.Data = &data
data["code"] = code
// Step2. generate file name
@ -90,13 +82,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
data["succ"] = false
return
}
}
// ShortURLHandler handles request of short URL.
func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
session, _ := session.HTTPSession.Get(r, "wide-session")
if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
return
}
url := args["url"].(string)
resp, _ := http.Post("http://dwz.cn/create.php", "application/x-www-form-urlencoded",
strings.NewReader("url="+url))
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
logger.Error(err)
data["succ"] = false
return
}
shortURL := url
if 0 == response["status"].(float64) {
shortURL = response["tinyurl"].(string)
}
data["shortURL"] = shortURL
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -26,40 +26,48 @@ 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)
// create a wide session
rand.Seed(time.Now().UnixNano())
sid := strconv.Itoa(rand.Int())
wideSession := session.WideSessions.New(httpSession, sid)
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 {
@ -84,19 +92,19 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
}
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"sid": session.WideSessions.GenId(), "pathSeparator": conf.PathSeparator,
"codeMirrorVer": conf.CodeMirrorVer,
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"session": wideSession, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"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-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,
@ -15,16 +15,239 @@
package playground
import (
"github.com/88250/wide/session"
"bufio"
"encoding/json"
"math/rand"
"net/http"
"os/exec"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/output"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
const (
outputBufMax = 128 // 128 string(rune)
outputTimeout = 100 // 100ms
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) {
session.RunHandler(w, r, session.PlaygroundWS)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
}
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
}
filePath := args["executable"].(string)
cmd := exec.Command(filePath)
if conf.Docker {
output.SetNamespace(cmd)
}
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
data["succ"] = false
}
outReader := bufio.NewReader(stdout)
errReader := bufio.NewReader(stderr)
if err := cmd.Start(); nil != err {
logger.Error(err)
data["succ"] = false
}
wsChannel := session.PlaygroundWS[sid]
channelRet := map[string]interface{}{}
if !data["succ"].(bool) {
if nil != wsChannel {
channelRet["cmd"] = "run-done"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
return
}
channelRet["pid"] = cmd.Process.Pid
// add the process to user's process set
output.Processes.Add(wSession, cmd.Process)
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
// push once for front-end to get the 'run' state and pid
if nil != wsChannel {
channelRet["cmd"] = "run"
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
return
}
wsChannel.Refresh()
}
go func() {
buf := outputBuf{}
for {
wsChannel := session.PlaygroundWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune()
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
buf.content += oneRuneStr
if nil != err {
// remove the exited process from user process set
output.Processes.Remove(wSession, cmd.Process)
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
break
}
now := time.Now().UnixNano() / int64(time.Millisecond)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Error(err)
break
}
wsChannel.Refresh()
}
}
}()
buf := outputBuf{}
for {
r, _, err := errReader.ReadRune()
wsChannel := session.PlaygroundWS[sid]
if nil != err || nil == wsChannel {
break
}
oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&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.Error(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)
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
data["succ"] = false
return
}
sid := args["sid"].(string)
pid := int(args["pid"].(float64))
wSession := session.WideSessions.Get(sid)
if nil == wSession {
data["succ"] = false
return
}
output.Processes.Kill(wSession, pid)
}

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-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,
@ -25,21 +25,17 @@ package session
import (
"bytes"
"encoding/json"
"math/rand"
"net/http"
"os"
"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/gorilla/sessions"
"github.com/gorilla/websocket"
)
@ -47,12 +43,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,13 +71,12 @@ 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
State int // state
Content *conf.LatestSessionContent // the latest session content
FileWatcher *fsnotify.Watcher // files change watcher
Created time.Time // create time
Updated time.Time // the latest use time
}
@ -105,15 +98,13 @@ 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)
for _ = range time.Tick(time.Hour) {
hour, _ := time.ParseDuration("-30m")
threshold := time.Now().Add(hour)
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 +115,7 @@ func FixedTimeRelease() {
// Online user statistic report.
type userReport struct {
userId string
username string
sessionCnt int
processCnt int
updated time.Time
@ -132,15 +123,13 @@ 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)
for _ = range time.Tick(10 * time.Minute) {
users := userReports{}
processSum := 0
@ -149,7 +138,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 +146,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 +165,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
}
}
@ -192,22 +181,26 @@ func (f userReports) Len() int { return len(f) }
func (f userReports) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].processCnt }
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
)
// WSHandler handles request of creating session channel.
//
// When a channel closed, releases all resources associated with it.
func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0]
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
wSession = WideSessions.New(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
}
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
@ -220,52 +213,15 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
SessionWS[sid] = &wsChan
wSession := WideSessions.Get(sid)
if nil == wSession {
httpSession, _ := HTTPSession.Get(r, CookieName)
if httpSession.IsNew {
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.UserId)
}
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
input := map[string]interface{}{}
wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait))
wsChan.Conn.SetPongHandler(func(string) error { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
ticker := time.NewTicker(pingPeriod)
defer func() {
WideSessions.Remove(sid)
ticker.Stop()
wsChan.Close()
}()
// send websocket ping message.
go func(t *time.Ticker, channel util.WSChannel) {
for {
select {
case <-t.C:
if err := channel.Conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}(ticker, wsChan)
for {
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)
WideSessions.Remove(sid)
return
}
@ -282,10 +238,10 @@ 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)
// SaveContent handles request of session content string.
func SaveContent(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
args := struct {
Sid string
@ -294,14 +250,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
data["succ"] = false
return
}
wSession := WideSessions.Get(args.Sid)
if nil == wSession {
result.Code = -1
data["succ"] = false
return
}
@ -309,7 +265,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
@ -334,11 +290,30 @@ func (s *WideSession) Refresh() {
s.Updated = time.Now()
}
// GenId generates a wide session id.
func (sessions *wSessions) GenId() string {
rand.Seed(time.Now().UnixNano())
// New creates a wide session.
func (sessions *wSessions) New(httpSession *sessions.Session, sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
return strconv.Itoa(rand.Int())
now := time.Now()
// create user event queue
userEventQueue := event.UserEventQueues.New(sid)
ret := &WideSession{
ID: sid,
Username: httpSession.Values["username"].(string),
HTTPSession: httpSession,
EventQueue: userEventQueue,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
return ret
}
// Get gets a wide session with the specified session id.
@ -362,7 +337,6 @@ func (sessions *wSessions) Get(sid string) *WideSession {
// 1. user event queue
// 2. process set
// 3. websocket channels
// 4. file watcher
func (sessions *wSessions) Remove(sid string) {
mutex.Lock()
defer mutex.Unlock()
@ -378,9 +352,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)
}
}
@ -405,19 +379,14 @@ func (sessions *wSessions) Remove(sid string) {
delete(PlaygroundWS, sid)
}
// file watcher
if nil != s.FileWatcher {
s.FileWatcher.Close()
}
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,130 +394,17 @@ 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)
}
}
return ret
}
// new creates a wide session.
func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
uid := httpSession.Values["uid"].(string)
now := time.Now()
ret := &WideSession{
ID: sid,
UserId: uid,
HTTPSession: httpSession,
EventQueue: nil,
State: sessionStateActive,
Content: &conf.LatestSessionContent{},
Created: now,
Updated: now,
}
*sessions = append(*sessions, ret)
if "playground" == uid {
return ret
}
// create user event queue
ret.EventQueue = event.UserEventQueues.New(sid)
// add a filesystem watcher to notify front-end after the files changed
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Error(err)
return ret
}
go func() {
defer gulu.Panic.Recover(nil)
for {
ch := SessionWS[sid]
if nil == ch {
return // release this goroutine
}
select {
case event := <-watcher.Events:
path := filepath.ToSlash(event.Name)
dir := filepath.ToSlash(filepath.Dir(path))
ch = SessionWS[sid]
if nil == ch {
return // release this goroutine
}
logger.Trace(event)
if event.Op&fsnotify.Create == fsnotify.Create {
fileType := "f"
if gulu.File.IsDir(path) {
fileType = "d"
if err = watcher.Add(path); nil != err {
logger.Warn(err, path)
}
}
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
ch.WriteJSON(&cmd)
}
case err := <-watcher.Errors:
if nil != err {
logger.Error("File watcher ERROR: ", err)
}
}
}
}()
go func() {
defer gulu.Panic.Recover(nil)
workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
return filepath.SkipDir
}
if f.IsDir() {
if err = watcher.Add(dirPath); nil != err {
logger.Error(err, dirPath)
}
logger.Tracef("File watcher added a dir [%s]", dirPath)
}
return nil
})
}
ret.FileWatcher = watcher
}()
return ret
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-present, 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,
@ -15,24 +15,30 @@
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"
emailExists = "email exists"
userCreated = "user created"
userCreateError = "user create error"
)
@ -42,82 +48,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)
succ := true
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
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
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
succ = false
return
}
@ -125,12 +118,16 @@ 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 +137,154 @@ 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
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
succ := true
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
args := struct {
Username string
Password string
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error("login error: ", err)
succ = false
return
}
succ = false
for _, user := range conf.Users {
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
succ = true
break
}
}
if !succ {
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// SignUpUser handles request of registering user.
func SignUpUser(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
dir := filepath.Dir(firstUserWorkspace)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": dir,
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
t, err := template.ParseFiles("views/sign_up.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as add user request
succ := true
data := map[string]interface{}{"succ": &succ}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
succ = false
return
}
username := args["username"].(string)
password := args["password"].(string)
email := args["email"].(string)
msg := addUser(username, password, email)
if userCreated != msg {
succ = false
data["msg"] = msg
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
}
// FixedTimeSave saves online users' configurations periodically (1 minute).
@ -158,57 +292,35 @@ 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)
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 {
path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(userId)
workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces {
if strings.HasPrefix(path, workspace) {
return true
}
}
return false
}
// SaveOnlineUsers saves online users' configurations at once.
func SaveOnlineUsers() {
users := getOnlineUsers()
for _, u := range users {
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 +330,133 @@ 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 "playground" == username {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Name) == strings.ToLower(username) {
return userExists
}
if strings.ToLower(user.Email) == strings.ToLower(email) {
return emailExists
}
}
firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name)
dir := filepath.Dir(firstUserWorkspace)
workspace := filepath.Join(dir, username)
newUser := conf.NewUser(username, password, email, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
logger.Infof("Created a user [%s]", username)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}

196
shell/shells.go Normal file
View File

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

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -58,7 +58,7 @@
}
#dialogAbout .thx a {
width: 80px;
width: 100px;
display: inline-block;
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,12 +14,6 @@
* limitations under the License.
*/
/*
* themes for base.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.2.0.0, Oct 5, 2018
*/
/* start reset & function */
::-webkit-scrollbar {
background: none;
@ -140,24 +134,17 @@ 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 {
font-family: 'icomoon';
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@ -166,140 +153,173 @@ 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-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-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,
@ -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
*/
@ -52,7 +52,7 @@
.dialog-header-bg {
height: 23px;
background-color: #bbb;
background-color: #CCD5E5;
cursor: move;
width: 100%;
}

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

Binary file not shown.

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

@ -6,50 +6,51 @@
<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="&#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: 30 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

@ -0,0 +1,99 @@
/*
* Copyright (c) 2014-2015, b3log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
body {
overflow: auto;
}
.header {
margin: 0;
padding: 5px;
}
.header li {
margin-left: 10px;
}
.header .gravatar {
width: 26px;
border-radius: 13px;
}
.header .logo {
height: 21px;
margin-top: -6px;
}
.share-panel {
position: absolute;
z-index: 20;
width: 226px;
padding: 5px 0px;
right: 0px;
line-height: normal;
top: 34px;
}
.share-panel .font-ico {
transition: all .2s ease-out 0s;
margin: 0 5px;
width: 24px;
}
.share-panel .font-ico:hover {
transform:rotate(360deg);
}
.footer {
height: 30px;
text-shadow: 0 0 0;
}
#editorDiv {
width: 100%;
height: 70%;
}
#output {
height: 30%;
width: 100%;
border-width: 0;
margin: 0;
padding: 0;
border-top: 1px solid #919191;
}
#dialogShare {
margin: 10px 15px 0;
line-height: 28px;
}
#dialogShare a {
white-space: pre-wrap;
word-wrap: break-word;
}
.wrapper {
width: auto;
}
.btn {
padding: 1px 5px;
}
#disqus_thread {
border-top: 1px solid #919191;
padding: 0 30px;
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -13,27 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
body {
display: flex;
flex-direction: column;
max-height: 100vh;
}
.main {
flex: 1;
min-height: 1px;
display: flex;
overflow: auto;
}
.header {
margin: 0;
padding: 5px;
padding: 10px;
}
.header li {
margin-left: 0;
margin-right: 20px;
.font-ico {
font-size: 26px;
}
.header .gravatar {
@ -41,25 +31,23 @@ body {
border-radius: 13px;
}
.header .logo {
height: 28px;
margin-top: -6px;
.header > .fn-right {
margin-top: 10px;
}
.header .font-ico {
font-size: 18px;
line-height: 28px;
.header .logo {
height: 36px;
margin-top: -4px;
}
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
padding: 5px 0px;
width: 258px;
padding: 10px 10px;
right: 0px;
line-height: normal;
top: 38px;
border-color: #999;
top: 57px;
}
.share-panel .font-ico {
@ -69,7 +57,7 @@ body {
}
.share-panel .font-ico:hover {
transform: rotate(360deg);
transform:rotate(360deg);
}
.footer {
@ -78,21 +66,17 @@ body {
}
#editorDiv {
width: 100%;
height: 70%;
}
.bottom-window-group {
height: 30%;
width: 60%;
float: left;
}
#output {
height: 100%;
width: 100%;
width: 40%;
float: right;
border-width: 0;
margin: 0;
padding: 2px 5px;
border-top: 1px solid #919191;
padding: 0;
border-left: 1px solid #919191;
}
#dialogShare {
@ -105,54 +89,7 @@ body {
word-wrap: break-word;
}
.wrapper {
width: auto;
}
.btn {
padding: 3px 8px;
font-size: 13px;
line-height: 20px;
height: 28px;
}
#editorDivWrap {
width: 70%;
}
#goNews {
width: 30%;
overflow: auto;
border-left: 1px solid #919191;
}
#goNews::-webkit-scrollbar {
display: none;
}
#goNews li a {
display: block;
padding: 8px 10px;
text-shadow: 0 1px 0 #fff;
border-bottom: 1px solid #eee;
color: #666;
}
#goNews li a.fn-right {
padding: 0;
border: 0;
color: #4285f4;
}
#goNews li a:hover {
text-decoration: none;
background-color: #f9f9f9;
}
#goNews li.title {
border-bottom: 1px solid #e5e5e5;
padding: 10px;
font-size: 14px;
line-height: 18px;
background-color: #f9fafb;
#disqus_thread {
border-top: 1px solid #919191;
padding: 0 30px;
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,35 +14,41 @@
* limitations under the License.
*/
/*
* themes for side
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
/* start side */
.side {
width: 20%;
position: absolute;
height: 100%;
z-index: 8;
flex-flow: column;
display: flex;
}
.side-max {
width: 100%;
z-index: 11;
}
.side .tabs-panel {
overflow: auto;
}
/* end side */
/* start side right */
.side-right .tabs-panel > div {
overflow: auto;
.side-right {
height: 70%;
position: absolute;
right: 0;
width: 20%;
z-index: 8;
}
.side-right {
flex-flow: column;
.side-right-max {
width: 100%;
height: 100%;
z-index: 11;
}
.side-right .tabs-panel > div {
overflow: auto;
}
#outline .ico {
@ -82,8 +88,6 @@
.ztree {
width: 100%;
padding: 0;
outline: 0px;
border: 0px;
}
.ztree li a.curSelectedNode {

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -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,13 @@ 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;
}
.content h2 {
@ -75,104 +60,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 {
@ -180,35 +155,27 @@ body {
color: #777;
font-size: 12px;
text-align: center;
position: relative;
}
.footer a {
text-decoration: none;
color: #4285f4;
color: #4183c4;
}
.footer a:hover {
text-decoration: underline;
}
.footer .github-btns {
height: 25px;
position: absolute;
top: 5px;
right: 0;
}
/* start sign up */
.dir {
color: #4285f4;
color: #4183c4;
font-size: 18px;
word-wrap: break-word;
margin-top: 20px;
}
#dir {
color: #999;
color: #999;
font-size: 13px;
}
@ -221,21 +188,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 */
/* end sign up */

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -32,13 +32,14 @@
}
#startPage .title {
background-color: #BBB;
background-color: #9F9F9F;
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, 0.4);
}
#startPage .details {
@ -70,8 +71,6 @@
border-left: 1px solid #f1f1f1;
margin-left: 10%;
padding-left: 10%;
white-space: nowrap;
overflow: hidden;
}
#startPage .news li {

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,18 +14,14 @@
* limitations under the License.
*/
/*
* themes for dark.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
.side {
background-color: #303130;
border-right: 1px solid #000;
}
.side-right {
background-color: #303130;
border-left: 1px solid #000;
color: #999;
}
@ -40,13 +36,11 @@
color: #EBEBEB;
}
.font-ico,
.ico-restore {
.font-ico {
color: rgba(255, 255, 255, 0.5);
}
.font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico {
color: #e1e1e1;
}
@ -147,33 +141,15 @@
color: #e1e1e1;
}
.bottom-window-group .tabs-panel {
.bottom-window-group {
border-top: 1px solid #000;
}
.bottom-window-group .tabs-panel > div{
background-color: #181818;
color: #ebebeb;
}
#dialogPreference .preference {
border: 1px solid #000;
}
/* start layout resize */
.ui-layout-resizer,
.ui-layout-resizer-dragging,
.ui-layout-resizer-open-hover,
.ui-layout-resizer-dragging {
background: #000;
}
.ui-layout-resizer-closed {
background-color: #555555;
}
.ui-layout-resizer-west-closed {
border-right: 1px solid #000;
}
.ui-layout-resizer-south-closed {
border-top: 1px solid #000;
}
.ui-layout-resizer-east-closed {
border-left: 1px solid #000;
}
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
background: #000;
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,19 +14,14 @@
* limitations under the License.
*/
/*
* themes for default.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
.side {
background-color: #FFF;
border-right: 1px solid #919191;
}
.side-right {
background-color: #FFF;
border-left: 1px solid #919191;
}
.footer {
@ -39,13 +34,11 @@
background-color: #FFF;
}
.font-ico,
.ico-restore {
.font-ico {
color: #666;
}
.font-ico:hover,
.ico-restore:hover,
.frame li:hover .font-ico {
color: #333;
}
@ -129,27 +122,10 @@
background-color: #F0F0F0;
}
#dialogPreference .preference {
border: 1px solid #A4A4A4;
}
/* start layout resize */
.ui-layout-resizer,
.ui-layout-resizer-dragging {
background: #bbb;
}
.ui-layout-resizer-open-hover , /* hover-color to 'resize' */
.ui-layout-resizer-dragging { /* resizer beging 'dragging' */
background: #919191;
}
.ui-layout-resizer-west-closed {
border-right: 1px solid #919191;
}
.ui-layout-resizer-south-closed {
.bottom-window-group {
border-top: 1px solid #919191;
}
.ui-layout-resizer-east-closed {
border-left: 1px solid #919191;
}
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
background: #919191;
#dialogPreference .preference {
border: 1px solid #A4A4A4;
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,12 +14,6 @@
* limitations under the License.
*/
/*
* themes for wide.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.1, Dec 15, 2015
*/
/* start frame */
.frame {
position: absolute;
@ -83,20 +77,9 @@
.tabs > div > span.changed {
font-weight: bold;
}
.tabs-panel {
overflow: auto;
flex: 1;
height: 100%;
}
/* end tabs */
/* start menu */
.menu {
display: block !important;
}
.menu > ul > li {
float: left;
}
@ -136,10 +119,9 @@
.share-panel {
position: absolute;
z-index: 20;
width: 190px;
width: 226px;
padding: 5px 0;
right: 0px;
top: 21px;
}
.share-panel .font-ico {
@ -161,8 +143,6 @@
width: 60%;
height: 70%;
overflow: hidden;
flex-flow: column;
display: flex;
}
.toolbars {
@ -204,7 +184,6 @@
.CodeMirror,
.CodeMirror-hints {
font-family: Consolas, 'Courier New', monospace;
}
.CodeMirror-hints .ico {
@ -239,8 +218,27 @@
/* start bottom-window-group */
.bottom-window-group {
width: 80%;
position: absolute;
left: 20%;
width: 80%;
height: 30%;
top: 70%;
z-index: 7;
background-color: #fff;
flex-flow: column;
}
.bottom-window-group-max {
height: 100%;
left: 0;
top: 0;
width: 100%;
z-index: 11;
border-top-width: 0 !important;
}
.bottom-window-group > div > div {
overflow: auto;
}
.bottom-window-group .output {
@ -249,7 +247,6 @@
line-height: 16px;
font-size: 12px;
overflow-x: scroll;
outline: 0;
}
.bottom-window-group .output pre {
@ -259,19 +256,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;
}
@ -295,29 +295,23 @@
line-height: 19px;
}
.bottom-window-group .notification {
outline: 0;
}
.bottom-window-group .notification .type,
.bottom-window-group .notification .severity {
width: 50px;
padding: 0 5px;
}
.bottom-window-group .search {
display: flex;
flex-flow: column;
outline: 0;
}
/* end bottom-window-group */
/* start footer */
.footer {
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.57);
padding-left: 5px;
height: 19px;
line-height: 18px;
display: block !important;
bottom: 0;
z-index: 11;
width: 100%;
}
.footer .cursor {

View File

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

Binary file not shown.

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-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,
@ -14,13 +14,6 @@
* limitations under the License.
*/
/*
* @file bottomGroup.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.1.1, Mar 15, 2017
*/
var bottomGroup = {
tabs: undefined,
searchTab: undefined,
@ -82,7 +75,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-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,
@ -14,17 +14,11 @@
* limitations under the License.
*/
/*
* @file dialog.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 1.0.0.1, Dec 8, 2015
*/
(function ($) {
$.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-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,
@ -14,15 +14,7 @@
* limitations under the License.
*/
/*
* @file editor.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.1.0, Jan 12, 2016
*/
var editors = {
autocompleteMutex: false,
data: [],
tabs: {},
getEditorByPath: function (path) {
@ -33,7 +25,7 @@ var editors = {
}
},
close: function () {
$('.edit-panel .tabs > div[data-index="' + $('.edit-panel .frame').data('index') + ']').find('.ico-close').click();
$(".edit-panel .tabs > div[data-index=" + $(".edit-panel .frame").data("index") + "]").find(".ico-close").click();
},
closeOther: function () {
var currentIndex = $(".edit-panel .frame").data("index");
@ -51,14 +43,14 @@ var editors = {
var firstIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData);
// 开始关闭
$('.edit-panel .tabs > div[data-index="' + firstIndex + '"]').find(".ico-close").click();
$(".edit-panel .tabs > div[data-index=" + firstIndex + "]").find(".ico-close").click();
},
_removeAllMarker: function () {
var removeData = $("#dialogCloseEditor").data("removeData");
if (removeData && removeData.length > 0) {
var removeIndex = removeData.splice(0, 1);
$("#dialogCloseEditor").data("removeData", removeData);
$('.edit-panel .tabs > div[data-index="' + removeIndex + '"] .ico-close').click();
$(".edit-panel .tabs > div[data-index=" + removeIndex + "] .ico-close").click();
}
if (wide.curEditor) {
wide.curEditor.focus();
@ -105,7 +97,7 @@ var editors = {
"afterInit": function () {
$("#dialogCloseEditor button.save").click(function () {
var i = $("#dialogCloseEditor").data("index");
wide.fmt(editors.data[i].id, editors.data[i].editor);
wide.fmt(tree.fileTree.getNodeByTId(editors.data[i].id).path, editors.data[i].editor);
editors.tabs.del(editors.data[i].id);
$("#dialogCloseEditor").dialog("close");
editors._removeAllMarker();
@ -137,9 +129,27 @@ var editors = {
wide.curEditor = undefined;
$(".footer .cursor").text('');
wide.refreshOutline();
return false;
}
// set tree node selected
var node = tree.fileTree.getNodeByTId(id);
tree.fileTree.selectNode(node);
wide.curNode = node;
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === id) {
wide.curEditor = editors.data[i].editor;
break;
}
}
var cursor = wide.curEditor.getCursor();
wide.curEditor.setCursor(cursor);
wide.curEditor.focus();
wide.refreshOutline();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
},
removeBefore: function (id) {
if (id === 'startPage') { // 当前关闭的 tab 是起始页
@ -153,8 +163,8 @@ var editors = {
editors._removeAllMarker();
return true;
} else {
$("#dialogCloseEditor").dialog("open", $('.edit-panel .tabs > div[data-index="'
+ editors.data[i].id + '"] > span:eq(0)').text());
$("#dialogCloseEditor").dialog("open", $(".edit-panel .tabs > div[data-index="
+ editors.data[i].id + "] > span:eq(0)").text());
$("#dialogCloseEditor").data("index", i);
return false;
}
@ -178,22 +188,14 @@ 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']);
// remove selected tree node
tree.fileTree.cancelSelectedNode();
wide.curNode = undefined;
wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
return false;
}
if (!nextId) {
// 编辑器区域不存在打开的 Tab
// 不存在打开的编辑器
// remove selected tree node
tree.fileTree.cancelSelectedNode();
wide.curNode = undefined;
@ -207,6 +209,30 @@ var editors = {
// 关闭的不是当前编辑器
return false;
}
// set tree node selected
var node = tree.fileTree.getNodeByTId(nextId);
tree.fileTree.selectNode(node);
wide.curNode = node;
for (var i = 0, ii = editors.data.length; i < ii; i++) {
if (editors.data[i].id === nextId) {
wide.curEditor = editors.data[i].editor;
break;
}
}
wide.refreshOutline();
var cursor = wide.curEditor.getCursor();
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
}
});
$(".edit-panel .tabs").on("dblclick", function () {
if ($(".toolbars .ico-max").length === 1) {
windows.maxEditor();
} else {
windows.restoreEditor();
}
});
@ -218,7 +244,7 @@ var editors = {
wide.curEditor = undefined;
wide.refreshOutline();
$(".footer .cursor").text('');
var dateFormat = function (time, fmt) {
var date = new Date(time);
var dateObj = {
@ -246,9 +272,10 @@ 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").height($('.side-right').height() - $(".bottom-window-group").children(".tabs").height() - 100);
$("#startPage").load(config.context + '/start?sid=' + config.wideSessionId);
$.ajax({
url: "https://ld246.com/apis/articles?tags=wide,golang&p=1&size=20",
url: "https://symphony.b3log.org/apis/articles?tags=wide,golang&p=1&size=30",
type: "GET",
dataType: "jsonp",
jsonp: "callback",
@ -258,21 +285,20 @@ var editors = {
return;
}
// 按 size = 20 取,但只保留最多 9
// 按 size = 30 取,但只保留最多 10
var length = articles.length;
if (length > 9) {
length = 9;
if (length > 10) {
length = 10;
}
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>"
+ "<a target='_blank' href='"
+ "<a target='_blank' href='http://symphony.b3log.org"
+ article.articlePermalink + "'>"
+ article.articleTitle + "</a>&nbsp; <span class='date'>"
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd');
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd hh:mm');
+"</span></li>";
}
@ -283,12 +309,11 @@ var editors = {
});
},
getCurrentId: function () {
var ret = editors.tabs.getCurrentId();
if (ret === 'startPage') {
ret = null;
var currentId = editors.tabs.getCurrentId();
if (currentId === 'startPage') {
currentId = null;
}
return ret;
return currentId;
},
getCurrentPath: function () {
var currentPath = $(".edit-panel .tabs .current span:eq(0)").attr("title");
@ -299,7 +324,6 @@ var editors = {
},
_initCodeMirrorHotKeys: function () {
CodeMirror.registerHelper("hint", "go", function (editor) {
editor = wide.curEditor; // 使用当前编辑器覆盖实参,因为异步调用的原因,实参不一定正确
var word = /[\w$]+/;
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
@ -320,16 +344,10 @@ var editors = {
var autocompleteHints = [];
if (editors.autocompleteMutex && editor.state.completionActive) {
return;
}
editors.autocompleteMutex = true;
$.ajax({
async: false, // 同步执行
type: 'POST',
url: '/autocomplete',
url: config.context + '/autocomplete',
data: JSON.stringify(request),
dataType: "json",
success: function (data) {
@ -379,15 +397,12 @@ var editors = {
}
}
// 清除未保存状态
editor.doc.markClean();
$(".edit-panel .tabs .current > span:eq(0)").removeClass("changed");
$(".edit-panel .tabs > div.current > span").removeClass("changed");
}
});
setTimeout(function () {
editors.autocompleteMutex = false;
}, 20);
return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
});
@ -396,7 +411,7 @@ var editors = {
if (mode && "go" !== mode.name) {
return CodeMirror.Pass;
}
var token = cm.getTokenAt(cm.getCursor());
if ("comment" === token.type || "string" === token.type) {
@ -435,18 +450,17 @@ var editors = {
$.ajax({
type: 'POST',
url: '/exprinfo',
url: config.context + '/exprinfo',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (0 != result.code) {
success: function (data) {
if (!data.succ) {
return;
}
var position = wide.curEditor.cursorCoords();
$("body").append('<div style="top:'
+ (position.top + 15) + 'px;left:' + position.left
+ 'px" class="edit-exprinfo">' + result.data + '</div>');
+ 'px" class="edit-exprinfo">' + data.info + '</div>');
}
});
};
@ -583,15 +597,13 @@ 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) {
success: function (data) {
if (!data.succ) {
return;
}
var data = result.data;
var tId = tree.getTIdByPath(data.path);
wide.curNode = tree.fileTree.getNodeByTId(tId);
@ -613,15 +625,15 @@ 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) {
success: function (data) {
if (!data.succ) {
return;
}
editors.appendSearch(result.data, 'usages', '');
editors.appendSearch(data.founds, 'usages', '');
}
});
};
@ -723,7 +735,7 @@ var editors = {
},
// 新建一个编辑器 Tab如果已经存在 Tab 则切换到该 Tab.
newEditor: function (data, cursor) {
var id = wide.curNode.id;
var id = wide.curNode.tId;
editors.tabs.add({
id: id,
@ -732,7 +744,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']);
@ -751,12 +763,9 @@ var editors = {
theme: config.editorTheme,
tabSize: config.editorTabSize,
indentUnit: 4,
indentWithTabs: true,
foldGutter: true,
cursorHeight: 1,
path: data.path,
readOnly: wide.curNode.isGOAPI,
profile: 'xhtml', // define Emmet output profile
extraKeys: {
"Ctrl-\\": "autocompleteAnyWord",
".": "autocompleteAfterDot",
@ -795,10 +804,6 @@ var editors = {
}
});
if ("text/html" === data.mode) {
emmetCodeMirror(editor);
}
editor.on('cursorActivity', function (cm) {
$(".edit-exprinfo").remove();
var cursor = cm.getCursor();
@ -806,59 +811,31 @@ var editors = {
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
});
editor.on('focus', function (cm) {
windows.clearFloat();
});
editor.on('blur', function (cm) {
$(".edit-exprinfo").remove();
});
editor.on('changes', function (cm) {
if (cm.doc.isClean()) { // no modification
if (cm.doc.isClean()) {
// 没有修改过
$(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) {
$span.removeClass("changed");
}
});
return;
}
// changed
$(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) {
$span.addClass("changed");
}
});
});
editor.on('keydown', function (cm, evt) {
if (evt.altKey || evt.ctrlKey || evt.shiftKey) {
return;
}
var k = evt.which;
if (k < 48) {
return;
}
// hit [0-9]
if (k > 57 && k < 65) {
return;
}
// hit [a-z]
if (k > 90) {
return;
}
if (config.autocomplete) {
if (0.5 <= Math.random()) {
CodeMirror.commands.autocompleteAfterDot(cm);
}
} else {
// 修改过
$(".edit-panel .tabs > div").each(function () {
var $span = $(this).find("span:eq(0)");
if ($span.attr("title") === cm.options.path) {
$span.addClass("changed");
}
});
}
});
@ -866,10 +843,6 @@ var editors = {
editor.setOption("mode", data.mode);
editor.setOption("gutters", ["CodeMirror-lint-markers", "CodeMirror-foldgutter"]);
if ("wide" !== config.keymap) {
editor.setOption("keyMap", config.keymap);
}
if ("text/x-go" === data.mode || "application/json" === data.mode) {
editor.setOption("lint", true);
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014-present, 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,
@ -14,13 +14,6 @@
* limitations under the License.
*/
/*
* @file hotkeys.js
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.2, Dec 15, 2015
*/
var hotkeys = {
defaultKeyMap: {
// Ctrl-0
@ -43,9 +36,13 @@ var hotkeys = {
which: 49,
fun: function () {
// 有些元素需设置 tabindex 为 -1 时才可以 focus
if (windows.outerLayout.west.state.isClosed) {
windows.outerLayout.slideOpen('west');
if ($(".footer .ico-restore:eq(0)").css("display") === "inline") {
// 当文件树最小化时
$(".side").css({
"left": "0"
});
}
$("#files").focus();
}
},
@ -56,8 +53,11 @@ var hotkeys = {
shiftKey: false,
which: 50,
fun: function () {
if (windows.innerLayout.east.state.isClosed) {
windows.innerLayout.slideOpen('east');
if ($(".footer .ico-restore:eq(2)").css("display") === "inline") {
// 当文件树最小化时
$(".side-right").css({
"right": "0"
});
}
$("#outline").focus();
@ -216,9 +216,9 @@ var hotkeys = {
if (event.altKey === hotKeys.clearWindow.altKey
&& event.which === hotKeys.clearWindow.which) { // Alt-C clear output
bottomGroup.clear('output');
event.preventDefault();
return;
}
});
@ -293,7 +293,7 @@ var hotkeys = {
case 40: // down
var node = {};
if (!wide.curNode) { // select the first one if no node been selected
if (!wide.curNode) { // select the first one if no node been selected
node = tree.fileTree.getNodeByTId("files_1");
} else {
if (wide.curNode && tree.isBottomNode(wide.curNode)) {
@ -426,19 +426,19 @@ var hotkeys = {
|| document.activeElement.className === "search") {
// 焦点在底部窗口组时,对底部进行切换
var tabs = ["output", "search", "notification"],
nextPath = "";
nextId = "";
for (var i = 0, ii = tabs.length; i < ii; i++) {
if (bottomGroup.tabs.getCurrentId() === tabs[i]) {
if (document.activeElement.className === tabs[i]) {
if (i < ii - 1) {
nextPath = tabs[i + 1];
nextId = tabs[i + 1];
} else {
nextPath = tabs[0];
nextId = tabs[0];
}
break;
}
}
bottomGroup.tabs.setCurrent(nextPath);
$(".bottom-window-group ." + nextPath).focus();
bottomGroup.tabs.setCurrent(nextId);
$(".bottom-window-group ." + nextId).focus();
event.preventDefault();
@ -446,16 +446,16 @@ var hotkeys = {
}
if (editors.data.length > 1) {
var nextPath = "";
var nextId = "";
for (var i = 0, ii = editors.data.length; i < ii; i++) {
var currentId = editors.getCurrentId();
if (currentId) {
if (currentId === editors.data[i].id) {
if (i < ii - 1) {
nextPath = editors.data[i + 1].id;
nextId = editors.data[i + 1].id;
wide.curEditor = editors.data[i + 1].editor;
} else {
nextPath = editors.data[0].id;
nextId = editors.data[0].id;
wide.curEditor = editors.data[0].editor;
}
break;
@ -463,10 +463,8 @@ var hotkeys = {
}
}
editors.tabs.setCurrent(nextPath);
var nextTId = tree.getTIdByPath(nextPath);
wide.curNode = tree.fileTree.getNodeByTId(nextTId);
editors.tabs.setCurrent(nextId);
wide.curNode = tree.fileTree.getNodeByTId(nextId);
tree.fileTree.selectNode(wide.curNode);
wide.refreshOutline();
var cursor = wide.curEditor.getCursor();

47
static/js/lib.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -58,9 +58,7 @@
if (inp) {
if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
inp.select();
}
inp.select();
}
if (options.onInput)

View File

@ -0,0 +1,159 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var DEFAULT_BRACKETS = "()[]{}''\"\"";
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
var SPACE_CHAR_REGEX = /\s/;
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseBrackets");
if (!val) return;
var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
if (typeof val == "string") pairs = val;
else if (typeof val == "object") {
if (val.pairs != null) pairs = val.pairs;
if (val.explode != null) explode = val.explode;
}
var map = buildKeymap(pairs);
if (explode) map.Enter = buildExplodeHandler(explode);
cm.addKeyMap(map);
});
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
// Project the token type that will exists after the given char is
// typed, and use it to determine whether it would cause the start
// of a string token.
function enteringString(cm, pos, ch) {
var line = cm.getLine(pos.line);
var token = cm.getTokenAt(pos);
if (/\bstring2?\b/.test(token.type)) return false;
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
stream.pos = stream.start = token.start;
for (;;) {
var type1 = cm.getMode().token(stream, token.state);
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
stream.start = stream.pos;
}
}
function buildKeymap(pairs) {
var map = {
name : "autoCloseBrackets",
Backspace: function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
}
}
};
var closingBrackets = "";
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
closingBrackets += right;
map["'" + left + "'"] = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), type, next;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (!range.empty()) {
curType = "surround";
} else if (left == right && next == right) {
if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
curType = "skipThree";
else
curType = "skip";
} else if (left == right && cur.ch > 1 &&
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) {
curType = "addFour";
} else if (left == '"' || left == "'") {
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
else return CodeMirror.Pass;
} else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) {
curType = "both";
} else {
return CodeMirror.Pass;
}
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
cm.operation(function() {
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "skipThree") {
for (var i = 0; i < 3; i++)
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.execCommand("goCharLeft");
} else if (type == "addFour") {
cm.replaceSelection(left + left + left + left, "before");
cm.execCommand("goCharRight");
}
});
};
if (left != right) map["'" + right + "'"] = function(cm) {
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (!range.empty() ||
cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
return CodeMirror.Pass;
}
cm.execCommand("goCharRight");
};
})(pairs.charAt(i), pairs.charAt(i + 1));
return map;
}
function buildExplodeHandler(pairs) {
return function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
cm.replaceSelection("\n\n", null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
};
}
});

View File

@ -94,15 +94,15 @@
}
}
function autoCloseCurrent(cm, typingSlash) {
function autoCloseSlash(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
var head = typingSlash ? "/" : "</";
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
tok.start != pos.ch - 1))
if (tok.type == "string" || tok.string.charAt(0) != "<" ||
tok.start != pos.ch - 1)
return CodeMirror.Pass;
// Kludge to get around the fact that we are not in XML mode
// when completing in JS/CSS snippet in htmlmixed mode. Does not
@ -110,16 +110,16 @@
// way to go from a mixed mode to its current XML state).
if (inner.mode.name != "xml") {
if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
replacements[i] = head + "script>";
replacements[i] = "/script>";
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
replacements[i] = head + "style>";
replacements[i] = "/style>";
else
return CodeMirror.Pass;
} else {
if (!state.context || !state.context.tagName ||
closingTagExists(cm, state.context.tagName, pos, state))
return CodeMirror.Pass;
replacements[i] = head + state.context.tagName + ">";
replacements[i] = "/" + state.context.tagName + ">";
}
}
cm.replaceSelections(replacements);
@ -129,13 +129,6 @@
cm.indentLine(ranges[i].head.line);
}
function autoCloseSlash(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
return autoCloseCurrent(cm, true);
}
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)

View File

@ -81,7 +81,7 @@
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
if (ie_lt8 && cm.state.focused) cm.display.input.focus();
var clear = function() {
cm.operation(function() {

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