Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
|
@ -1,3 +0,0 @@
|
|||
static/js/lib/* linguist-vendored
|
||||
static/js/overwrite/* linguist-vendored
|
||||
*.min.js linguist-vendored
|
|
@ -1,12 +1,11 @@
|
|||
/wide.exe
|
||||
/wide
|
||||
/main
|
||||
|
||||
/header
|
||||
/header.exe
|
||||
/data/workspace/bin/
|
||||
/data/workspace/pkg/
|
||||
/data/workspace/src/
|
||||
|
||||
/**/.DS_Store
|
||||
|
||||
/node_modules
|
||||
/nbproject
|
||||
/workspaces
|
||||
.idea
|
||||
/data/user_workspaces/*/bin/
|
||||
/data/user_workspaces/*/pkg/
|
||||
/data/user_workspaces/*/src/**/*.exe
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
author: DL88250@gmail.com
|
||||
description: A Web IDE for Teams using Golang.
|
||||
|
||||
filesets:
|
||||
depth: 10
|
||||
includes:
|
||||
- conf
|
||||
- data
|
||||
- doc
|
||||
- i18n
|
||||
- static
|
||||
- views
|
||||
- README.md
|
||||
- LICENSE
|
||||
excludes:
|
||||
- \.git
|
||||
settings:
|
||||
targetdir: ""
|
||||
build: |
|
||||
test -d Godeps && go(){ godep go "$@";} ; go install -v
|
||||
outfiles:
|
||||
- wide
|
21
.header.json
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"Dir": ".",
|
||||
"Template": ".header.txt",
|
||||
"Includes": [
|
||||
"*.go",
|
||||
"*/*.go",
|
||||
"static/css/*.css",
|
||||
"static/js/*.js",
|
||||
"static/js/overwrite/*.js"
|
||||
],
|
||||
"Excludes": [
|
||||
"static/js/lib/*",
|
||||
"static/user/*",
|
||||
"vendor/**"
|
||||
],
|
||||
"UseDefaultExcludes": true,
|
||||
"Properties": {
|
||||
"Year": "2014-present",
|
||||
"Owner": "b3log.org"
|
||||
}
|
||||
}
|
13
.header.txt
|
@ -1,13 +0,0 @@
|
|||
Copyright (c) {{.Year}}, {{.Owner}}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
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.
|
|
@ -1,4 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.12
|
2
LICENSE
|
@ -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,
|
||||
|
|
126
README.md
|
@ -1,18 +1,124 @@
|
|||
* [Wide 用户指南](https://ld246.com/article/1538873544275)
|
||||
* [Wide 开发指南](https://ld246.com/article/1538876422995)
|
||||
# Wide [](https://drone.io/github.com/b3log/wide/latest)
|
||||
|
||||

|
||||
## Intro
|
||||
|
||||

|
||||
A <b>W</b>eb <b>IDE</b> for Teams using Golang.
|
||||
|
||||

|
||||
<img src="https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png" width="100%" />
|
||||
|
||||

|
||||
## 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 for extensions
|
||||
* Easy integration 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
|
||||
|
||||

|
||||
* Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc.
|
||||
* Autocomplete: Go/HTML etc.
|
||||
* Format: Go/HTML/JSON etc.
|
||||
* Run & Debug: run/debug multiple processes at the same time
|
||||
* Multiplayer: a real team development experience
|
||||
* Navigation, Jump to declaration, Find usages, File search etc.
|
||||
* Shell: run command on the server
|
||||
* Git integration: git command on the web
|
||||
* Web development: Frontend devlopment (HTML/JS/CSS) all in one
|
||||
* Go tool: go get/install/fmt etc.
|
||||
|
||||

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

|
||||
|
||||
* A browser tab corresponds to a Wide session
|
||||
* Execution output push via WebSocket
|
||||
|
||||
Flow:
|
||||
1. Browser sends ````Build```` request
|
||||
2. Server executes ````go build```` command via ````os/exec````<br/>
|
||||
2.1. Generates a executable file
|
||||
3. Browser sends ````Run```` request
|
||||
4. Server executes the file via ````os/exec````<br/>
|
||||
4.1. A running process<br/>
|
||||
4.2. Execution output push via WebSocket channel
|
||||
5. Browser renders with callback function ````ws.onmessage````
|
||||
|
||||
### Code Assist
|
||||
|
||||

|
||||
|
||||
* Autocompletion
|
||||
* Find Usages
|
||||
|
||||
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)
|
||||
|
||||
## Demos
|
||||
|
||||
* 20141024-1.0.0, png 
|
||||
|
||||
### Olds
|
||||
* [20140927, png](http://b3log.org/wide/demo/20140927.png)
|
||||
* [20140913, png](http://b3log.org/wide/demo/20140913.png)
|
||||
* [20140910, png](http://b3log.org/wide/demo/20140910.png)
|
||||
* [20140823, swf](http://b3log.org/wide/demo/20140823.html)
|
||||
|
||||
## Setup from sources
|
||||
|
||||
1. Downloads source
|
||||
2. Gets dependencies with
|
||||
* `go get -u`
|
||||
* `go get -u github.com/88250/ide_stub`
|
||||
* `go get -u github.com/nsf/gocode`
|
||||
3. Compiles wide with `go build`
|
||||
4. Configures `conf/wide.json`
|
||||
5. Runs the executable `wide` or `wide.exe`
|
||||
|
||||
## Known Issues
|
||||
|
||||
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2014, B3log Team (http://b3log.org)
|
||||
|
||||
Licensed under the [Apache License 2.0](https://github.com/b3log/wide/blob/master/LICENSE).
|
||||
|
||||
## 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)
|
||||
|
||||
----
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/873584/4606328/4e848b96-5219-11e4-8db1-fa12774b57b4.png" width="256px" />
|
||||
</center>
|
||||
|
|
4
TERMS.md
|
@ -1,4 +0,0 @@
|
|||
* 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
|
||||
* Copyright (c) b3log.org, all rights reserved
|
162
conf/user.go
|
@ -1,162 +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 conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Panel represents a UI panel.
|
||||
type Panel struct {
|
||||
State string `json:"state"` // panel state, "min"/"max"/"normal"
|
||||
Size uint16 `json:"size"` // panel size
|
||||
}
|
||||
|
||||
// Layout represents the UI layout.
|
||||
type Layout struct {
|
||||
Side *Panel `json:"side"` // Side panel
|
||||
SideRight *Panel `json:"sideRight"` // Right-Side panel
|
||||
Bottom *Panel `json:"bottom"` // Bottom panel
|
||||
}
|
||||
|
||||
// LatestSessionContent represents the latest session content.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Editor configuration of a user.
|
||||
type editor struct {
|
||||
FontFamily string
|
||||
FontSize string
|
||||
LineHeight string
|
||||
Theme string
|
||||
TabSize string
|
||||
}
|
||||
|
||||
// Save saves the user's configurations in conf/users/{userId}.json.
|
||||
func (u *User) Save() bool {
|
||||
bytes, err := json.MarshalIndent(u, "", " ")
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
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 {
|
||||
logger.Error(err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
581
conf/wide.go
|
@ -1,331 +1,147 @@
|
|||
// 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 conf includes configurations related manipulations.
|
||||
// Wide 配置相关,所有配置(包括用户配置)都是保存在 wide.json 中.
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/event"
|
||||
"github.com/b3log/wide/event"
|
||||
_ "github.com/b3log/wide/i18n"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// PathSeparator holds the OS-specific path separator.
|
||||
PathSeparator = string(os.PathSeparator)
|
||||
// PathListSeparator holds the OS-specific path list separator.
|
||||
PathListSeparator = string(os.PathListSeparator)
|
||||
PathSeparator = string(os.PathSeparator) // 系统文件路径分隔符
|
||||
PathListSeparator = string(os.PathListSeparator) // 系统路径列表分隔符
|
||||
|
||||
// WideVersion holds the current Wide's version.
|
||||
WideVersion = "1.6.0"
|
||||
// CodeMirrorVer holds the current editor version.
|
||||
CodeMirrorVer = "5.1"
|
||||
// UserAgent represents HTTP client user agent.
|
||||
UserAgent = "Wide/" + WideVersion + "; +https://github.com/88250/wide"
|
||||
|
||||
HelloWorld = `package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("欢迎通过《边看边练 Go 系列》来学习 Go 语言 https://ld246.com/article/1437497122181")
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// Configuration.
|
||||
// 最后一次会话内容结构.
|
||||
type LatestSessionContent struct {
|
||||
FileTree []string // 文件树展开的路径集
|
||||
Files []string // 编辑器打开的文件路径集
|
||||
CurrentFile string // 当前编辑器文件路径
|
||||
}
|
||||
|
||||
// 用户结构.
|
||||
type User struct {
|
||||
Name string
|
||||
Password string
|
||||
Workspace string // 该用户的工作空间 GOPATH 路径
|
||||
Locale string
|
||||
LatestSessionContent *LatestSessionContent
|
||||
}
|
||||
|
||||
// 配置结构.
|
||||
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
|
||||
Server string // 服务地址({IP}:7070)
|
||||
StaticServer string // 静态资源服务地址(http://{IP}:7070)
|
||||
EditorChannel string // 编辑器通道地址(ws://{IP}:7070)
|
||||
OutputChannel string // 输出窗口通道地址(ws://{IP}:7070)
|
||||
ShellChannel string // Shell 通道地址(ws://{IP}:7070)
|
||||
SessionChannel string // Wide 会话通道地址(ws://{IP}:7070)
|
||||
HTTPSessionMaxAge int // HTTP 会话失效时间(秒)
|
||||
StaticResourceVersion string // 静态资源版本
|
||||
MaxProcs int // 并发执行数
|
||||
RuntimeMode string // 运行模式
|
||||
Pwd string // 工作目录
|
||||
Workspace string // 主工作空间 GOPATH 路径
|
||||
Locale string // 默认的区域
|
||||
Users []*User // 用户集
|
||||
}
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
// 配置.
|
||||
var Wide conf
|
||||
|
||||
// Wide configurations.
|
||||
var Wide *conf
|
||||
|
||||
// configurations of users.
|
||||
var Users []*User
|
||||
|
||||
// Indicates whether Docker is available.
|
||||
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/{userId}.json.
|
||||
func Load(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||
initWide(confPath, confData, confServer, confLogLevel, confReadOnly, confSiteStatCode)
|
||||
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")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
names, err := f.Readdirnames(-1)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(name, ".") { // hiden files that not be created by Wide
|
||||
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))
|
||||
err := json.Unmarshal(bytes, user)
|
||||
if err != nil {
|
||||
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Compatibility upgrade (1.3.0): https://github.com/b3log/wide/issues/83
|
||||
if "" == user.Keymap {
|
||||
user.Keymap = "wide"
|
||||
}
|
||||
|
||||
// Compatibility upgrade (1.5.3): https://github.com/b3log/wide/issues/308
|
||||
//if "" == user.GoBuildArgsForLinux {
|
||||
// user.GoBuildArgsForLinux = "-i"
|
||||
//}
|
||||
//if "" == user.GoBuildArgsForWindows {
|
||||
// user.GoBuildArgsForWindows = "-i"
|
||||
//}
|
||||
//if "" == user.GoBuildArgsForDarwin {
|
||||
// user.GoBuildArgsForDarwin = "-i"
|
||||
//}
|
||||
|
||||
Users = append(Users, user)
|
||||
}
|
||||
|
||||
initWorkspaceDirs()
|
||||
initCustomizedConfs()
|
||||
}
|
||||
|
||||
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||
bytes, err := os.ReadFile(confPath)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
Wide = &conf{}
|
||||
|
||||
err = json.Unmarshal(bytes, Wide)
|
||||
if err != nil {
|
||||
logger.Error("Parses [wide.json] error: ", err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
Wide.Autocomplete = true // default to true
|
||||
|
||||
// Logging Level
|
||||
gulu.Log.SetLevel(Wide.LogLevel)
|
||||
if "" != confLogLevel {
|
||||
Wide.LogLevel = confLogLevel
|
||||
gulu.Log.SetLevel(confLogLevel)
|
||||
}
|
||||
|
||||
logger.Debug("Conf: \n" + string(bytes))
|
||||
|
||||
// User Home
|
||||
home, err := gulu.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
|
||||
}
|
||||
Wide.Data = strings.Replace(Wide.Data, "${home}", home, -1)
|
||||
Wide.Data = filepath.Clean(Wide.Data)
|
||||
if err := os.MkdirAll(Wide.Data+"/playground/", 0755); nil != err {
|
||||
logger.Errorf("Create data directory [%s] error", err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
if err := os.MkdirAll(Wide.Data+"/users/", 0755); nil != err {
|
||||
logger.Errorf("Create data directory [%s] error", err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
if err := os.MkdirAll(Wide.Data+"/workspaces/", 0755); nil != err {
|
||||
logger.Errorf("Create data directory [%s] error", err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Server
|
||||
if "" != confServer {
|
||||
Wide.Server = confServer
|
||||
}
|
||||
|
||||
if "" != confReadOnly {
|
||||
Wide.ReadOnly, _ = strconv.ParseBool(confReadOnly)
|
||||
}
|
||||
|
||||
// SiteStatCode
|
||||
if "" != confSiteStatCode {
|
||||
Wide.SiteStatCode = confSiteStatCode
|
||||
}
|
||||
|
||||
time := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
logger.Debugf("${time} [%s]", time)
|
||||
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
|
||||
}
|
||||
|
||||
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
|
||||
// 维护非变化部分的配置.
|
||||
//
|
||||
// Exits process if found fatal issues (such as not found $GOPATH),
|
||||
// Notifies user by notification queue if found warning issues (such as not found gocode).
|
||||
func FixedTimeCheckEnv() {
|
||||
checkEnv() // check immediately
|
||||
// 只有 Users 是会运行时变化的,保存回写文件时要使用这个变量.
|
||||
var rawWide conf
|
||||
|
||||
// 定时检查 Wide 运行环境.
|
||||
//
|
||||
// 如果是特别严重的问题(比如 $GOPATH 不存在)则退出进程,另一些不太严重的问题(比如 gocode 不存在)则放入全局通知队列.
|
||||
func FixedTimeCheckEnv() {
|
||||
go func() {
|
||||
// 7 分钟进行一次检查环境
|
||||
for _ = range time.Tick(time.Minute * 7) {
|
||||
checkEnv()
|
||||
if "" == os.Getenv("GOPATH") {
|
||||
glog.Fatal("Not found $GOPATH")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
if "" == os.Getenv("GOROOT") {
|
||||
glog.Fatal("Not found $GOROOT")
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
gocode := Wide.GetGocode()
|
||||
cmd := exec.Command(gocode, "close")
|
||||
_, err := cmd.Output()
|
||||
if nil != err {
|
||||
event.EventQueue <- event.EvtCodeGocodeNotFound
|
||||
glog.Warningf("Not found gocode [%s]", gocode)
|
||||
}
|
||||
|
||||
ide_stub := Wide.GetIDEStub()
|
||||
cmd = exec.Command(ide_stub, "version")
|
||||
_, err = cmd.Output()
|
||||
if nil != err {
|
||||
event.EventQueue <- event.EvtCodeIDEStubNotFound
|
||||
glog.Warningf("Not found ide_stub [%s]", ide_stub)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func checkEnv() {
|
||||
defer gulu.Panic.Recover(nil)
|
||||
|
||||
cmd := exec.Command("go", "version")
|
||||
buf, err := cmd.CombinedOutput()
|
||||
if nil != err {
|
||||
logger.Error("Not found 'go' command, please make sure Go has been installed correctly")
|
||||
|
||||
os.Exit(-1)
|
||||
// 定时(1 分钟)保存配置.
|
||||
//
|
||||
// 主要目的是保存用户会话内容,以备下一次用户打开 Wide 时进行会话还原.
|
||||
func FixedTimeSave() {
|
||||
go func() {
|
||||
// 1 分钟进行一次配置保存
|
||||
for _ = range time.Tick(time.Minute) {
|
||||
Save()
|
||||
}
|
||||
logger.Trace(string(buf))
|
||||
|
||||
if "" == os.Getenv("GOPATH") {
|
||||
logger.Error("Not found $GOPATH, please configure it before running Wide")
|
||||
|
||||
os.Exit(-1)
|
||||
}()
|
||||
}
|
||||
|
||||
gocode := gulu.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)
|
||||
}
|
||||
|
||||
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
|
||||
func GetUserWorkspace(userId string) string {
|
||||
for _, user := range Users {
|
||||
if user.Id == userId {
|
||||
return user.WorkspacePath()
|
||||
// 获取 username 指定的用户的工作空间路径,查找不到时返回空字符串.
|
||||
func (c *conf) GetUserWorkspace(username string) string {
|
||||
for _, user := range c.Users {
|
||||
if user.Name == username {
|
||||
ret := strings.Replace(user.Workspace, "{pwd}", c.Pwd, 1)
|
||||
return filepath.FromSlash(ret)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
|
||||
func GetGoFmt(userId string) string {
|
||||
for _, user := range Users {
|
||||
if user.Id == userId {
|
||||
switch user.GoFormat {
|
||||
case "gofmt":
|
||||
return "gofmt"
|
||||
case "goimports":
|
||||
return gulu.Go.GetExecutableInGOBIN("goimports")
|
||||
default:
|
||||
logger.Errorf("Unsupported Go Format tool [%s]", user.GoFormat)
|
||||
return "gofmt"
|
||||
}
|
||||
}
|
||||
// 获取主工作空间路径.
|
||||
func (c *conf) GetWorkspace() string {
|
||||
return filepath.FromSlash(strings.Replace(c.Workspace, "{pwd}", c.Pwd, 1))
|
||||
}
|
||||
|
||||
return "gofmt"
|
||||
// 获取 user 的工作空间路径.
|
||||
func (user *User) getWorkspace() string {
|
||||
ret := strings.Replace(user.Workspace, "{pwd}", Wide.Pwd, 1)
|
||||
|
||||
return filepath.FromSlash(ret)
|
||||
}
|
||||
|
||||
// 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", "", "")
|
||||
}
|
||||
|
||||
for _, user := range Users {
|
||||
if user.Id == id {
|
||||
// 获取 username 指定的用户配置.
|
||||
func (*conf) GetUser(username string) *User {
|
||||
for _, user := range Wide.Users {
|
||||
if user.Name == username {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
@ -333,131 +149,110 @@ func GetUser(id string) *User {
|
|||
return nil
|
||||
}
|
||||
|
||||
// initCustomizedConfs initializes the user customized configurations.
|
||||
func initCustomizedConfs() {
|
||||
for _, user := range Users {
|
||||
UpdateCustomizedConf(user.Id)
|
||||
}
|
||||
// 获取 gocode 路径.
|
||||
func (*conf) GetGocode() string {
|
||||
return getGOBIN() + "gocode"
|
||||
}
|
||||
|
||||
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
|
||||
//
|
||||
// 1. /static/users/{userId}/style.css
|
||||
func UpdateCustomizedConf(userId 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 {
|
||||
u = user
|
||||
}
|
||||
// 获取 ide_stub 路径.
|
||||
func (*conf) GetIDEStub() string {
|
||||
return getGOBIN() + "ide_stub"
|
||||
}
|
||||
|
||||
if nil == u {
|
||||
return
|
||||
// 获取 GOBIN 路径,末尾带路径分隔符.
|
||||
func getGOBIN() string {
|
||||
// $GOBIN/
|
||||
ret := os.Getenv("GOBIN")
|
||||
if "" != ret {
|
||||
return ret + PathSeparator
|
||||
}
|
||||
|
||||
model := map[string]interface{}{"user": u}
|
||||
// $GOPATH/bin/$GOOS_$GOARCH/
|
||||
ret = os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator +
|
||||
os.Getenv("GOOS") + "_" + os.Getenv("GOARCH")
|
||||
if isExist(ret) {
|
||||
return ret + PathSeparator
|
||||
}
|
||||
|
||||
// $GOPATH/bin/{runtime.GOOS}_{runtime.GOARCH}/
|
||||
ret = os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator +
|
||||
runtime.GOOS + "_" + runtime.GOARCH
|
||||
if isExist(ret) {
|
||||
return ret + PathSeparator
|
||||
}
|
||||
|
||||
// $GOPATH/bin/
|
||||
return os.Getenv("GOPATH") + PathSeparator + "bin" + PathSeparator
|
||||
}
|
||||
|
||||
// 保存 Wide 配置.
|
||||
func Save() bool {
|
||||
// 只有 Users 是会运行时变化的,其他属性只能手工维护 wide.json 配置文件
|
||||
rawWide.Users = Wide.Users
|
||||
|
||||
// 原始配置文件内容
|
||||
bytes, err := json.MarshalIndent(rawWide, "", " ")
|
||||
|
||||
t, err := template.ParseFiles("static/user/style.css.tmpl")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
glog.Error(err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile("conf/wide.json", bytes, 0644); nil != err {
|
||||
glog.Error(err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 加载 Wide 配置.
|
||||
func Load() {
|
||||
bytes, _ := ioutil.ReadFile("conf/wide.json")
|
||||
|
||||
err := json.Unmarshal(bytes, &Wide)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
|
||||
if err := os.MkdirAll(dir, 0755); nil != err {
|
||||
logger.Error(err)
|
||||
// 保存未经变量替换处理的原始配置文件,用于写回时
|
||||
json.Unmarshal(bytes, &rawWide)
|
||||
|
||||
ip, err := util.Net.LocalIP()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
fout, err := os.Create(dir + PathSeparator + "style.css")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
glog.V(3).Infof("IP [%s]", ip)
|
||||
|
||||
os.Exit(-1)
|
||||
Wide.Server = strings.Replace(Wide.Server, "{IP}", ip, 1)
|
||||
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", ip, 1)
|
||||
Wide.EditorChannel = strings.Replace(Wide.EditorChannel, "{IP}", ip, 1)
|
||||
Wide.OutputChannel = strings.Replace(Wide.OutputChannel, "{IP}", ip, 1)
|
||||
Wide.ShellChannel = strings.Replace(Wide.ShellChannel, "{IP}", ip, 1)
|
||||
Wide.SessionChannel = strings.Replace(Wide.SessionChannel, "{IP}", ip, 1)
|
||||
|
||||
// 获取当前执行路径
|
||||
file, _ := exec.LookPath(os.Args[0])
|
||||
pwd, _ := filepath.Abs(file)
|
||||
pwd = pwd[:strings.LastIndex(pwd, PathSeparator)]
|
||||
Wide.Pwd = pwd
|
||||
glog.V(3).Infof("pwd [%s]", pwd)
|
||||
|
||||
glog.V(3).Info("Conf: \n" + string(bytes))
|
||||
}
|
||||
|
||||
defer fout.Close()
|
||||
|
||||
if err := t.Execute(fout, model); nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// initWorkspaceDirs initializes the directories of users' workspaces.
|
||||
// 检查文件或目录是否存在.
|
||||
//
|
||||
// Creates directories if not found on path of workspace.
|
||||
func initWorkspaceDirs() {
|
||||
paths := []string{}
|
||||
// 如果由 filename 指定的文件或目录存在则返回 true,否则返回 false.
|
||||
func isExist(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
|
||||
for _, user := range Users {
|
||||
paths = append(paths, filepath.SplitList(user.WorkspacePath())...)
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
CreateWorkspaceDir(path)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWorkspaceDir creates (if not exists) directories on the path.
|
||||
//
|
||||
// 1. root directory:{path}
|
||||
// 2. src directory: {path}/src
|
||||
// 3. package directory: {path}/pkg
|
||||
// 4. binary directory: {path}/bin
|
||||
func CreateWorkspaceDir(path string) {
|
||||
createDir(path)
|
||||
createDir(path + PathSeparator + "src")
|
||||
createDir(path + PathSeparator + "pkg")
|
||||
createDir(path + PathSeparator + "bin")
|
||||
}
|
||||
|
||||
// createDir creates a directory on the path if it not exists.
|
||||
func createDir(path string) {
|
||||
if !gulu.File.IsExist(path) {
|
||||
if err := os.MkdirAll(path, 0775); nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
logger.Tracef("Created a dir [%s]", path)
|
||||
}
|
||||
}
|
||||
|
||||
// GetEditorThemes gets the names of editor themes.
|
||||
func GetEditorThemes() []string {
|
||||
ret := []string{}
|
||||
|
||||
f, _ := os.Open("static/js/overwrite/codemirror" + "/theme")
|
||||
names, _ := f.Readdirnames(-1)
|
||||
f.Close()
|
||||
|
||||
for _, name := range names {
|
||||
ret = append(ret, name[:strings.LastIndex(name, ".")])
|
||||
}
|
||||
|
||||
sort.Strings(ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetThemes gets the names of themes.
|
||||
func GetThemes() []string {
|
||||
ret := []string{}
|
||||
|
||||
f, _ := os.Open("static/css/themes")
|
||||
names, _ := f.Readdirnames(-1)
|
||||
f.Close()
|
||||
|
||||
for _, name := range names {
|
||||
ret = append(ret, name[:strings.LastIndex(name, ".")])
|
||||
}
|
||||
|
||||
sort.Strings(ret)
|
||||
|
||||
return ret
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
{
|
||||
"Server": "http://127.0.0.1:7070",
|
||||
"LogLevel": "debug",
|
||||
"Data": "${home}/wide",
|
||||
"RuntimeMode": "prod",
|
||||
"Server": "{IP}:7070",
|
||||
"StaticServer": "http://{IP}:7070",
|
||||
"EditorChannel": "ws://{IP}:7070",
|
||||
"OutputChannel": "ws://{IP}:7070",
|
||||
"ShellChannel": "ws://{IP}:7070",
|
||||
"SessionChannel": "ws://{IP}:7070",
|
||||
"HTTPSessionMaxAge": 86400,
|
||||
"StaticResourceVersion": "${time}",
|
||||
"StaticResourceVersion": "201410241024",
|
||||
"MaxProcs": 4,
|
||||
"RuntimeMode": "dev",
|
||||
"Pwd": "{pwd}",
|
||||
"Workspace": "{pwd}/data/workspace",
|
||||
"Locale": "zh_CN",
|
||||
"SiteStatCode": "",
|
||||
"ReadOnly": false,
|
||||
"OAuthLoginURL": "",
|
||||
"OAuthAccessTokenURL": "",
|
||||
"OAuthUserInfoURL": "",
|
||||
"OAuthClientID": "",
|
||||
"OAuthClientSecret": ""
|
||||
"Users": [
|
||||
{
|
||||
"Name": "admin",
|
||||
"Password": "admin",
|
||||
"Workspace": "{pwd}/data/user_workspaces/admin",
|
||||
"Locale": "zh_CN",
|
||||
"LatestSessionContent": {
|
||||
"FileTree": [],
|
||||
"Files": [],
|
||||
"CurrentFile": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
User workspaces.
|
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, 世界")
|
||||
}
|
|
@ -0,0 +1,411 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
{{.i18n.wide}}
|
||||
|
||||
</title>
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/codemirror.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/hint/show-hint.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/lint.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldgutter.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/theme/wide.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/ztree/zTreeStyle.css">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/dialog.css?{{.conf.StaticResourceVersion}}">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/base.css?{{.conf.StaticResourceVersion}}">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/wide.css?{{.conf.StaticResourceVersion}}">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/side.css?{{.conf.StaticResourceVersion}}">
|
||||
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/start.css?{{.conf.StaticResourceVersion}}">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- 主菜单 -->
|
||||
<div class="menu fn-clear">
|
||||
<ul class="fn-cleaer">
|
||||
<li>
|
||||
<span>
|
||||
{{.i18n.file}}
|
||||
|
||||
</span>
|
||||
<div class="frame">
|
||||
<ul>
|
||||
<li class="save-all disabled" onclick="wide.saveAllFiles()">
|
||||
<span>
|
||||
{{.i18n.save_all_files}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li class="close-all disabled" onclick="wide.closeAllFiles()">
|
||||
<span>
|
||||
{{.i18n.close_all_files}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li class="hr">
|
||||
</li>
|
||||
<li onclick="wide.exit()">
|
||||
<span>
|
||||
{{.i18n.exit}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
{{.i18n.run}}
|
||||
|
||||
</span>
|
||||
<div class="frame">
|
||||
<ul>
|
||||
<li class="run disabled" onclick="wide.run()">
|
||||
<span>
|
||||
{{.i18n.build_n_run}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li class="hr">
|
||||
</li>
|
||||
<li class="go-get disabled" onclick="wide.goget()">
|
||||
<span>
|
||||
{{.i18n.goget}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li class="go-install disabled" onclick="wide.goinstall()">
|
||||
<span>
|
||||
{{.i18n.goinstall}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<span>{{.i18n.debug}}</span>
|
||||
<div class="frame">
|
||||
<ul>
|
||||
<li>
|
||||
<span>{{.i18n.debug}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
-->
|
||||
<li>
|
||||
<span>
|
||||
{{.i18n.help}}
|
||||
|
||||
</span>
|
||||
<div class="frame">
|
||||
<ul>
|
||||
<li onclick="window.open('https://www.gitbook.io/book/88250/wide-user-guide')">
|
||||
<span>
|
||||
{{.i18n.wide_doc}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li onclick="window.open('https://github.com/b3log/wide/issues/new')">
|
||||
{{.i18n.report_issues}}
|
||||
|
||||
</li>
|
||||
<li class="hr">
|
||||
</li>
|
||||
<li onclick="editors.openStartPage()">
|
||||
<span>
|
||||
{{.i18n.start_page}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
<li onclick="wide.openAbout()">
|
||||
<span>
|
||||
{{.i18n.about}}
|
||||
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="side">
|
||||
<span title="{{.i18n.min}}" class="font-ico ico-min">
|
||||
</span>
|
||||
<div class="tabs">
|
||||
<div class="current" data-index="filreTree">
|
||||
<span title="{{.i18n.file}}">
|
||||
{{.i18n.file}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs-panel">
|
||||
<div data-index="filreTree">
|
||||
<ul id="files" tabindex="-1" class="ztree">
|
||||
</ul>
|
||||
<!-- 目录右键菜单 -->
|
||||
<div id="dirRMenu" class="frame">
|
||||
<ul>
|
||||
<li onclick="tree.newFile();">
|
||||
{{.i18n.create_file}}
|
||||
|
||||
</li>
|
||||
<li onclick="tree.newDir();">
|
||||
{{.i18n.create_dir}}
|
||||
|
||||
</li>
|
||||
<li onclick="tree.removeIt();">
|
||||
{{.i18n.delete}}
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 文件右键菜单 -->
|
||||
<div id="fileRMenu" class="frame">
|
||||
<ul>
|
||||
<li onclick="tree.removeIt();">
|
||||
{{.i18n.delete}}
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-panel">
|
||||
<div class="toolbars fn-none">
|
||||
<span onclick="wide.run()" class="font-ico ico-buildrun" title="{{.i18n.build_n_run}}">
|
||||
</span>
|
||||
<span onclick="wide.saveFile()" title="{{.i18n.save}}" class="font-ico ico-save">
|
||||
</span>
|
||||
<span onclick="wide.fmt(editors.getCurrentPath(), wide.curEditor)" class="ico-format font-ico" title="{{.i18n.format}}">
|
||||
</span>
|
||||
<span class="font-ico ico-max" onclick="windows.maxEditor()" title="{{.i18n.max_editor}}">
|
||||
</span>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
</div>
|
||||
<div class="tabs-panel">
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-window-group">
|
||||
<span title="{{.i18n.min}}" class="font-ico ico-min">
|
||||
</span>
|
||||
<div class="tabs">
|
||||
<div class="current" data-index="output">
|
||||
<span title="{{.i18n.output}}">
|
||||
{{.i18n.output}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div data-index="search">
|
||||
<span title="{{.i18n.search}}">
|
||||
{{.i18n.search}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div data-index="notification">
|
||||
<span title="{{.i18n.notification}}">
|
||||
{{.i18n.notification}}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs-panel">
|
||||
<div data-index="output">
|
||||
<textarea class="output">
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="fn-none" data-index="search">
|
||||
<div tabindex="-1" class="search">
|
||||
<div class="tabs fn-none">
|
||||
<div class="current" data-index="first">
|
||||
<span class="first">
|
||||
</span>
|
||||
<span class="ico-close font-ico">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs-panel">
|
||||
<div data-index="first">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fn-none" data-index="notification">
|
||||
<div tabindex="-1" class="notification">
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer fn-clear">
|
||||
<div class="fn-left">
|
||||
<span title="{{.i18n.restore_side}}" class="font-ico ico-restore fn-none">
|
||||
</span>
|
||||
<span title="{{.i18n.resotre_bottom}}" class="font-ico ico-restore fn-none">
|
||||
</span>
|
||||
</div>
|
||||
<div class="fn-right">
|
||||
<span class="cursor">
|
||||
</span>
|
||||
<span class="notification-count" title="{{.i18n.unread_notification}}">
|
||||
{{.i18n.notification}}!
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialogRemoveConfirm" class="fn-none">
|
||||
{{.i18n.isDelete}}
|
||||
|
||||
<b>
|
||||
</b>
|
||||
?
|
||||
|
||||
</div>
|
||||
<div id="dialogAlert" class="fn-none">
|
||||
</div>
|
||||
<div id="dialogNewFilePrompt" class="dialog-prompt fn-none">
|
||||
<input/>
|
||||
</div>
|
||||
<div id="dialogNewDirPrompt" class="dialog-prompt fn-none">
|
||||
<input/>
|
||||
</div>
|
||||
<div id="dialogGoLinePrompt" class="dialog-prompt fn-none">
|
||||
<input/>
|
||||
</div>
|
||||
<div id="dialogSearchForm" class="dialog-form fn-none">
|
||||
<input placeholder="{{.i18n.keyword}}" />
|
||||
<input placeholder="{{.i18n.file_format}}" />
|
||||
</div>
|
||||
<script>
|
||||
var config = {
|
||||
"pathSeparator": {{.pathSeparator}},
|
||||
"latestSessionContent": {{.latestSessionContent}},
|
||||
"label": {
|
||||
"restore_editor": "{{.i18n.restore_editor}}",
|
||||
"max_editor": "{{.i18n.max_editor}}",
|
||||
"delete": "{{.i18n.delete}}",
|
||||
"cancel": "{{.i18n.cancel}}",
|
||||
"goto_line": "{{.i18n.goto_line}}",
|
||||
"goto": "{{.i18n.goto}}",
|
||||
"create": "{{.i18n.create}}",
|
||||
"create_file": "{{.i18n.create_file}}",
|
||||
"create_dir": "{{.i18n.create_dir}}",
|
||||
"tip": "{{.i18n.tip}}",
|
||||
"confirm": "{{.i18n.confirm}}",
|
||||
"build_n_run": "{{.i18n.build_n_run}}",
|
||||
"stop": "{{.i18n.stop}}",
|
||||
"usages": "{{.i18n.find_usages}}",
|
||||
"search_text": "{{.i18n.search_text}}",
|
||||
"search": "{{.i18n.search}}",
|
||||
"initialise": "{{.i18n.initialise}}",
|
||||
"confirm_save": "{{.i18n.confirm_save}}"
|
||||
},
|
||||
"channel": {
|
||||
"editor": '{{.conf.EditorChannel}}',
|
||||
"shell": '{{.conf.ShellChannel}}',
|
||||
"output": '{{.conf.OutputChannel}}',
|
||||
"session": '{{.conf.SessionChannel}}'
|
||||
},
|
||||
"wideSessionId": '{{.session.Id}}'
|
||||
};
|
||||
// 发往 Wide 的所有 AJAX 请求需要使用该函数创建请求参数.
|
||||
function newWideRequest() {
|
||||
var ret = {
|
||||
sid: config.wideSessionId
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/jquery-2.1.1.min.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/reconnecting-websocket.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/ztree/jquery.ztree.all-3.5.min.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/codemirror.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/lint.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/lint/json-lint.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/selection/active-line.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/overwrite/codemirror-4.5/addon/hint/show-hint.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/hint/anyword-hint.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/display/rulers.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/closebrackets.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/matchbrackets.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/edit/closetag.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/search/searchcursor.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/search/match-highlighter.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldcode.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/foldgutter.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/brace-fold.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/xml-fold.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/markdown-fold.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/addon/fold/comment-fold.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/go/go.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/xml/xml.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/htmlmixed/htmlmixed.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/javascript/javascript.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/markdown/markdown.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/css/css.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/shell/shell.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-4.5/mode/sql/sql.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/lint/json-lint.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/lint/go-lint.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/tabs.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/dialog.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/editors.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/notification.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/tree.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/wide.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/session.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/menu.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/windows.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/hotkeys.js?{{.conf.StaticResourceVersion}}">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mytest/time/pkg"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 50; i++ {
|
||||
fmt.Println("Hello, 世界", pkg.Now())
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func Now1() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func Now2() {
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Master workspace.
|
|
@ -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)
|
|
@ -1,18 +1,4 @@
|
|||
// 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 editor includes editor related manipulations.
|
||||
// 编辑器操作.
|
||||
package editor
|
||||
|
||||
import (
|
||||
|
@ -21,88 +7,147 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
var editorWS = map[string]*websocket.Conn{}
|
||||
|
||||
// AutocompleteHandler handles request of code autocompletion.
|
||||
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if conf.Wide.ReadOnly {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
// 建立编辑器通道.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
sid := session.Values["id"].(string)
|
||||
|
||||
editorWS[sid], _ = websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||
|
||||
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
|
||||
editorWS[sid].WriteJSON(&ret)
|
||||
|
||||
glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(editorWS))
|
||||
|
||||
args := map[string]interface{}{}
|
||||
for {
|
||||
if err := editorWS[sid].ReadJSON(&args); err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
if err.Error() == "unexpected EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
glog.Error("Editor WS ERROR: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
line := int(args["cursorLine"].(float64))
|
||||
ch := int(args["cursorCh"].(float64))
|
||||
|
||||
offset := getCursorOffset(code, line, ch)
|
||||
|
||||
// glog.Infof("offset: %d", offset)
|
||||
|
||||
gocode := conf.Wide.GetGocode()
|
||||
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 := editorWS[sid].WriteJSON(&ret); err != nil {
|
||||
glog.Error("Editor WS ERROR: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自动完成(代码补全).
|
||||
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
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)
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
uid := session.Values["uid"].(string)
|
||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
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)
|
||||
glog.Error(err)
|
||||
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)
|
||||
glog.Error(err)
|
||||
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)
|
||||
workspaces := filepath.SplitList(userWorkspace)
|
||||
libPath := ""
|
||||
for _, workspace := range workspaces {
|
||||
userLib := workspace + conf.PathSeparator + "pkg" + conf.PathSeparator +
|
||||
// glog.Infof("offset: %d", offset)
|
||||
|
||||
userWorkspace := conf.Wide.GetUserWorkspace(username)
|
||||
|
||||
//glog.Infof("User [%s] workspace [%s]", username, userWorkspace)
|
||||
userLib := userWorkspace + conf.PathSeparator + "pkg" + conf.PathSeparator +
|
||||
runtime.GOOS + "_" + runtime.GOARCH
|
||||
libPath += userLib + conf.PathListSeparator
|
||||
}
|
||||
|
||||
logger.Tracef("gocode set lib-path [%s]", libPath)
|
||||
libPath := userLib
|
||||
//glog.Infof("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()
|
||||
// FIXME: 使用 gocode set lib-path 在多工作空间环境下肯定是有问题的,需要考虑其他实现方式
|
||||
gocode := conf.Wide.GetGocode()
|
||||
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)
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -111,31 +156,33 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write(output)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
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)
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
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
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -144,8 +191,8 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|||
fout.WriteString(code)
|
||||
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -155,63 +202,61 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
offset := getCursorOffset(code, line, ch)
|
||||
|
||||
logger.Tracef("offset [%d]", offset)
|
||||
// glog.Infof("offset [%d]", offset)
|
||||
|
||||
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."}
|
||||
cmd := exec.Command(ideStub, argv...)
|
||||
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
|
||||
ide_stub := conf.Wide.GetIDEStub()
|
||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."}
|
||||
cmd := exec.Command(ide_stub, 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)
|
||||
glog.Error(err)
|
||||
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)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
username := session.Values["username"].(string)
|
||||
|
||||
return
|
||||
}
|
||||
uid := session.Values["uid"].(string)
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
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)
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
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
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -220,8 +265,8 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
|||
fout.WriteString(code)
|
||||
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -231,26 +276,27 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
offset := getCursorOffset(code, line, ch)
|
||||
|
||||
logger.Tracef("offset [%d]", offset)
|
||||
// glog.Infof("offset [%d]", offset)
|
||||
|
||||
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-def", "."}
|
||||
cmd := exec.Command(ideStub, argv...)
|
||||
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
|
||||
ide_stub := conf.Wide.GetIDEStub()
|
||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."}
|
||||
cmd := exec.Command(ide_stub, 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)
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
found := strings.TrimSpace(string(output))
|
||||
if "" == found {
|
||||
result.Code = -1
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -258,49 +304,42 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
|
|||
part := found[:strings.LastIndex(found, ":")]
|
||||
cursorSep := strings.LastIndex(part, ":")
|
||||
path = found[:cursorSep]
|
||||
cursorLine := found[cursorSep+1 : strings.LastIndex(found, ":")]
|
||||
cursorCh := found[strings.LastIndex(found, ":")+1:]
|
||||
|
||||
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)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
username := session.Values["username"].(string)
|
||||
|
||||
return
|
||||
}
|
||||
uid := session.Values["uid"].(string)
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
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)
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
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
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -309,8 +348,8 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
fout.WriteString(code)
|
||||
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -319,38 +358,39 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
ch := int(args["cursorCh"].(float64))
|
||||
|
||||
offset := getCursorOffset(code, line, ch)
|
||||
logger.Tracef("offset [%d]", offset)
|
||||
// glog.Infof("offset [%d]", offset)
|
||||
|
||||
ideStub := gulu.Go.GetExecutableInGOBIN("gotools")
|
||||
argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."}
|
||||
cmd := exec.Command(ideStub, argv...)
|
||||
// TODO: 目前是调用 liteide_stub 工具来查找使用,后续需要重新实现
|
||||
ide_stub := conf.Wide.GetIDEStub()
|
||||
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."}
|
||||
cmd := exec.Command(ide_stub, 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)
|
||||
glog.Error(err)
|
||||
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,22 +398,21 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
usages = append(usages, usage)
|
||||
}
|
||||
|
||||
result.Data = usages
|
||||
data["founds"] = usages
|
||||
}
|
||||
|
||||
// getCursorOffset calculates the cursor offset.
|
||||
// 计算光标偏移位置.
|
||||
//
|
||||
// line is the line number, starts with 0 that means the first line
|
||||
// ch is the column number, starts with 0 that means the first column
|
||||
// line 指定了行号(第一行为 0),ch 指定了列号(第一列为 0).
|
||||
func getCursorOffset(code string, line, ch int) (offset int) {
|
||||
lines := strings.Split(code, "\n")
|
||||
|
||||
// calculate sum length of lines before
|
||||
// 计算前几行长度
|
||||
for i := 0; i < line; i++ {
|
||||
offset += len(lines[i])
|
||||
}
|
||||
|
||||
// calculate length of the current line and column
|
||||
// 计算当前行、当前列长度
|
||||
curLine := lines[line]
|
||||
var buffer bytes.Buffer
|
||||
r := []rune(curLine)
|
||||
|
@ -381,14 +420,14 @@ func getCursorOffset(code string, line, ch int) (offset int) {
|
|||
buffer.WriteString(string(r[i]))
|
||||
}
|
||||
|
||||
offset += len(buffer.String()) // append length of current line
|
||||
offset += line // append number of '\n'
|
||||
offset += line // 加换行符
|
||||
offset += len(buffer.String()) // 加当前行列偏移
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
func setCmdEnv(cmd *exec.Cmd, userId string) {
|
||||
userWorkspace := conf.GetUserWorkspace(userId)
|
||||
func setCmdEnv(cmd *exec.Cmd, username string) {
|
||||
userWorkspace := conf.Wide.GetUserWorkspace(username)
|
||||
|
||||
cmd.Env = append(cmd.Env,
|
||||
"GOPATH="+userWorkspace,
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
// 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 editor
|
||||
|
||||
import (
|
||||
|
@ -20,56 +6,36 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/conf"
|
||||
"github.com/88250/wide/session"
|
||||
"github.com/88250/gohtml"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// GoFmtHandler handles request of formatting Go source code.
|
||||
//
|
||||
// This function will select a format tooll based on user's configuration:
|
||||
// 1. gofmt
|
||||
// 2. goimports
|
||||
// TODO: 加入 goimports 格式化 Go 源码文件
|
||||
|
||||
// gofmt 格式化 Go 源码文件.
|
||||
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)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
uid := session.Values["uid"].(string)
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filePath := args["file"].(string)
|
||||
|
||||
if gulu.Go.IsAPI(filePath) {
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -78,29 +44,19 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
result.Data = &data
|
||||
|
||||
data["code"] = code
|
||||
|
||||
result.Data = data
|
||||
|
||||
fmt := conf.GetGoFmt(uid)
|
||||
|
||||
argv := []string{filePath}
|
||||
cmd := exec.Command(fmt, argv...)
|
||||
cmd := exec.Command("gofmt", argv...)
|
||||
|
||||
bytes, _ := cmd.Output()
|
||||
output := string(bytes)
|
||||
if "" == output {
|
||||
// format error, returns the original content
|
||||
result.Code = 0
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -111,8 +67,133 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
|
|||
fout, err = os.Create(filePath)
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化 HTML 文件.
|
||||
// FIXME:依赖的工具 gohtml 格式化 HTML 时有问题
|
||||
func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filePath := args["file"].(string)
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
output := gohtml.Format(code)
|
||||
if "" == output {
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code = string(output)
|
||||
data["code"] = code
|
||||
|
||||
fout, err = os.Create(filePath)
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化 JSON 文件.
|
||||
func JSONFmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filePath := args["file"].(string)
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
obj := new(interface{})
|
||||
if err := json.Unmarshal([]byte(code), &obj); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
glog.Info(obj)
|
||||
|
||||
bytes, err := json.MarshalIndent(obj, "", " ")
|
||||
if nil != err {
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code = string(bytes)
|
||||
data["code"] = code
|
||||
|
||||
fout, err = os.Create(filePath)
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
|
131
event/events.go
|
@ -1,119 +1,89 @@
|
|||
// 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 event includes event related manipulations.
|
||||
// 事件处理.
|
||||
package event
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
)
|
||||
import "github.com/golang/glog"
|
||||
|
||||
const (
|
||||
// EvtCodeGOPATHNotFound indicates an event: not found $GOPATH env variable
|
||||
EvtCodeGOPATHNotFound = iota
|
||||
// EvtCodeGOROOTNotFound indicates an event: not found $GOROOT env variable
|
||||
EvtCodeGOROOTNotFound
|
||||
// EvtCodeGocodeNotFound indicates an event: not found gocode
|
||||
EvtCodeGocodeNotFound
|
||||
// EvtCodeIDEStubNotFound indicates an event: not found gotools
|
||||
EvtCodeIDEStubNotFound
|
||||
// EvtCodeServerInternalError indicates an event: server internal error
|
||||
EvtCodeServerInternalError
|
||||
EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH
|
||||
EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT
|
||||
EvtCodeGocodeNotFound // 事件代码:找不到 gocode
|
||||
EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub
|
||||
)
|
||||
|
||||
// Max length of queue.
|
||||
const maxQueueLength = 10
|
||||
// 事件队列最大长度.
|
||||
const MaxQueueLength = 10
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
|
||||
// Event represents an event.
|
||||
// 事件结构.
|
||||
type Event struct {
|
||||
Code int `json:"code"` // event code
|
||||
Sid string `json:"sid"` // wide session id related
|
||||
Data interface{} `json:"data"` // event data
|
||||
Code int `json:"code"` // 事件代码
|
||||
Sid string `json:"sid"` // 用户会话 id
|
||||
}
|
||||
|
||||
// Global event queue.
|
||||
// 全局事件队列.
|
||||
//
|
||||
// Every event in this queue will be dispatched to each user event queue.
|
||||
var EventQueue = make(chan *Event, maxQueueLength)
|
||||
// 入队的事件将分发到每个用户的事件队列中.
|
||||
var EventQueue = make(chan int, MaxQueueLength)
|
||||
|
||||
// UserEventQueue represents a user event queue.
|
||||
// 用户事件队列.
|
||||
type UserEventQueue struct {
|
||||
Sid string // wide session id related
|
||||
Queue chan *Event // queue
|
||||
Handlers []Handler // event handlers
|
||||
Sid string // 关联的会话 id
|
||||
Queue chan int // 队列
|
||||
Handlers []Handler // 事件处理器集
|
||||
}
|
||||
|
||||
type queues map[string]*UserEventQueue
|
||||
// 事件队列集类型.
|
||||
type Queues map[string]*UserEventQueue
|
||||
|
||||
// User event queues.
|
||||
// 用户事件队列集.
|
||||
//
|
||||
// <sid, *UserEventQueue>
|
||||
var UserEventQueues = queues{}
|
||||
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)
|
||||
glog.V(5).Info("收到全局事件 [%d]", event)
|
||||
|
||||
// dispatch the event to each user event queue
|
||||
// 将事件分发到每个用户的事件队列里
|
||||
for _, userQueue := range UserEventQueues {
|
||||
event.Sid = userQueue.Sid
|
||||
|
||||
userQueue.Queue <- event
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
logger.Warnf("Already exist a user queue in session [%s]", sid)
|
||||
// 初始化一个用户事件队列.
|
||||
func (ueqs Queues) New(sid string) *UserEventQueue {
|
||||
q := ueqs[sid]
|
||||
if nil != q {
|
||||
glog.Warningf("Already exist a user queue in session [%s]", sid)
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
q := &UserEventQueue{
|
||||
q = &UserEventQueue{
|
||||
Sid: sid,
|
||||
Queue: make(chan *Event, maxQueueLength),
|
||||
Queue: make(chan int, MaxQueueLength),
|
||||
}
|
||||
|
||||
ueqs[sid] = q
|
||||
|
||||
go func() { // start listening
|
||||
defer gulu.Panic.Recover(nil)
|
||||
go func() { // 队列开始监听事件
|
||||
for evtCode := range q.Queue {
|
||||
glog.V(5).Infof("Session [%s] received a event [%d]", sid, evtCode)
|
||||
|
||||
for evt := range q.Queue {
|
||||
logger.Debugf("Session [%s] received an event [%d]", sid, evt.Code)
|
||||
|
||||
// process event by each handlers
|
||||
// 将事件交给事件处理器进行处理
|
||||
for _, handler := range q.Handlers {
|
||||
handler.Handle(evt)
|
||||
handler.Handle(&Event{Code: evtCode, Sid: sid})
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -121,24 +91,25 @@ func (ueqs queues) New(sid string) *UserEventQueue {
|
|||
return q
|
||||
}
|
||||
|
||||
// Close closes a user event queue with the specified wide session id.
|
||||
func (ueqs queues) Close(sid string) {
|
||||
// 关闭一个用户事件队列.
|
||||
func (ueqs Queues) Close(sid string) {
|
||||
q := ueqs[sid]
|
||||
if nil == q {
|
||||
return
|
||||
}
|
||||
|
||||
if q, ok := ueqs[sid]; ok {
|
||||
close(q.Queue)
|
||||
delete(ueqs, sid)
|
||||
}
|
||||
}
|
||||
|
||||
// Handler represents an event handler.
|
||||
// 事件处理接口.
|
||||
type Handler interface {
|
||||
Handle(event *Event)
|
||||
}
|
||||
|
||||
// HandleFunc represents a handler function.
|
||||
// 函数指针包装.
|
||||
type HandleFunc func(event *Event)
|
||||
|
||||
// Default implementation of event handling.
|
||||
// 事件处理默认实现.
|
||||
func (fn HandleFunc) Handle(event *Event) {
|
||||
fn(event)
|
||||
}
|
||||
|
|
102
file/exporter.go
|
@ -1,102 +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 file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
)
|
||||
|
||||
// GetZipHandler handles request of retrieving zip file.
|
||||
func GetZipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
path := q["path"][0]
|
||||
|
||||
if ".zip" != filepath.Ext(path) {
|
||||
http.Error(w, "Bad Request", 400)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !gulu.File.IsExist(path) {
|
||||
http.Error(w, "Not Found", 404)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filename := filepath.Base(path)
|
||||
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
http.ServeFile(w, r, path)
|
||||
|
||||
os.Remove(path)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
data.Code = -1
|
||||
|
||||
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 + "]"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
zipPath := filepath.Join(dir, name)
|
||||
zipFile, err := gulu.Zip.Create(zipPath + ".zip")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
data.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
if gulu.File.IsDir(path) {
|
||||
zipFile.AddDirectory(base, path)
|
||||
} else {
|
||||
zipFile.AddEntry(base, path)
|
||||
}
|
||||
|
||||
data.Data = zipPath
|
||||
}
|
754
file/files.go
159
file/outline.go
|
@ -1,159 +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 file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
)
|
||||
|
||||
type element struct {
|
||||
Name string
|
||||
Line int
|
||||
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)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "", code, 0)
|
||||
if err != nil {
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
result.Data = &data
|
||||
|
||||
// ast.Print(fset, f)
|
||||
|
||||
line, ch := getCursor(code, int(f.Name.Pos()))
|
||||
data["package"] = &element{Name: f.Name.Name, Line: line, Ch: ch}
|
||||
|
||||
imports := []*element{}
|
||||
for _, astImport := range f.Imports {
|
||||
line, ch := getCursor(code, int(astImport.Path.Pos()))
|
||||
|
||||
imports = append(imports, &element{Name: astImport.Path.Value, Line: line, Ch: ch})
|
||||
}
|
||||
data["imports"] = imports
|
||||
|
||||
funcDecls := []*element{}
|
||||
varDecls := []*element{}
|
||||
constDecls := []*element{}
|
||||
structDecls := []*element{}
|
||||
interfaceDecls := []*element{}
|
||||
typeDecls := []*element{}
|
||||
for _, decl := range f.Decls {
|
||||
switch decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
funcDecl := decl.(*ast.FuncDecl)
|
||||
|
||||
line, ch := getCursor(code, int(funcDecl.Name.Pos()))
|
||||
|
||||
funcDecls = append(funcDecls, &element{Name: funcDecl.Name.Name, Line: line, Ch: ch})
|
||||
case *ast.GenDecl:
|
||||
genDecl := decl.(*ast.GenDecl)
|
||||
|
||||
for _, spec := range genDecl.Specs {
|
||||
|
||||
switch genDecl.Tok {
|
||||
case token.VAR:
|
||||
variableSpec := spec.(*ast.ValueSpec)
|
||||
|
||||
for _, varName := range variableSpec.Names {
|
||||
line, ch := getCursor(code, int(varName.Pos()))
|
||||
|
||||
varDecls = append(varDecls, &element{Name: varName.Name, Line: line, Ch: ch})
|
||||
}
|
||||
case token.TYPE:
|
||||
typeSpec := spec.(*ast.TypeSpec)
|
||||
line, ch := getCursor(code, int(typeSpec.Pos()))
|
||||
|
||||
switch typeSpec.Type.(type) {
|
||||
case *ast.StructType:
|
||||
structDecls = append(structDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
|
||||
case *ast.InterfaceType:
|
||||
interfaceDecls = append(interfaceDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
|
||||
case *ast.Ident:
|
||||
typeDecls = append(typeDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch})
|
||||
}
|
||||
case token.CONST:
|
||||
constSpec := spec.(*ast.ValueSpec)
|
||||
|
||||
for _, constName := range constSpec.Names {
|
||||
line, ch := getCursor(code, int(constName.Pos()))
|
||||
|
||||
constDecls = append(constDecls, &element{Name: constName.Name, Line: line, Ch: ch})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data["funcDecls"] = funcDecls
|
||||
data["varDecls"] = varDecls
|
||||
data["constDecls"] = constDecls
|
||||
data["structDecls"] = structDecls
|
||||
data["interfaceDecls"] = interfaceDecls
|
||||
data["typeDecls"] = typeDecls
|
||||
}
|
||||
|
||||
// getCursor calculates the cursor position (line, ch) by the specified offset.
|
||||
//
|
||||
// line is the line number, starts with 0 that means the first line
|
||||
// ch is the column number, starts with 0 that means the first column
|
||||
func getCursor(code string, offset int) (line, ch int) {
|
||||
code = code[:offset]
|
||||
|
||||
lines := strings.Split(code, "\n")
|
||||
|
||||
line = 0
|
||||
for range lines {
|
||||
line++
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
runes := []rune(lines[line-1])
|
||||
for _, r := range runes {
|
||||
buffer.WriteString(string(r))
|
||||
}
|
||||
|
||||
ch = len(buffer.String())
|
||||
|
||||
return line - 1, ch - 1
|
||||
}
|
23
go.mod
|
@ -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
|
@ -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=
|
150
gulpfile.js
|
@ -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)))
|
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
"colon": ": ",
|
||||
"wide": "Wide",
|
||||
"wide_title": "Playing golang, anytime, anywhere",
|
||||
"isDelete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"file": "File",
|
||||
"login": "Login",
|
||||
"username": "Username",
|
||||
"current_user": "Current User",
|
||||
"current_session": "Current Session",
|
||||
"password": "Password",
|
||||
"login_error": "Login Error",
|
||||
"login_failed": "Login Failed",
|
||||
"run": "Run",
|
||||
"debug": "Debug",
|
||||
"help": "Help",
|
||||
"check_update": "Check Update",
|
||||
"issues": "Issues",
|
||||
"report_issues": "Report Issues",
|
||||
"wide_doc": "Wide Document",
|
||||
"about": "About",
|
||||
"start_page": "Start Page",
|
||||
|
@ -22,12 +20,12 @@
|
|||
"create": "Create",
|
||||
"create_dir": "Create Dir",
|
||||
"delete": "Delete",
|
||||
"rename": "Rename",
|
||||
"save": "Save",
|
||||
"exit": "Exit",
|
||||
"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",
|
||||
|
@ -37,9 +35,7 @@
|
|||
"unread_notification": "Unread",
|
||||
"notification_2": "Not found [gocode], thereby [Autocomplete] will not work",
|
||||
"notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work",
|
||||
"notification_4": "Server Internal Error",
|
||||
"goto_line": "Goto Line",
|
||||
"goto_file": "Goto File",
|
||||
"go": "Go",
|
||||
"tip": "Tip",
|
||||
"confirm": "Confirm",
|
||||
|
@ -55,7 +51,7 @@
|
|||
"find_previous": "Find Previous",
|
||||
"replace": "Replace",
|
||||
"replace_all": "Replace All",
|
||||
"restore_bottom": "Restore Bottom Windows",
|
||||
"restore_bottom": "Bottom Windows Restore",
|
||||
"file_format": "File Extension",
|
||||
"keyword": "Keyword",
|
||||
"user_guide": "User Guide",
|
||||
|
@ -64,7 +60,7 @@
|
|||
"ver": "Version",
|
||||
"current_ver": "Current Version",
|
||||
"dev_team": "Dev Team",
|
||||
"donate": "Donate",
|
||||
"donate_us": "Donate Us",
|
||||
"confirm_save": "Confirm Save",
|
||||
"workspace": "Workspace",
|
||||
"project_address": "Project",
|
||||
|
@ -74,10 +70,6 @@
|
|||
"show_expr_info": "Show Expression Info",
|
||||
"find_usages": "Find Usages",
|
||||
"delete_line": "Delete Line",
|
||||
"copy_lines_up": "Copy Lines Up",
|
||||
"copy_lines_down": "Copy Lines Down",
|
||||
"move_lines_up": "Move Lines Up",
|
||||
"move_lines_down": "Move Lines Down",
|
||||
"save_editor_file": "Save File",
|
||||
"save_all_editors_files": "Save All",
|
||||
"close_editor": "Close File",
|
||||
|
@ -94,13 +86,13 @@
|
|||
"focus_notification": "Focus to Notification",
|
||||
"start-build": "START [go build]",
|
||||
"build-succ": "[go build] SUCCESS",
|
||||
"build-error": "[go build] ERROR",
|
||||
"start-test": "START [go test]",
|
||||
"test-succ": "[go test] SUCCESS",
|
||||
"test-error": "[go test] ERROR",
|
||||
"build-failed": "[go build] Failed",
|
||||
"start-install": "START [go install]",
|
||||
"install-succ": "[go install] SUCCESS",
|
||||
"install-error": "[go install] ERROR",
|
||||
"install-failed": "[go install] Fialed",
|
||||
"start-get": "START [go get]",
|
||||
"get-succ": "[go get] SUCCESS",
|
||||
"get-failed": "[go get] Failed",
|
||||
"check_version": "Checking update",
|
||||
"new_version_available": "new version available",
|
||||
"go_env": "Go",
|
||||
|
@ -109,63 +101,5 @@
|
|||
"license": "License",
|
||||
"credits": "Credits",
|
||||
"uptodate": "it is up to date",
|
||||
"test": "Test",
|
||||
"sign_up": "Sign Up",
|
||||
"team": "Team",
|
||||
"sing_up_error": "Sign Up Error",
|
||||
"user_name_ruler": "Username only by az, AZ, 0-9, _ consisting of a length of 16",
|
||||
"password_no_match": "Password doesn't match the confirmation",
|
||||
"discard": "Discard",
|
||||
"close": "Close",
|
||||
"close_other": "Close Other",
|
||||
"clear": "Clear",
|
||||
"preference": "Preference",
|
||||
"appearence": "Appearence",
|
||||
"gotool": "Go Tool",
|
||||
"user": "User",
|
||||
"font": "Font",
|
||||
"font_size": "Font Size",
|
||||
"line_height": "Line Height",
|
||||
"go_format": "Go Format",
|
||||
"locale": "Locale",
|
||||
"apply": "Apply",
|
||||
"clearOutput": "Clear Output",
|
||||
"export": "Export",
|
||||
"refresh": "Refresh",
|
||||
"theme": "Theme",
|
||||
"tab_size": "Tab Size",
|
||||
"copy_file_path": "Copy File Path",
|
||||
"file_tree": "File Tree",
|
||||
"select": "Select",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"edit": "Edit",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo",
|
||||
"cut": "Cut",
|
||||
"copy": "Copy",
|
||||
"paste": "Paste",
|
||||
"select_all": "Select All",
|
||||
"select_identifier": "Select Identifier",
|
||||
"source": "Source",
|
||||
"toggle_comment": "Toggle Comment",
|
||||
"find_in_files": "Find in Files",
|
||||
"no_empty": "Can not Empty!",
|
||||
"open": "Open",
|
||||
"search_no_match": "No matching files were found.",
|
||||
"outline": "Outline",
|
||||
"govet": "go vet",
|
||||
"start-vet": "START [go vet]",
|
||||
"vet-succ": "[go vet] SUCCESS",
|
||||
"vet-error": "[go vet] ERROR",
|
||||
"restore_outline": "Restore Outline",
|
||||
"share": "Share",
|
||||
"url": "URL",
|
||||
"embeded": "Embeded",
|
||||
"terms": "Terms",
|
||||
"download": "Download",
|
||||
"decompress": "Decompress",
|
||||
"keymap": "Keymap",
|
||||
"resize": "Resize",
|
||||
"sponsor": "Sponsor"
|
||||
"colon": ": "
|
||||
}
|
171
i18n/ja_JP.json
|
@ -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": "Wide について",
|
||||
"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] が見つかりません。[Autocomplete] は動作しません。",
|
||||
"notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。",
|
||||
"notification_4": "内部サーバーエラー",
|
||||
"goto_line": "指定行にジャンプ",
|
||||
"goto_file": "ファイルをオープンする",
|
||||
"go": "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": "式の情報を表示",
|
||||
"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": "タブを切り替える",
|
||||
"focus_editor": "エディタにフォーカスを与える",
|
||||
"focus_file_tree": "ファイルツリーにフォーカスを与える",
|
||||
"focus_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": "OS",
|
||||
"project": "プロジェクト",
|
||||
"license": "ライセンス",
|
||||
"credits": "クレジット",
|
||||
"uptodate": "最新です",
|
||||
"test": "テスト",
|
||||
"sign_up": "登録",
|
||||
"team": "チーム",
|
||||
"sing_up_error": "登録に失敗しました",
|
||||
"user_name_ruler": "16の長さからなる_ AZ、AZ、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": "空の出力",
|
||||
"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": "スポンサー"
|
||||
}
|
171
i18n/ko_KR.json
|
@ -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": "후원사"
|
||||
}
|
|
@ -1,66 +1,35 @@
|
|||
// 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 i18n includes internationalization related manipulations.
|
||||
// 国际化操作.
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
|
||||
// Locale.
|
||||
type locale struct {
|
||||
Name string
|
||||
Langs map[string]interface{}
|
||||
TimeZone string
|
||||
}
|
||||
|
||||
// All locales.
|
||||
// 所有的 locales.
|
||||
var Locales = map[string]locale{}
|
||||
|
||||
// Load loads i18n message configurations.
|
||||
// 加载国际化配置.
|
||||
func Load() {
|
||||
f, _ := os.Open("i18n")
|
||||
names, _ := f.Readdirnames(-1)
|
||||
f.Close()
|
||||
// TODO: 自动加载所有语言配置
|
||||
|
||||
if len(Locales) == len(names)-1 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if !strings.HasSuffix(name, ".json") {
|
||||
continue
|
||||
}
|
||||
|
||||
loc := name[:strings.LastIndex(name, ".")]
|
||||
load(loc)
|
||||
}
|
||||
load("zh_CN")
|
||||
load("en_US")
|
||||
}
|
||||
|
||||
func load(localeStr string) {
|
||||
bytes, err := os.ReadFile("i18n/" + localeStr + ".json")
|
||||
bytes, err := ioutil.ReadFile("i18n/" + localeStr + ".json")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
glog.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
@ -69,33 +38,22 @@ func load(localeStr string) {
|
|||
|
||||
err = json.Unmarshal(bytes, &l.Langs)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
glog.Error(err)
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
Locales[localeStr] = l
|
||||
|
||||
glog.V(5).Infof("Loaded [%s] locale configuration", localeStr)
|
||||
}
|
||||
|
||||
// Get gets a message with the specified locale and key.
|
||||
// 获取语言配置项.
|
||||
func Get(locale, key string) interface{} {
|
||||
return Locales[locale].Langs[key]
|
||||
}
|
||||
|
||||
// GetAll gets all messages with the specified locale.
|
||||
// 获取语言配置.
|
||||
func GetAll(locale string) map[string]interface{} {
|
||||
return Locales[locale].Langs
|
||||
}
|
||||
|
||||
// GetLocalesNames gets names of all locales. Returns ["zh_CN", "en_US"] for example.
|
||||
func GetLocalesNames() []string {
|
||||
ret := []string{}
|
||||
|
||||
for name := range Locales {
|
||||
ret = append(ret, name)
|
||||
}
|
||||
|
||||
sort.Strings(ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
"colon": ":",
|
||||
"wide": "Wide",
|
||||
"wide_title": "随时随地玩 golang",
|
||||
"isDelete": "是否删除",
|
||||
"cancel": "取消",
|
||||
"file": "文件",
|
||||
"login": "登录",
|
||||
"username": "用户名",
|
||||
"current_user": "当前用户",
|
||||
"current_session": "当前会话",
|
||||
"password": "密码",
|
||||
"login_error": "登录失败",
|
||||
"login_failed": "登录失败",
|
||||
"run": "运行",
|
||||
"debug": "调试",
|
||||
"help": "帮助",
|
||||
"check_update": "检查更新",
|
||||
"issues": "问题",
|
||||
"report_issues": "建议",
|
||||
"wide_doc": "Wide 文档",
|
||||
"about": "关于",
|
||||
"start_page": "起始页",
|
||||
|
@ -22,12 +20,12 @@
|
|||
"create": "创建",
|
||||
"create_dir": "创建目录",
|
||||
"delete": "删除",
|
||||
"rename": "重命名",
|
||||
"save": "保存",
|
||||
"exit": "退出",
|
||||
"close_all_files": "关闭所有文件",
|
||||
"save_all_files": "保存所有文件",
|
||||
"format": "格式化",
|
||||
"goget": "go get",
|
||||
"goinstall": "go install",
|
||||
"build": "构建",
|
||||
"build_n_run": "构建并运行",
|
||||
|
@ -37,9 +35,7 @@
|
|||
"unread_notification": "未读通知",
|
||||
"notification_2": "没有检查到 gocode,这将会导致 [自动完成] 失效",
|
||||
"notification_3": "没有检查到 ide_stub,这将会导致 [跳转到声明]、[查找使用] 失效",
|
||||
"notification_4": "服务器内部错误",
|
||||
"goto_line": "跳转到行",
|
||||
"goto_file": "打开文件",
|
||||
"go": "跳转",
|
||||
"tip": "提示",
|
||||
"confirm": "确定",
|
||||
|
@ -64,7 +60,7 @@
|
|||
"ver": "版本",
|
||||
"current_ver": "当前版本",
|
||||
"dev_team": "开发团队",
|
||||
"donate": "捐赠我们",
|
||||
"donate_us": "捐赠我们",
|
||||
"confirm_save": "请确认所有文件已保存",
|
||||
"workspace": "工作空间",
|
||||
"project_address": "项目地址",
|
||||
|
@ -74,10 +70,6 @@
|
|||
"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": "保存所有编辑器文件",
|
||||
"close_editor": "关闭当前编辑器",
|
||||
|
@ -94,13 +86,13 @@
|
|||
"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] 失败",
|
||||
"build-failed": "[go build] 失败",
|
||||
"start-install": "开始 [go install]",
|
||||
"install-succ": "[go install] 成功",
|
||||
"install-error": "[go install] 失败",
|
||||
"install-failed": "[go install] 失败",
|
||||
"start-get": "开始 [go get]",
|
||||
"get-succ": "[go get] 成功",
|
||||
"get-failed": "[go get] 失败",
|
||||
"check_version": "正在检查更新",
|
||||
"new_version_available": "新版本可用",
|
||||
"go_env": "Go 环境",
|
||||
|
@ -109,63 +101,5 @@
|
|||
"license": "许可协议",
|
||||
"credits": "致谢",
|
||||
"uptodate": "已是最新版本",
|
||||
"test": "测试",
|
||||
"sign_up": "注册",
|
||||
"team": "团队",
|
||||
"sing_up_error": "注册失败",
|
||||
"user_name_ruler": "用户名只能由 a-z, A-Z, 0-9, _ 组成,长度为16",
|
||||
"password_no_match": "密码输入不一致",
|
||||
"discard": "放弃",
|
||||
"close": "关闭",
|
||||
"close_other": "关闭其它",
|
||||
"clear": "清空",
|
||||
"preference": "偏好设定",
|
||||
"appearence": "外观",
|
||||
"gotool": "Go 工具",
|
||||
"user": "用户",
|
||||
"font": "字体",
|
||||
"font_size": "字体大小",
|
||||
"line_height": "行高",
|
||||
"go_format": "Go 格式化",
|
||||
"locale": "语言环境",
|
||||
"apply": "应用",
|
||||
"clearOutput": "清空输出",
|
||||
"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": "赞助"
|
||||
"colon": ":"
|
||||
}
|
171
i18n/zh_TW.json
|
@ -1,171 +0,0 @@
|
|||
{
|
||||
"colon": ":",
|
||||
"wide": "Wide",
|
||||
"wide_title": "隨時隨地玩 golang",
|
||||
"cancel": "取消",
|
||||
"file": "檔案",
|
||||
"login": "登入",
|
||||
"username": "使用者",
|
||||
"current_user": "當前使用者",
|
||||
"current_session": "當前會話",
|
||||
"password": "密碼",
|
||||
"login_error": "登入失敗",
|
||||
"run": "執行",
|
||||
"debug": "Debug",
|
||||
"help": "說明書",
|
||||
"check_update": "檢查更新?",
|
||||
"issues": "問題",
|
||||
"wide_doc": "Wide 說明",
|
||||
"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_us": "愛心捐贈",
|
||||
"confirm_save": "請確認所有檔案都已儲存",
|
||||
"workspace": "工作空間",
|
||||
"project_address": "項目地址",
|
||||
"community": "社區",
|
||||
"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": "儲存所有檔案",
|
||||
"close_editor": "關閉當前編輯器",
|
||||
"full_screen": "全螢幕",
|
||||
"auto_indent": "自動縮進",
|
||||
"indent": "縮進",
|
||||
"unindent": "縮進還原",
|
||||
"focus": "焦點",
|
||||
"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] 失敗",
|
||||
"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": "帳號只能由 az, AZ, 0-9, _ 組成,長度為16",
|
||||
"password_no_match": "密碼輸入不一致",
|
||||
"discard": "捨棄",
|
||||
"close": "關閉",
|
||||
"close_other": "關閉其它",
|
||||
"clear": "清空",
|
||||
"preference": "偏好設定",
|
||||
"appearence": "外觀",
|
||||
"gotool": "Go 工具",
|
||||
"user": "使用者",
|
||||
"font": "字體",
|
||||
"font_size": "字體大小",
|
||||
"line_height": "行高",
|
||||
"go_format": "Go 格式化",
|
||||
"locale": "語言環境",
|
||||
"apply": "應用",
|
||||
"clearOutput": "清空輸出",
|
||||
"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": "贊助"
|
||||
}
|
537
main.go
|
@ -1,216 +1,163 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"strconv"
|
||||
"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/notification"
|
||||
"github.com/b3log/wide/output"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/shell"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Logger
|
||||
var logger *gulu.Logger
|
||||
const (
|
||||
Ver = "1.0.0" // 当前 Wide 版本
|
||||
)
|
||||
|
||||
// The only one init function in Wide.
|
||||
// Wide 中唯一一个 init 函数.
|
||||
func init() {
|
||||
confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
|
||||
confData := flag.String("data", "", "path of data dir")
|
||||
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")
|
||||
|
||||
// TODO: 默认启动参数
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Set("v", "3")
|
||||
flag.Parse()
|
||||
|
||||
gulu.Log.SetLevel("warn")
|
||||
logger = gulu.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)
|
||||
//}
|
||||
|
||||
i18n.Load()
|
||||
// 加载事件处理
|
||||
event.Load()
|
||||
conf.Load(*confPath, *confData, *confServer, *confLogLevel, *confReadOnly, template.HTML(*confSiteStatCode))
|
||||
|
||||
// 加载配置
|
||||
conf.Load()
|
||||
|
||||
// 定时检查运行环境
|
||||
conf.FixedTimeCheckEnv()
|
||||
session.FixedTimeSave()
|
||||
|
||||
// 定时保存配置
|
||||
conf.FixedTimeSave()
|
||||
|
||||
// 定时检查无效会话
|
||||
session.FixedTimeRelease()
|
||||
session.FixedTimeReport()
|
||||
|
||||
logger.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
|
||||
}
|
||||
|
||||
// Main.
|
||||
func main() {
|
||||
initMime()
|
||||
handleSignal()
|
||||
// 登录.
|
||||
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
i18n.Load()
|
||||
|
||||
// IDE
|
||||
http.HandleFunc("/", handlerGzWrapper(indexHandler))
|
||||
http.HandleFunc("/start", handlerWrapper(startHandler))
|
||||
http.HandleFunc("/about", handlerWrapper(aboutHandler))
|
||||
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
|
||||
if "GET" == r.Method {
|
||||
// 展示登录页面
|
||||
|
||||
// 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")
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
|
||||
"locale": conf.Wide.Locale, "ver": Ver}
|
||||
|
||||
// oauth
|
||||
http.HandleFunc("/login/redirect", session.LoginRedirectHandler)
|
||||
http.HandleFunc("/login/callback", session.LoginCallbackHandler)
|
||||
t, err := template.ParseFiles("views/login.html")
|
||||
|
||||
// session
|
||||
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
|
||||
http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
// 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))
|
||||
return
|
||||
}
|
||||
|
||||
// cross-compilation
|
||||
http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
|
||||
t.Execute(w, model)
|
||||
|
||||
// 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))
|
||||
return
|
||||
}
|
||||
|
||||
// outline
|
||||
http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
|
||||
// 非 GET 请求当作是登录请求
|
||||
succ := false
|
||||
|
||||
// file export
|
||||
http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
|
||||
http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
|
||||
data := map[string]interface{}{"succ": &succ}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
// 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))
|
||||
args := struct {
|
||||
Username string
|
||||
Password string
|
||||
}{}
|
||||
|
||||
// notification
|
||||
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
succ = true
|
||||
|
||||
// user
|
||||
http.HandleFunc("/login", handlerWrapper(session.LoginHandler))
|
||||
http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
|
||||
http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
|
||||
return
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
logger.Infof("Wide is running [%s]", conf.Wide.Server)
|
||||
|
||||
err := http.ListenAndServe("0.0.0.0:7070", nil)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
for _, user := range conf.Wide.Users {
|
||||
if user.Name == args.Username && user.Password == args.Password {
|
||||
succ = true
|
||||
}
|
||||
}
|
||||
|
||||
// indexHandler handles request of Wide index.
|
||||
if !succ {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建 HTTP 会话
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
httpSession.Values["username"] = args.Username
|
||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
glog.Infof("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
|
||||
}
|
||||
|
||||
// 退出(登出).
|
||||
func logoutHandler(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")
|
||||
|
||||
httpSession.Options.MaxAge = -1
|
||||
httpSession.Save(r, w)
|
||||
}
|
||||
|
||||
// Wide 首页.
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if "/" != r.RequestURI {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
i18n.Load()
|
||||
|
||||
return
|
||||
}
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
if "playground" == uid { // reserved user for Playground
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
user := conf.GetUser(uid)
|
||||
if nil == user {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
// 创建一个 Wide 会话
|
||||
wideSession := session.WideSessions.New(httpSession)
|
||||
|
||||
return
|
||||
}
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
locale := user.Locale
|
||||
|
||||
wideSessions := session.WideSessions.GetByUserId(uid)
|
||||
wideSessions := session.WideSessions.GetByUsername(username)
|
||||
userConf := conf.Wide.GetUser(username)
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||
"uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
|
||||
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
||||
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": []string{"darwin_amd64", "linux_amd64", "windows_amd64"}}
|
||||
"session": wideSession, "latestSessionContent": userConf.LatestSessionContent,
|
||||
"pathSeparator": conf.PathSeparator}
|
||||
|
||||
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
|
||||
glog.V(3).Infof("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)
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -218,70 +165,19 @@ 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) {
|
||||
http.ServeFile(w, r, filename)
|
||||
})
|
||||
}
|
||||
|
||||
// startHandler handles request of start page.
|
||||
// 起始页请求处理.
|
||||
func startHandler(w http.ResponseWriter, r *http.Request) {
|
||||
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/s", http.StatusFound)
|
||||
i18n.Load()
|
||||
|
||||
return
|
||||
}
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
user := conf.GetUser(uid)
|
||||
locale := user.Locale
|
||||
userWorkspace := conf.GetUserWorkspace(uid)
|
||||
|
||||
sid := r.URL.Query()["sid"][0]
|
||||
wSession := session.WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
logger.Errorf("Session [%s] not found", sid)
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
t, err := template.ParseFiles("views/start.html")
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// keyboardShortcutsHandler handles request of keyboard shortcuts page.
|
||||
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
|
@ -291,16 +187,50 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
locale := conf.GetUser(uid).Locale
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
userWorkspace := conf.Wide.GetUserWorkspace(username)
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||
"username": username, "workspace": userWorkspace, "ver": Ver}
|
||||
|
||||
t, err := template.ParseFiles("views/start.html")
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// 键盘快捷键页请求处理.
|
||||
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
i18n.Load()
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
|
||||
|
||||
t, err := template.ParseFiles("views/keyboard_shortcuts.html")
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -308,9 +238,12 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// aboutHandle handles request of about page.
|
||||
// 关于页请求处理.
|
||||
func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
i18n.Load()
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
|
@ -320,17 +253,17 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
|||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
locale := conf.GetUser(uid).Locale
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.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()}
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, "ver": Ver,
|
||||
"goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}
|
||||
|
||||
t, err := template.ParseFiles("views/about.html")
|
||||
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -338,104 +271,112 @@ func aboutHandler(w http.ResponseWriter, r *http.Request) {
|
|||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// handlerWrapper wraps the HTTP Handler for some common processes.
|
||||
// 主程序入口.
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(conf.Wide.MaxProcs)
|
||||
|
||||
defer glog.Flush()
|
||||
|
||||
// IDE
|
||||
http.HandleFunc("/login", handlerWrapper(loginHandler))
|
||||
http.HandleFunc("/logout", handlerWrapper(logoutHandler))
|
||||
http.HandleFunc("/", handlerWrapper(indexHandler))
|
||||
http.HandleFunc("/start", handlerWrapper(startHandler))
|
||||
http.HandleFunc("/about", handlerWrapper(aboutHandler))
|
||||
http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
|
||||
|
||||
// 静态资源
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
serveSingle("/favicon.ico", "./static/favicon.ico")
|
||||
|
||||
// 库资源
|
||||
http.Handle("/data/", http.StripPrefix("/data/", http.FileServer(http.Dir("data"))))
|
||||
|
||||
// 会话
|
||||
http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
|
||||
http.HandleFunc("/session/save", handlerWrapper(session.SaveContent))
|
||||
|
||||
// 运行相关
|
||||
http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
|
||||
http.HandleFunc("/run", handlerWrapper(output.RunHandler))
|
||||
http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
|
||||
http.HandleFunc("/go/get", handlerWrapper(output.GoGetHandler))
|
||||
http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
|
||||
http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
|
||||
|
||||
// 文件树
|
||||
http.HandleFunc("/files", handlerWrapper(file.GetFiles))
|
||||
http.HandleFunc("/file", handlerWrapper(file.GetFile))
|
||||
http.HandleFunc("/file/save", handlerWrapper(file.SaveFile))
|
||||
http.HandleFunc("/file/new", handlerWrapper(file.NewFile))
|
||||
http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFile))
|
||||
http.HandleFunc("/file/search/text", handlerWrapper(file.SearchText))
|
||||
|
||||
// 编辑器
|
||||
http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler))
|
||||
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("/html/fmt", handlerWrapper(editor.HTMLFmtHandler))
|
||||
http.HandleFunc("/json/fmt", handlerWrapper(editor.JSONFmtHandler))
|
||||
|
||||
// Shell
|
||||
http.HandleFunc("/shell/ws", handlerWrapper(shell.WSHandler))
|
||||
http.HandleFunc("/shell", handlerWrapper(shell.IndexHandler))
|
||||
|
||||
// 通知
|
||||
http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
|
||||
|
||||
// 用户
|
||||
http.HandleFunc("/user/new", handlerWrapper(session.AddUser))
|
||||
http.HandleFunc("/user/repos/init", handlerWrapper(session.InitGitRepos))
|
||||
|
||||
// 文档
|
||||
http.Handle("/doc/", http.StripPrefix("/doc/", http.FileServer(http.Dir("doc"))))
|
||||
|
||||
glog.V(0).Infof("Wide is running [%s]", conf.Wide.Server)
|
||||
|
||||
err := http.ListenAndServe(conf.Wide.Server, nil)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Handler 包装,完成共性处理.
|
||||
//
|
||||
// 共性处理:
|
||||
//
|
||||
// 1. panic recover
|
||||
// 2. request stopwatch
|
||||
// 3. i18n
|
||||
// 2. 请求计时
|
||||
func handlerWrapper(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := panicRecover(f)
|
||||
handler = stopwatch(handler)
|
||||
handler = i18nLoad(handler)
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// handlerGzWrapper wraps the HTTP Handler for some common processes.
|
||||
//
|
||||
// 1. panic recover
|
||||
// 2. gzip response
|
||||
// 3. request stopwatch
|
||||
// 4. i18n
|
||||
func handlerGzWrapper(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := panicRecover(f)
|
||||
handler = gzipWrapper(handler)
|
||||
handler = stopwatch(handler)
|
||||
handler = i18nLoad(handler)
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// gzipWrapper wraps the process with response gzip.
|
||||
func gzipWrapper(f func(http.ResponseWriter, *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
f(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
|
||||
f(gzr, r)
|
||||
}
|
||||
}
|
||||
|
||||
// i18nLoad wraps the i18n process.
|
||||
func i18nLoad(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
i18n.Load()
|
||||
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// stopwatch wraps the request stopwatch process.
|
||||
// Handler 包装请求计时.
|
||||
func stopwatch(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
defer func() {
|
||||
logger.Tracef("[%s, %s, %s]", r.Method, r.RequestURI, time.Since(start))
|
||||
glog.V(5).Infof("[%s] [%s]", r.RequestURI, time.Since(start))
|
||||
}()
|
||||
|
||||
// Handler 处理
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// panicRecover wraps the panic recover process.
|
||||
// Handler 包装 recover panic.
|
||||
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 处理
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// initMime initializes mime types.
|
||||
//
|
||||
// We can't get the mime types on some OS (such as Windows XP) by default, so initializes them here.
|
||||
func initMime() {
|
||||
mime.AddExtensionType(".css", "text/css")
|
||||
mime.AddExtensionType(".js", "application/x-javascript")
|
||||
mime.AddExtensionType(".json", "application/json")
|
||||
}
|
||||
|
||||
// gzipResponseWriter represents a gzip response writer.
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
// Write writes response with appropriate 'Content-Type'.
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
if "" == w.Header().Get("Content-Type") {
|
||||
// If no content type, apply sniffing algorithm to un-gzipped body.
|
||||
w.Header().Set("Content-Type", http.DetectContentType(b))
|
||||
}
|
||||
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
|
|
@ -1,48 +1,30 @@
|
|||
// 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 notification includes notification related manipulations.
|
||||
// 通知.
|
||||
package notification
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"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"
|
||||
"strconv"
|
||||
"github.com/b3log/wide/conf"
|
||||
"github.com/b3log/wide/event"
|
||||
"github.com/b3log/wide/i18n"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
error = "ERROR" // notification.severity: ERROR
|
||||
warn = "WARN" // notification.severity: WARN
|
||||
info = "INFO" // notification.severity: INFO
|
||||
Error = "ERROR" // 通知.严重程度:ERROR
|
||||
Warn = "WARN" // 通知.严重程度:WARN
|
||||
Info = "INFO" // 通知.严重程度:INFO
|
||||
|
||||
setup = "Setup" // notification.type: setup
|
||||
server = "Server" // notification.type: server
|
||||
Setup = "Setup" // 通知.类型:安装
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
|
||||
// Notification represents a notification.
|
||||
// 通知结构.
|
||||
type Notification struct {
|
||||
event *event.Event
|
||||
Type string `json:"type"`
|
||||
|
@ -50,74 +32,75 @@ type Notification struct {
|
|||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// event2Notification processes user event by converting the specified event to a notification, and then push it to front
|
||||
// browser with notification channel.
|
||||
// 用户事件处理:将事件转为通知,并通过通知通道推送给前端.
|
||||
//
|
||||
// 当用户事件队列接收到事件时将会调用该函数进行处理.
|
||||
func event2Notification(e *event.Event) {
|
||||
if nil == session.NotificationWS[e.Sid] {
|
||||
return
|
||||
}
|
||||
|
||||
wsChannel := session.NotificationWS[e.Sid]
|
||||
if nil == wsChannel {
|
||||
return
|
||||
}
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, session.CookieName)
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
locale := conf.GetUser(uid).Locale
|
||||
|
||||
var notification *Notification
|
||||
var notification Notification
|
||||
|
||||
switch e.Code {
|
||||
case event.EvtCodeGocodeNotFound:
|
||||
fallthrough
|
||||
notification = Notification{event: e, Type: Setup, Severity: Error}
|
||||
case event.EvtCodeIDEStubNotFound:
|
||||
notification = &Notification{event: e, Type: setup, Severity: error,
|
||||
Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string)}
|
||||
case event.EvtCodeServerInternalError:
|
||||
notification = &Notification{event: e, Type: server, Severity: error,
|
||||
Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string) + " [" + e.Data.(string) + "]"}
|
||||
notification = Notification{event: e, Type: Setup, Severity: Error}
|
||||
default:
|
||||
logger.Warnf("Can't handle event[code=%d]", e.Code)
|
||||
|
||||
glog.Warningf("Can't handle event[code=%d]", e.Code)
|
||||
return
|
||||
}
|
||||
|
||||
wsChannel.WriteJSON(notification)
|
||||
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, "wide-session")
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
wsChannel.Refresh()
|
||||
// 消息国际化处理
|
||||
notification.Message = i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string)
|
||||
|
||||
wsChannel.Conn.WriteJSON(¬ification)
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
// WSHandler handles request of creating notification channel.
|
||||
// 建立通知通道.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sid := r.URL.Query()["sid"][0]
|
||||
|
||||
wSession := session.WideSessions.Get(sid)
|
||||
|
||||
if nil == wSession {
|
||||
glog.Errorf("Session [%s] not found", sid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
||||
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
||||
|
||||
ret := map[string]interface{}{"notification": "Notification initialized", "cmd": "init-notification"}
|
||||
err := wsChan.WriteJSON(&ret)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
session.NotificationWS[sid] = &wsChan
|
||||
|
||||
logger.Tracef("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS))
|
||||
glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS))
|
||||
|
||||
// add user event handler
|
||||
// 添加用户事件处理器
|
||||
wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification))
|
||||
|
||||
input := map[string]interface{}{}
|
||||
|
||||
for {
|
||||
if err := wsChan.ReadJSON(&input); err != nil {
|
||||
if err := wsChan.Conn.ReadJSON(&input); err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
if err.Error() == "unexpected EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
glog.Error("Notification WS ERROR: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
315
output/build.go
|
@ -1,315 +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"
|
||||
"fmt"
|
||||
"io"
|
||||
"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"
|
||||
)
|
||||
|
||||
// BuildHandler handles request of building.
|
||||
func BuildHandler(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)
|
||||
user := conf.GetUser(uid)
|
||||
locale := user.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["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
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
if _, err := fout.WriteString(code); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
fout.Close()
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
if nil != session.OutputWS[sid] {
|
||||
// display "START [go build]" in front-end browser
|
||||
|
||||
msg := i18n.Get(locale, "start-build").(string)
|
||||
msg = strings.Replace(msg, "build]", "build "+fmt.Sprint(user.BuildArgs(runtime.GOOS))+"]", 1)
|
||||
|
||||
channelRet["output"] = "<span class='start-build'>" + msg + "</span>\n"
|
||||
channelRet["cmd"] = "start-build"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
wsChannel.WriteJSON(&channelRet)
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
var goModCmd *exec.Cmd
|
||||
if !gulu.File.IsExist(filepath.Join(curDir, "go.mod")) {
|
||||
curDirName := filepath.Base(curDir)
|
||||
goModCmd = exec.Command("go", "mod", "init", curDirName)
|
||||
} else {
|
||||
goModCmd = exec.Command("go", "mod", "tidy")
|
||||
}
|
||||
goModCmd.Dir = curDir
|
||||
setCmdEnv(goModCmd, uid)
|
||||
outputBytes, err := goModCmd.CombinedOutput()
|
||||
output := string(outputBytes)
|
||||
if nil != err && strings.Contains(output, "go.mod already exists") {
|
||||
logger.Error(err.Error() + ": " + output)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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() {
|
||||
suffix = ".exe"
|
||||
}
|
||||
executable := filepath.Base(curDir) + suffix
|
||||
executable = filepath.Join(curDir, executable)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
channelRet["cmd"] = "build"
|
||||
channelRet["executable"] = executable
|
||||
|
||||
outReader := bufio.NewReader(stdout)
|
||||
|
||||
/////////
|
||||
go func() {
|
||||
defer gulu.Panic.Recover(nil)
|
||||
|
||||
for {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
if nil == wsChannel {
|
||||
break
|
||||
}
|
||||
|
||||
line, err := outReader.ReadString('\n')
|
||||
if io.EOF == err {
|
||||
break
|
||||
}
|
||||
|
||||
_, ok := err.(*os.PathError)
|
||||
if ok {
|
||||
// 构建时报 “read |0: file already closed” https://github.com/b3log/wide/issues/363
|
||||
break
|
||||
}
|
||||
|
||||
if nil != err {
|
||||
logger.Warnf("%#v", err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
channelRet["output"] = line
|
||||
|
||||
err = wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}()
|
||||
|
||||
errReader := bufio.NewReader(stderr)
|
||||
var lines []string
|
||||
for {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
if nil == wsChannel {
|
||||
break
|
||||
}
|
||||
|
||||
line, err := errReader.ReadString('\n')
|
||||
if io.EOF == err {
|
||||
break
|
||||
}
|
||||
|
||||
lines = append(lines, line)
|
||||
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// path process
|
||||
errOutWithPath := parsePath(curDir, line)
|
||||
channelRet["output"] = "<span class='stderr'>" + errOutWithPath + "</span>"
|
||||
|
||||
err = wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
break
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
// lint process
|
||||
if lines[0][0] == '#' {
|
||||
lines = lines[1:] // skip the first line
|
||||
}
|
||||
|
||||
lints := []*Lint{}
|
||||
|
||||
for _, line := range lines {
|
||||
if len(line) < 1 || !strings.Contains(line, ":") {
|
||||
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.ToSlash(filepath.Join(curDir, file)),
|
||||
LineNo: lineNo - 1,
|
||||
Severity: lintSeverityError,
|
||||
Msg: msg,
|
||||
}
|
||||
|
||||
lints = append(lints, lint)
|
||||
}
|
||||
|
||||
channelRet["lints"] = lints
|
||||
}
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
if nil == wsChannel {
|
||||
return
|
||||
}
|
||||
err = wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
252
output/cross.go
|
@ -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())
|
||||
}
|
|
@ -1,204 +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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
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["file"].(string)
|
||||
curDir := filepath.Dir(filePath)
|
||||
|
||||
cmd := exec.Command("go", "install")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, uid)
|
||||
|
||||
logger.Debugf("go install %s", curDir)
|
||||
|
||||
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 install]" in front-end browser
|
||||
|
||||
channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-install"
|
||||
|
||||
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()
|
||||
|
||||
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
|
||||
|
||||
// read all
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "go install"
|
||||
|
||||
if 0 != len(buf) { // build error
|
||||
// build gutter lint
|
||||
|
||||
errOut := string(buf)
|
||||
lines := strings.Split(errOut, "\n")
|
||||
|
||||
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, _ = strconv.Atoi(left[:index])
|
||||
msg = left[index+2:]
|
||||
}
|
||||
|
||||
lint := &Lint{
|
||||
File: file,
|
||||
LineNo: lineNo - 1,
|
||||
Severity: lintSeverityError,
|
||||
Msg: msg,
|
||||
}
|
||||
|
||||
lints = append(lints, lint)
|
||||
}
|
||||
|
||||
channelRet["lints"] = lints
|
||||
|
||||
channelRet["output"] = "<span class='install-error'>" + i18n.Get(locale, "install-error").(string) + "</span>\n" + errOut
|
||||
} else {
|
||||
channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n"
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
}(rand.Int())
|
||||
}
|
|
@ -1,132 +1,723 @@
|
|||
// 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 includes build, run and go tool related manipulations.
|
||||
// 构建、运行、go tool 操作.
|
||||
package output
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"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/i18n"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
lintSeverityError = "error" // lint severity: error
|
||||
lintSeverityWarn = "warning" // lint severity: warning
|
||||
lintSeverityError = "error" // Lint 严重级别:错误
|
||||
lintSeverityWarn = "warning" // Lint 严重级别:警告
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
|
||||
// Lint represents a code lint.
|
||||
// 代码 Lint 结构.
|
||||
type Lint struct {
|
||||
File string `json:"file"`
|
||||
LineNo int `json:"lineNo"`
|
||||
Severity string `json:"severity"`
|
||||
Msg string `json:"msg"`
|
||||
Msg string
|
||||
}
|
||||
|
||||
// WSHandler handles request of creating output channel.
|
||||
// 建立输出通道.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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()}
|
||||
|
||||
session.OutputWS[sid] = &wsChan
|
||||
|
||||
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-output"}
|
||||
err := wsChan.WriteJSON(&ret)
|
||||
if nil != err {
|
||||
wsChan.Conn.WriteJSON(&ret)
|
||||
|
||||
glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
|
||||
}
|
||||
|
||||
// 运行一个可执行文件.
|
||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
session.OutputWS[sid] = &wsChan
|
||||
sid := args["sid"].(string)
|
||||
wSession := session.WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
data["succ"] = false
|
||||
|
||||
logger.Tracef("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
|
||||
return
|
||||
}
|
||||
|
||||
// parsePath parses file path in the specified outputLine, and returns new line with front-end friendly.
|
||||
func parsePath(curDir, outputLine string) string {
|
||||
index := strings.Index(outputLine, " ")
|
||||
if -1 == index || index >= len(outputLine) {
|
||||
return outputLine
|
||||
}
|
||||
filePath := args["executable"].(string)
|
||||
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
|
||||
|
||||
pathPart := outputLine[:index]
|
||||
msgPart := outputLine[index:]
|
||||
cmd := exec.Command(filePath)
|
||||
cmd.Dir = curDir
|
||||
|
||||
parts := strings.Split(pathPart, ":")
|
||||
if len(parts) < 2 { // no file path info (line & column) found
|
||||
return outputLine
|
||||
}
|
||||
|
||||
file := parts[0]
|
||||
line := parts[1]
|
||||
if _, err := strconv.Atoi(line); nil != err {
|
||||
return outputLine
|
||||
}
|
||||
|
||||
column := "0"
|
||||
hasColumn := 4 == len(parts)
|
||||
if hasColumn {
|
||||
column = parts[2]
|
||||
}
|
||||
|
||||
tagStart := `<span class="path" data-path="` + filepath.ToSlash(filepath.Join(curDir, file)) + `" data-line="` + line +
|
||||
`" data-column="` + column + `">`
|
||||
text := file + ":" + line
|
||||
if hasColumn {
|
||||
text += ":" + column
|
||||
}
|
||||
tagEnd := "</span>:"
|
||||
|
||||
return tagStart + text + tagEnd + msgPart
|
||||
}
|
||||
|
||||
func setCmdEnv(cmd *exec.Cmd, uid string) {
|
||||
userWorkspace := conf.GetUserWorkspace(uid)
|
||||
cache, err := os.UserCacheDir()
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if nil != err {
|
||||
logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
|
||||
cache = os.TempDir()
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到用户进程集中
|
||||
processes.add(wSession, cmd.Process)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["pid"] = cmd.Process.Pid
|
||||
|
||||
go func(runningId int) {
|
||||
defer util.Recover()
|
||||
defer cmd.Wait()
|
||||
|
||||
glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath)
|
||||
|
||||
// 在读取程序输出前先返回一次,使前端获取到 run 状态与 pid
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
channelRet["cmd"] = "run"
|
||||
channelRet["output"] = ""
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
for {
|
||||
buf, err := reader.ReadBytes('\n')
|
||||
|
||||
if nil != err || 0 == len(buf) {
|
||||
// 从用户进程集中移除这个执行完毕(或是被主动停止)的进程
|
||||
processes.remove(wSession, cmd.Process)
|
||||
|
||||
glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath)
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
channelRet["cmd"] = "run-done"
|
||||
channelRet["output"] = "<pre>" + string(buf) + "</pre>"
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
break
|
||||
} else {
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
channelRet["cmd"] = "run"
|
||||
channelRet["output"] = "<pre>" + string(buf) + "</pre>"
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rand.Int())
|
||||
}
|
||||
|
||||
// 构建可执行文件.
|
||||
func BuildHandler(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")
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
|
||||
filePath := args["file"].(string)
|
||||
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
fout.WriteString(code)
|
||||
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
suffix := ""
|
||||
if "windows" == runtime.GOOS {
|
||||
suffix = ".exe"
|
||||
}
|
||||
executable := "main" + suffix
|
||||
argv := []string{"build", "-o", executable}
|
||||
|
||||
cmd := exec.Command("go", argv...)
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, username)
|
||||
|
||||
glog.V(5).Infof("go build -o %s", executable)
|
||||
|
||||
executable = curDir + conf.PathSeparator + executable
|
||||
|
||||
// 先把可执行文件删了
|
||||
err = os.RemoveAll(executable)
|
||||
if nil != err {
|
||||
glog.Info(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !data["succ"].(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
// 在前端 output 中显示“开始构建”
|
||||
|
||||
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-build"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(runningId int) {
|
||||
defer util.Recover()
|
||||
defer cmd.Wait()
|
||||
|
||||
glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir)
|
||||
|
||||
// 一次性读取
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "build"
|
||||
channelRet["executable"] = executable
|
||||
|
||||
if 0 == len(buf) { // 说明构建成功,没有错误信息输出
|
||||
// 设置下一次执行命令(前端会根据该参数发送请求)
|
||||
channelRet["nextCmd"] = args["nextCmd"]
|
||||
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
|
||||
|
||||
go func() { // 运行 go install,生成的库用于 gocode lib-path
|
||||
cmd := exec.Command("go", "install")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, username)
|
||||
|
||||
out, _ := cmd.CombinedOutput()
|
||||
if len(out) > 0 {
|
||||
glog.Warning(string(out))
|
||||
}
|
||||
}()
|
||||
} else { // 构建失败
|
||||
// 解析错误信息,返回给编辑器 gutter lint
|
||||
errOut := string(buf)
|
||||
channelRet["output"] = "<span class='build-failed'>" + i18n.Get(locale, "build-failed").(string) + "</span>\n" + errOut
|
||||
|
||||
lines := strings.Split(errOut, "\n")
|
||||
|
||||
if lines[0][0] == '#' {
|
||||
lines = lines[1:] // 跳过第一行
|
||||
}
|
||||
|
||||
lints := []*Lint{}
|
||||
|
||||
for _, line := range lines {
|
||||
if len(line) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '\t' {
|
||||
// 添加到上一个 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:]
|
||||
lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")])
|
||||
msg := left[strings.Index(left, ":")+2:]
|
||||
|
||||
lint := &Lint{
|
||||
File: file,
|
||||
LineNo: lineNo - 1,
|
||||
Severity: lintSeverityError,
|
||||
Msg: msg,
|
||||
}
|
||||
|
||||
lints = append(lints, lint)
|
||||
}
|
||||
|
||||
channelRet["lints"] = lints
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
glog.V(3).Infof("Session [%s] 's build [id=%d, dir=%s] has done", sid, runningId, curDir)
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
}(rand.Int())
|
||||
}
|
||||
|
||||
// go install.
|
||||
func GoInstallHandler(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")
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
|
||||
filePath := args["file"].(string)
|
||||
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
|
||||
|
||||
fout, err := os.Create(filePath)
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
fout.WriteString(code)
|
||||
|
||||
if err := fout.Close(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "install")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, username)
|
||||
|
||||
glog.V(5).Infof("go install %s", curDir)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !data["succ"].(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
// 在前端 output 中显示“开始 go install”
|
||||
|
||||
channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-install"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(runningId int) {
|
||||
defer util.Recover()
|
||||
defer cmd.Wait()
|
||||
|
||||
glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir)
|
||||
|
||||
// 一次性读取
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "go install"
|
||||
|
||||
if 0 != len(buf) { // 构建失败
|
||||
// 解析错误信息,返回给编辑器 gutter lint
|
||||
errOut := string(buf)
|
||||
lines := strings.Split(errOut, "\n")
|
||||
|
||||
if lines[0][0] == '#' {
|
||||
lines = lines[1:] // 跳过第一行
|
||||
}
|
||||
|
||||
lints := []*Lint{}
|
||||
|
||||
for _, line := range lines {
|
||||
if len(line) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '\t' {
|
||||
// 添加到上一个 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:]
|
||||
lineNo, _ := strconv.Atoi(left[:strings.Index(left, ":")])
|
||||
msg := left[strings.Index(left, ":")+2:]
|
||||
|
||||
lint := &Lint{
|
||||
File: file,
|
||||
LineNo: lineNo - 1,
|
||||
Severity: lintSeverityError,
|
||||
Msg: msg,
|
||||
}
|
||||
|
||||
lints = append(lints, lint)
|
||||
}
|
||||
|
||||
channelRet["lints"] = lints
|
||||
|
||||
channelRet["output"] = "<span class='install-failed'>" + i18n.Get(locale, "install-failed").(string) + "</span>\n" + errOut
|
||||
} else {
|
||||
channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n"
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
glog.V(3).Infof("Session [%s] 's running [go install] [id=%d, dir=%s] has done", sid, runningId, curDir)
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
}(rand.Int())
|
||||
}
|
||||
|
||||
// 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")
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
|
||||
filePath := args["file"].(string)
|
||||
curDir := filePath[:strings.LastIndex(filePath, conf.PathSeparator)]
|
||||
|
||||
cmd := exec.Command("go", "get")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, username)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !data["succ"].(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
// 在前端 output 中显示“开始 go get
|
||||
|
||||
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-get"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(runningId int) {
|
||||
defer util.Recover()
|
||||
defer cmd.Wait()
|
||||
|
||||
glog.V(3).Infof("Session [%s] is running [go get] [runningId=%d]", sid, runningId)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "go get"
|
||||
|
||||
// 一次性读取
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
if 0 != len(buf) {
|
||||
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done (with error)", sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='get-failed'>" + i18n.Get(locale, "get-failed").(string) + "</span>\n" + string(buf)
|
||||
} else {
|
||||
glog.V(3).Infof("Session [%s] 's running [go get] [runningId=%d] has done", sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
|
||||
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.Conn.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChannel.Time = time.Now()
|
||||
}
|
||||
}(rand.Int())
|
||||
}
|
||||
|
||||
// 结束正在运行的进程.
|
||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func setCmdEnv(cmd *exec.Cmd, username string) {
|
||||
userWorkspace := conf.Wide.GetUserWorkspace(username)
|
||||
masterWorkspace := conf.Wide.GetWorkspace()
|
||||
|
||||
cmd.Env = append(cmd.Env,
|
||||
"GOPROXY=https://goproxy.cn",
|
||||
"GO111MODULE=on",
|
||||
"GOPATH="+userWorkspace,
|
||||
"GOPATH="+userWorkspace+conf.PathListSeparator+masterWorkspace,
|
||||
"GOOS="+runtime.GOOS,
|
||||
"GOARCH="+runtime.GOARCH,
|
||||
"GOROOT="+runtime.GOROOT(),
|
||||
"GOCACHE="+cache,
|
||||
"PATH="+os.Getenv("PATH"))
|
||||
|
||||
if gulu.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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 进程集类型.
|
||||
type procs map[string][]*os.Process
|
||||
|
||||
// 所有用户正在运行的程序进程集.
|
||||
//
|
||||
// <sid, []*os.Process>
|
||||
var processes = procs{}
|
||||
|
||||
// 排它锁,防止并发修改.
|
||||
var mutex sync.Mutex
|
||||
|
||||
// 添加用户执行进程.
|
||||
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
|
||||
|
||||
// 会话关联进程
|
||||
wSession.SetProcesses(userProcesses)
|
||||
|
||||
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
||||
}
|
||||
|
||||
// 移除用户执行进程.
|
||||
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:]...)
|
||||
(*procs)[sid] = newProcesses
|
||||
|
||||
// 会话关联进程
|
||||
wSession.SetProcesses(newProcesses)
|
||||
|
||||
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束用户正在执行的进程.
|
||||
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 {
|
||||
glog.Error("Kill a process [pid=%d] of session [%s] failed [error=%v]", pid, sid, err)
|
||||
} else {
|
||||
var newProcesses []*os.Process
|
||||
|
||||
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
|
||||
(*procs)[sid] = newProcesses
|
||||
|
||||
// 会话关联进程
|
||||
wSession.SetProcesses(newProcesses)
|
||||
|
||||
glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +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 (
|
||||
"github.com/88250/wide/session"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RunHandler handles request of executing a binary file.
|
||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session.RunHandler(w, r, session.OutputWS)
|
||||
}
|
||||
|
||||
// StopHandler handles request of stopping a running process.
|
||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session.StopHandler(w, r)
|
||||
}
|
149
output/test.go
|
@ -1,149 +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"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/conf"
|
||||
"github.com/88250/wide/i18n"
|
||||
"github.com/88250/wide/session"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
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["file"].(string)
|
||||
curDir := filepath.Dir(filePath)
|
||||
|
||||
cmd := exec.Command("go", "test", "-v")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, uid)
|
||||
|
||||
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 test]" in front-end browser
|
||||
|
||||
channelRet["output"] = "<span class='start-test'>" + i18n.Get(locale, "start-test").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-test"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
return
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(runningId int) {
|
||||
defer gulu.Panic.Recover(nil)
|
||||
|
||||
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "go test"
|
||||
|
||||
// read all
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
// waiting for go test finished
|
||||
cmd.Wait()
|
||||
|
||||
if !cmd.ProcessState.Success() {
|
||||
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", uid, sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
|
||||
} else {
|
||||
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", uid, sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}(rand.Int())
|
||||
}
|
149
output/vet.go
|
@ -1,149 +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"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/conf"
|
||||
"github.com/88250/wide/i18n"
|
||||
"github.com/88250/wide/session"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
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["file"].(string)
|
||||
curDir := filepath.Dir(filePath)
|
||||
|
||||
cmd := exec.Command("go", "vet", ".")
|
||||
cmd.Dir = curDir
|
||||
|
||||
setCmdEnv(cmd, uid)
|
||||
|
||||
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 vet]" in front-end browser
|
||||
|
||||
channelRet["output"] = "<span class='start-vet'>" + i18n.Get(locale, "start-vet").(string) + "</span>\n"
|
||||
channelRet["cmd"] = "start-vet"
|
||||
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
return
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
|
||||
|
||||
if err := cmd.Start(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(runningId int) {
|
||||
defer gulu.Panic.Recover(nil)
|
||||
|
||||
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
|
||||
|
||||
channelRet := map[string]interface{}{}
|
||||
channelRet["cmd"] = "go vet"
|
||||
|
||||
// read all
|
||||
buf, _ := ioutil.ReadAll(reader)
|
||||
|
||||
// waiting for go vet finished
|
||||
cmd.Wait()
|
||||
|
||||
if !cmd.ProcessState.Success() {
|
||||
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", uid, sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
|
||||
} else {
|
||||
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", uid, sid, runningId)
|
||||
|
||||
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
|
||||
}
|
||||
|
||||
if nil != session.OutputWS[sid] {
|
||||
wsChannel := session.OutputWS[sid]
|
||||
|
||||
err := wsChannel.WriteJSON(&channelRet)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
}
|
||||
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}(rand.Int())
|
||||
}
|
36
package.json
|
@ -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"
|
||||
}
|
||||
}
|
80
pkg.sh
|
@ -1,80 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Wide package tool.
|
||||
#
|
||||
# Command:
|
||||
# ./pkg.sh ${version} ${target}
|
||||
# Example:
|
||||
# ./pkg.sh 1.0.0 /home/daniel/1.0.0/
|
||||
|
||||
ver=$1
|
||||
target=$2
|
||||
list="conf doc i18n static views README.md TERMS.md LICENSE"
|
||||
|
||||
mkdir -p ${target}
|
||||
|
||||
echo version=${ver}
|
||||
echo target=${target}
|
||||
|
||||
## darwin
|
||||
os=darwin
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=amd64
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||
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
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=386
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||
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
|
||||
|
||||
## linux
|
||||
os=linux
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=amd64
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||
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
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=386
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.tar.gz
|
||||
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
|
||||
|
||||
## windows
|
||||
os=windows
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=amd64
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.zip
|
||||
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
|
||||
|
||||
export GOOS=${os}
|
||||
export GOARCH=386
|
||||
echo wide-${ver}-${GOOS}-${GOARCH}.zip
|
||||
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
|
|
@ -1,111 +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 playground
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
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")
|
||||
cmd := exec.Command(gocode, argv...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(output)
|
||||
}
|
||||
|
||||
// getCursorOffset calculates the cursor offset.
|
||||
//
|
||||
// line is the line number, starts with 0 that means the first line
|
||||
// ch is the column number, starts with 0 that means the first column
|
||||
func getCursorOffset(code string, line, ch int) (offset int) {
|
||||
lines := strings.Split(code, "\n")
|
||||
|
||||
// calculate sum length of lines before
|
||||
for i := 0; i < line; i++ {
|
||||
offset += len(lines[i])
|
||||
}
|
||||
|
||||
// calculate length of the current line and column
|
||||
curLine := lines[line]
|
||||
var buffer bytes.Buffer
|
||||
r := []rune(curLine)
|
||||
for i := 0; i < ch; i++ {
|
||||
buffer.WriteString(string(r[i]))
|
||||
}
|
||||
|
||||
offset += len(buffer.String()) // append length of current line
|
||||
offset += line // append number of '\n'
|
||||
|
||||
return offset
|
||||
}
|
|
@ -1,81 +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 playground
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/conf"
|
||||
"github.com/88250/wide/session"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fileName := args["fileName"].(string)
|
||||
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
|
||||
|
||||
suffix := ""
|
||||
if gulu.OS.IsWindows() {
|
||||
suffix = ".exe"
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
result.Data = &data
|
||||
|
||||
executable := filepath.Clean(conf.Wide.Data + "/playground/" + strings.Replace(fileName, ".go", suffix, -1))
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", executable, filePath)
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
||||
data["output"] = template.HTML(string(out))
|
||||
|
||||
if nil != err {
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
data["executable"] = executable
|
||||
}
|
|
@ -1,102 +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 playground
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/wide/conf"
|
||||
"github.com/88250/wide/session"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
if conf.Wide.ReadOnly {
|
||||
result.Code = -1
|
||||
result.Msg = "readonly mode"
|
||||
return
|
||||
}
|
||||
|
||||
session, _ := session.HTTPSession.Get(r, session.CookieName)
|
||||
if session.IsNew {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
code := args["code"].(string)
|
||||
|
||||
// Step1. format code
|
||||
cmd := exec.Command("gofmt")
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
io.WriteString(stdin, code)
|
||||
stdin.Close()
|
||||
|
||||
bytes, _ := cmd.Output()
|
||||
output := string(bytes)
|
||||
if "" != output {
|
||||
code = string(output)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
result.Data = &data
|
||||
|
||||
data["code"] = code
|
||||
|
||||
// Step2. generate file name
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(code))
|
||||
fileName := hex.EncodeToString(hasher.Sum(nil))
|
||||
fileName += ".go"
|
||||
data["fileName"] = fileName
|
||||
|
||||
// Step3. write file
|
||||
filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
|
||||
fout, err := os.Create(filePath)
|
||||
fout.WriteString(code)
|
||||
if err := fout.Close(); nil != err {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
}
|
|
@ -1,123 +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 shell include playground related mainipulations.
|
||||
package playground
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"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/session"
|
||||
"github.com/88250/wide/util"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.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)
|
||||
if httpSession.IsNew {
|
||||
httpSession.Values["id"] = strconv.Itoa(rand.Int())
|
||||
httpSession.Values["uid"] = "playground"
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
|
||||
locale := conf.Wide.Locale
|
||||
|
||||
// try to load file
|
||||
code := conf.HelloWorld
|
||||
fileName := "6c5595ec6fbadf4cfce3edbfcfd8c6d0.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)
|
||||
|
||||
bytes, err := ioutil.ReadFile(filePath)
|
||||
if nil != err {
|
||||
logger.Warn(err)
|
||||
} else {
|
||||
code = string(bytes)
|
||||
fileName = fileNameArg
|
||||
}
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
embed := false
|
||||
embedArg, ok := query["embed"]
|
||||
if ok && "true" == embedArg[0] {
|
||||
embed = true
|
||||
}
|
||||
|
||||
disqus := false
|
||||
disqusArg, ok := query["disqus"]
|
||||
if ok && "true" == disqusArg[0] {
|
||||
disqus = true
|
||||
}
|
||||
|
||||
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(),
|
||||
"embed": embed, "disqus": disqus, "fileName": fileName}
|
||||
|
||||
wideSessions := session.WideSessions.GetByUserId(uid)
|
||||
|
||||
logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
|
||||
|
||||
t, err := template.ParseFiles("views/playground/index.html")
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// WSHandler handles request of creating Playground channel.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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": "Playground initialized", "cmd": "init-playground"}
|
||||
err := wsChan.WriteJSON(&ret)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
session.PlaygroundWS[sid] = &wsChan
|
||||
|
||||
logger.Tracef("Open a new [PlaygroundWS] with session [%s], %d", sid, len(session.PlaygroundWS))
|
||||
}
|
|
@ -1,30 +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 playground
|
||||
|
||||
import (
|
||||
"github.com/88250/wide/session"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RunHandler handles request of executing a binary file.
|
||||
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session.RunHandler(w, r, session.PlaygroundWS)
|
||||
}
|
||||
|
||||
// StopHandler handles request of stopping a running process.
|
||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session.StopHandler(w, r)
|
||||
}
|
185
session/oauth.go
|
@ -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()
|
||||
}
|
|
@ -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, "<", "<", -1)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||
channelRet["cmd"] = "run"
|
||||
channelRet["output"] = oneRuneStr
|
||||
wsChannel := channel[sid]
|
||||
if nil != wsChannel {
|
||||
wsChannel.WriteJSON(&channelRet)
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
if shouldExitBuf {
|
||||
break
|
||||
}
|
||||
|
||||
if 1 > errBuf.Len() {
|
||||
time.Sleep(7 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
r, _, err := errBuf.ReadRune()
|
||||
if nil != err {
|
||||
time.Sleep(7 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
oneRuneStr := string(r)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
||||
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||
channelRet["cmd"] = "run"
|
||||
channelRet["output"] = "<span class='stderr'>" + oneRuneStr + "</span>"
|
||||
wsChannel := channel[sid]
|
||||
if nil != wsChannel {
|
||||
wsChannel.WriteJSON(&channelRet)
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
after := time.After(5 * time.Second)
|
||||
kill := false
|
||||
select {
|
||||
case <-after:
|
||||
if conf.Docker {
|
||||
killCmd := exec.Command("docker", "rm", "-f", rid)
|
||||
if err := killCmd.Run(); nil != err {
|
||||
logger.Errorf("executes [docker rm -f " + rid + "] failed [" + err.Error() + "], this will cause resource leaking")
|
||||
}
|
||||
} else {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
channelRet["output"] = "\n<span class='stderr'>run program timeout in 5s</span>\n"
|
||||
kill = true
|
||||
case <-done:
|
||||
channelRet["output"] = "\n<span class='stderr'>run program complete</span>\n"
|
||||
}
|
||||
|
||||
shouldExitBuf = true
|
||||
Processes.Remove(wSession, cmd.Process)
|
||||
logger.Debugf("User [%s, %s] done running [id=%s, file=%s, kill=%v]", wSession.UserId, sid, rid, filePath, kill)
|
||||
|
||||
if nil != wsChannel {
|
||||
channelRet["cmd"] = "run-done"
|
||||
wsChannel.WriteJSON(&channelRet)
|
||||
wsChannel.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// StopHandler handles request of stopping a running process.
|
||||
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
result := gulu.Ret.NewResult()
|
||||
defer gulu.Ret.RetResult(w, r, result)
|
||||
|
||||
var args map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sid := args["sid"].(string)
|
||||
pid := int(args["pid"].(float64))
|
||||
|
||||
wSession := WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Processes.Kill(wSession, pid)
|
||||
}
|
||||
|
||||
// Add adds the specified process to the user process set.
|
||||
func (procs *procs) Add(wSession *WideSession, proc *os.Process) {
|
||||
procMutex.Lock()
|
||||
defer procMutex.Unlock()
|
||||
|
||||
sid := wSession.ID
|
||||
userProcesses := (*procs)[sid]
|
||||
|
||||
userProcesses = append(userProcesses, proc)
|
||||
(*procs)[sid] = userProcesses
|
||||
|
||||
// bind process with wide session
|
||||
wSession.SetProcesses(userProcesses)
|
||||
|
||||
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
||||
}
|
||||
|
||||
// Remove removes the specified process from the user process set.
|
||||
func (procs *procs) Remove(wSession *WideSession, proc *os.Process) {
|
||||
procMutex.Lock()
|
||||
defer procMutex.Unlock()
|
||||
|
||||
sid := wSession.ID
|
||||
|
||||
userProcesses := (*procs)[sid]
|
||||
|
||||
var newProcesses []*os.Process
|
||||
for i, p := range userProcesses {
|
||||
if p.Pid == proc.Pid {
|
||||
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
|
||||
(*procs)[sid] = newProcesses
|
||||
|
||||
// bind process with wide session
|
||||
wSession.SetProcesses(newProcesses)
|
||||
|
||||
logger.Tracef("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kill kills a process specified by the given pid.
|
||||
func (procs *procs) Kill(wSession *WideSession, pid int) {
|
||||
procMutex.Lock()
|
||||
defer procMutex.Unlock()
|
||||
|
||||
sid := wSession.ID
|
||||
|
||||
userProcesses := (*procs)[sid]
|
||||
|
||||
for i, p := range userProcesses {
|
||||
if p.Pid == pid {
|
||||
if err := p.Kill(); nil != err {
|
||||
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.UserId, sid, err)
|
||||
} else {
|
||||
var newProcesses []*os.Process
|
||||
|
||||
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
|
||||
(*procs)[sid] = newProcesses
|
||||
|
||||
// bind process with wide session
|
||||
wSession.SetProcesses(newProcesses)
|
||||
|
||||
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.UserId, sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,280 +1,127 @@
|
|||
// 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
|
||||
// Wide 服务器端需要维护两种会话:
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
// 1. HTTP 会话:主要用于验证登录
|
||||
// 2. Wide 会话:浏览器 tab 打开/刷新会创建一个,并和 HTTP 会话进行关联
|
||||
//
|
||||
// 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 includes session related manipulations.
|
||||
//
|
||||
// Wide server side needs maintain two kinds of sessions:
|
||||
//
|
||||
// 1. HTTP session: mainly used for login authentication
|
||||
// 2. Wide session: browser tab open/refresh will create one, and associates with HTTP session
|
||||
//
|
||||
// When a session gone: release all resources associated with it, such as running processes, event queues.
|
||||
// 当会话失效时:释放所有和该会话相关的资源,例如运行中的程序进程、事件队列等.
|
||||
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/util"
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionStateActive = iota
|
||||
sessionStateClosed // (not used so far)
|
||||
|
||||
CookieName = "wide-sess"
|
||||
SessionStateActive = iota // 会话状态:活的
|
||||
SessionStateClosed // 会话状态:已关闭(这个状态目前暂时没有使用到)
|
||||
)
|
||||
|
||||
// Logger.
|
||||
var logger = gulu.Log.NewLogger(os.Stdout)
|
||||
|
||||
var (
|
||||
// SessionWS holds all session channels. <sid, *util.WSChannel>
|
||||
// 会话通道. <sid, *util.WSChannel>
|
||||
SessionWS = map[string]*util.WSChannel{}
|
||||
|
||||
// EditorWS holds all editor channels. <sid, *util.WSChannel>
|
||||
EditorWS = map[string]*util.WSChannel{}
|
||||
|
||||
// OutputWS holds all output channels. <sid, *util.WSChannel>
|
||||
// 输出通道. <sid, *util.WSChannel>
|
||||
OutputWS = map[string]*util.WSChannel{}
|
||||
|
||||
// NotificationWS holds all notification channels. <sid, *util.WSChannel>
|
||||
// 通知通道. <sid, *util.WSChannel>
|
||||
NotificationWS = map[string]*util.WSChannel{}
|
||||
|
||||
// PlaygroundWS holds all playground channels. <sid, *util.WSChannel>
|
||||
PlaygroundWS = map[string]*util.WSChannel{}
|
||||
)
|
||||
|
||||
// HTTP session store.
|
||||
// 用户 HTTP 会话,用于验证登录.
|
||||
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
|
||||
|
||||
// WideSession represents a session associated with a browser tab.
|
||||
// Wide 会话,对应一个浏览器 tab.
|
||||
type WideSession struct {
|
||||
ID string // id
|
||||
UserId string // user id
|
||||
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
|
||||
Id string // 唯一标识
|
||||
Username string // 用户名
|
||||
HTTPSession *sessions.Session // 关联的 HTTP 会话
|
||||
Processes []*os.Process // 关联的进程集
|
||||
EventQueue *event.UserEventQueue // 关联的事件队列
|
||||
State int // 状态
|
||||
Content *conf.LatestSessionContent // 最近一次会话内容
|
||||
Created time.Time // 创建时间
|
||||
Updated time.Time // 最近一次使用时间
|
||||
}
|
||||
|
||||
// Type of wide sessions.
|
||||
type wSessions []*WideSession
|
||||
// 会话集类型.
|
||||
type Sessions []*WideSession
|
||||
|
||||
// Wide sessions.
|
||||
var WideSessions wSessions
|
||||
// 所有 Wide 会话集.
|
||||
var WideSessions Sessions
|
||||
|
||||
// Exclusive lock.
|
||||
// 排它锁,防止并发修改.
|
||||
var mutex sync.Mutex
|
||||
|
||||
// FixedTimeRelease releases invalid sessions.
|
||||
// 在一些特殊情况(例如浏览器不间断刷新/在源代码视图刷新)下 Wide 会话集内会出现无效会话,该函数定时(1 小时)检查并移除这些无效会话.
|
||||
//
|
||||
// In some special cases (such as a browser uninterrupted refresh / refresh in the source code view) will occur
|
||||
// some invalid sessions, the function checks and removes these invalid sessions periodically (1 hour).
|
||||
//
|
||||
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
|
||||
// 无效会话:在检查时间内 30 分钟都没有使用过的会话,参考 WideSession.Updated 字段.
|
||||
func FixedTimeRelease() {
|
||||
go func() {
|
||||
defer gulu.Panic.Recover(nil)
|
||||
|
||||
// 1 小时进行一次检查
|
||||
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)
|
||||
glog.V(3).Infof("Removes a invalid session [%s]", s.Id)
|
||||
|
||||
WideSessions.Remove(s.ID)
|
||||
WideSessions.Remove(s.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Online user statistic report.
|
||||
type userReport struct {
|
||||
userId string
|
||||
sessionCnt int
|
||||
processCnt int
|
||||
updated time.Time
|
||||
}
|
||||
|
||||
// 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) +
|
||||
"] 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
|
||||
|
||||
for _, s := range WideSessions {
|
||||
processCnt := len(s.Processes)
|
||||
processSum += processCnt
|
||||
|
||||
if report, exists := contains(users, s.UserId); exists {
|
||||
if s.Updated.After(report.updated) {
|
||||
report.updated = s.Updated
|
||||
}
|
||||
|
||||
report.sessionCnt++
|
||||
report.processCnt += processCnt
|
||||
} else {
|
||||
users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users, [" + strconv.Itoa(processSum) + "] running processes and [" +
|
||||
strconv.Itoa(len(WideSessions)) + "] sessions currently\n")
|
||||
|
||||
sort.Sort(users)
|
||||
|
||||
for _, t := range users {
|
||||
buf.WriteString(" " + t.report() + "\n")
|
||||
}
|
||||
|
||||
logger.Info(buf.String())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func contains(reports []*userReport, userId string) (*userReport, bool) {
|
||||
for _, ur := range reports {
|
||||
if userId == ur.userId {
|
||||
return ur, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type userReports []*userReport
|
||||
|
||||
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 {
|
||||
glog.Errorf("Session [%s] not found", sid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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": "Session initialized", "cmd": "init-session"}
|
||||
err := wsChan.WriteJSON(&ret)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
SessionWS[sid] = &wsChan
|
||||
|
||||
wSession := WideSessions.Get(sid)
|
||||
if nil == wSession {
|
||||
httpSession, _ := HTTPSession.Get(r, CookieName)
|
||||
ret := map[string]interface{}{"output": "Ouput initialized", "cmd": "init-session"}
|
||||
wsChan.Conn.WriteJSON(&ret)
|
||||
|
||||
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))
|
||||
glog.V(4).Infof("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)
|
||||
for {
|
||||
if err := wsChan.Conn.ReadJSON(&input); err != nil {
|
||||
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid)
|
||||
|
||||
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)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ret = map[string]interface{}{"output": "", "cmd": "session-output"}
|
||||
|
||||
if err := wsChan.WriteJSON(&ret); err != nil {
|
||||
logger.Error("Session WS ERROR: " + err.Error())
|
||||
|
||||
if err := wsChan.Conn.WriteJSON(&ret); err != nil {
|
||||
glog.Error("Session WS ERROR: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -282,10 +129,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)
|
||||
// 会话内容保存.
|
||||
func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
args := struct {
|
||||
Sid string
|
||||
|
@ -293,28 +140,26 @@ func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
wSession := WideSessions.Get(args.Sid)
|
||||
if nil == wSession {
|
||||
result.Code = -1
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
wSession.Content = args.LatestSessionContent
|
||||
|
||||
for _, user := range conf.Users {
|
||||
if user.Id == wSession.UserId {
|
||||
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically
|
||||
for _, user := range conf.Wide.Users {
|
||||
if user.Name == wSession.Username {
|
||||
// 更新配置(内存变量),conf.FixedTimeSave() 会负责定时持久化
|
||||
user.LatestSessionContent = wSession.Content
|
||||
|
||||
user.Lived = time.Now().UnixNano()
|
||||
|
||||
wSession.Refresh()
|
||||
|
||||
return
|
||||
|
@ -322,32 +167,54 @@ func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetProcesses binds process set with the wide session.
|
||||
// 设置会话关联的进程集.
|
||||
func (s *WideSession) SetProcesses(ps []*os.Process) {
|
||||
s.Processes = ps
|
||||
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
// Refresh refreshes the channel by updating its use time.
|
||||
// 刷新会话最近一次使用时间.
|
||||
func (s *WideSession) Refresh() {
|
||||
s.Updated = time.Now()
|
||||
}
|
||||
|
||||
// GenId generates a wide session id.
|
||||
func (sessions *wSessions) GenId() string {
|
||||
// 创建一个 Wide 会话.
|
||||
func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return strconv.Itoa(rand.Int())
|
||||
id := strconv.Itoa(rand.Int())
|
||||
now := time.Now()
|
||||
|
||||
// 创建用户事件队列
|
||||
userEventQueue := event.UserEventQueues.New(id)
|
||||
|
||||
ret := &WideSession{
|
||||
Id: id,
|
||||
Username: httpSession.Values["username"].(string),
|
||||
HTTPSession: httpSession,
|
||||
EventQueue: userEventQueue,
|
||||
State: SessionStateActive,
|
||||
Content: &conf.LatestSessionContent{},
|
||||
Created: now,
|
||||
Updated: now,
|
||||
}
|
||||
|
||||
// Get gets a wide session with the specified session id.
|
||||
func (sessions *wSessions) Get(sid string) *WideSession {
|
||||
*sessions = append(*sessions, ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// 获取 Wide 会话.
|
||||
func (sessions *Sessions) Get(sid string) *WideSession {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for _, s := range *sessions {
|
||||
if s.ID == sid {
|
||||
if s.Id == sid {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
@ -355,36 +222,35 @@ func (sessions *wSessions) Get(sid string) *WideSession {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a wide session specified with the given session id, releases resources associated with it.
|
||||
// 移除 Wide 会话,释放相关资源.
|
||||
//
|
||||
// Session-related resources:
|
||||
// 会话相关资源:
|
||||
//
|
||||
// 1. user event queue
|
||||
// 2. process set
|
||||
// 3. websocket channels
|
||||
// 4. file watcher
|
||||
func (sessions *wSessions) Remove(sid string) {
|
||||
// 1. 用户事件队列
|
||||
// 2. 运行中的进程集
|
||||
// 3. WebSocket 通道
|
||||
func (sessions *Sessions) Remove(sid string) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for i, s := range *sessions {
|
||||
if s.ID == sid {
|
||||
// remove from session set
|
||||
if s.Id == sid {
|
||||
// 从会话集中移除
|
||||
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
|
||||
|
||||
// close user event queue
|
||||
// 关闭用户事件队列
|
||||
event.UserEventQueues.Close(sid)
|
||||
|
||||
// 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)
|
||||
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid)
|
||||
} else {
|
||||
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
|
||||
glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid)
|
||||
}
|
||||
}
|
||||
|
||||
// close websocket channels
|
||||
// 回收所有通道
|
||||
if ws, ok := OutputWS[sid]; ok {
|
||||
ws.Close()
|
||||
delete(OutputWS, sid)
|
||||
|
@ -400,155 +266,33 @@ func (sessions *wSessions) Remove(sid string) {
|
|||
delete(SessionWS, sid)
|
||||
}
|
||||
|
||||
if ws, ok := PlaygroundWS[sid]; ok {
|
||||
ws.Close()
|
||||
delete(PlaygroundWS, sid)
|
||||
}
|
||||
glog.V(3).Infof("Removed a session [%s]", s.Id)
|
||||
|
||||
// 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 {
|
||||
cnt := 0 // 统计当前 HTTP 会话关联的 Wide 会话数量
|
||||
for _, s := range *sessions {
|
||||
if s.HTTPSession.ID == s.HTTPSession.ID {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
|
||||
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetByUsername gets wide sessions.
|
||||
func (sessions *wSessions) GetByUserId(userId string) []*WideSession {
|
||||
// 获取 username 指定的用户的所有 Wide 会话.
|
||||
func (sessions *Sessions) 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
|
||||
}
|
||||
|
|
239
session/users.go
|
@ -1,220 +1,83 @@
|
|||
// 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
|
||||
|
||||
// TODO: 这个文件内的功能目前没有使用,只是开了个头 :p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"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/util"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: i18n
|
||||
|
||||
userExists = "user exists"
|
||||
userCreated = "user created"
|
||||
userCreateError = "user create error"
|
||||
USER_EXISTS = "user exists"
|
||||
USER_CREATED = "user created"
|
||||
USER_CREATE_FAILED = "user create failed"
|
||||
)
|
||||
|
||||
// Exclusive lock for adding user.
|
||||
var addUserMutex sync.Mutex
|
||||
// 添加用户.
|
||||
func AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
// PreferenceHandler handles request of preference page.
|
||||
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
httpSession, _ := HTTPSession.Get(r, CookieName)
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
var args map[string]interface{}
|
||||
|
||||
if err := decoder.Decode(&args); err != nil {
|
||||
glog.Error(err)
|
||||
data["succ"] = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
username := args["username"].(string)
|
||||
password := args["password"].(string)
|
||||
|
||||
uid := httpSession.Values["uid"].(string)
|
||||
user := conf.GetUser(uid)
|
||||
|
||||
if "GET" == r.Method {
|
||||
tmpLinux := user.GoBuildArgsForLinux
|
||||
tmpWindows := user.GoBuildArgsForWindows
|
||||
tmpDarwin := user.GoBuildArgsForDarwin
|
||||
|
||||
user.GoBuildArgsForLinux = strings.Replace(user.GoBuildArgsForLinux, `"`, `"`, -1)
|
||||
user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `"`, -1)
|
||||
user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `"`, -1)
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
|
||||
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
|
||||
"locales": i18n.GetLocalesNames(), "gofmts": gulu.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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||
logger.Error(err)
|
||||
result.Code = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
user.Locale = args.Locale
|
||||
user.Theme = args.Theme
|
||||
user.Editor.FontFamily = args.EditorFontFamily
|
||||
user.Editor.FontSize = args.EditorFontSize
|
||||
user.Editor.LineHeight = args.EditorLineHeight
|
||||
user.Editor.Theme = args.EditorTheme
|
||||
user.Editor.TabSize = args.EditorTabSize
|
||||
|
||||
conf.UpdateCustomizedConf(uid)
|
||||
|
||||
now := time.Now().UnixNano()
|
||||
user.Lived = now
|
||||
user.Updated = now
|
||||
|
||||
if user.Save() {
|
||||
result.Code = 0
|
||||
} else {
|
||||
result.Code = -1
|
||||
msg := addUser(username, password)
|
||||
if USER_CREATED != msg {
|
||||
data["succ"] = false
|
||||
data["msg"] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// FixedTimeSave saves online users' configurations periodically (1 minute).
|
||||
//
|
||||
// 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)
|
||||
// 初始化用户 git 仓库.
|
||||
func InitGitRepos(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{"succ": true}
|
||||
defer util.RetJSON(w, r, data)
|
||||
|
||||
for _ = range time.Tick(time.Minute) {
|
||||
SaveOnlineUsers()
|
||||
}
|
||||
}()
|
||||
session, _ := HTTPSession.Get(r, "wide-session")
|
||||
|
||||
username := session.Values["username"].(string)
|
||||
userRepos := conf.Wide.GetUserWorkspace(username) + conf.PathSeparator + "src"
|
||||
|
||||
glog.Info(userRepos)
|
||||
|
||||
// TODO: git clone
|
||||
}
|
||||
|
||||
// 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)
|
||||
func addUser(username, password string) string {
|
||||
// TODO: https://github.com/b3log/wide/issues/23
|
||||
conf.Load()
|
||||
|
||||
userWorkspace := conf.GetUserWorkspace(userId)
|
||||
workspaces := filepath.SplitList(userWorkspace)
|
||||
|
||||
for _, workspace := range workspaces {
|
||||
if strings.HasPrefix(path, workspace) {
|
||||
return true
|
||||
// XXX: 新建用户校验增强
|
||||
for _, user := range conf.Wide.Users {
|
||||
if user.Name == username {
|
||||
return USER_EXISTS
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
// FIXME: 新建用户时保存工作空间
|
||||
newUser := &conf.User{Name: username, Password: password, Workspace: ""}
|
||||
conf.Wide.Users = append(conf.Wide.Users, newUser)
|
||||
|
||||
if !conf.Save() {
|
||||
return USER_CREATE_FAILED
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
glog.Infof("Created a user [%s]", username)
|
||||
|
||||
func getOnlineUsers() []*conf.User {
|
||||
ret := []*conf.User{}
|
||||
|
||||
uids := map[string]string{} // distinct uid
|
||||
for _, s := range WideSessions {
|
||||
uids[s.UserId] = s.UserId
|
||||
}
|
||||
|
||||
for _, uid := range uids {
|
||||
u := conf.GetUser(uid)
|
||||
|
||||
if "playground" == uid { // user [playground] is a reserved mock user
|
||||
continue
|
||||
}
|
||||
|
||||
if nil == u {
|
||||
logger.Warnf("Not found user [%s]", uid)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, u)
|
||||
}
|
||||
|
||||
return ret
|
||||
return USER_CREATED
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
// Shell.
|
||||
package shell
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/b3log/wide/conf"
|
||||
"github.com/b3log/wide/i18n"
|
||||
"github.com/b3log/wide/session"
|
||||
"github.com/b3log/wide/util"
|
||||
"github.com/golang/glog"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Shell 通道.
|
||||
//
|
||||
// <sid, *util.WSChannel>>
|
||||
var ShellWS = map[string]*util.WSChannel{}
|
||||
|
||||
// Shell 首页.
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
i18n.Load()
|
||||
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
|
||||
if httpSession.IsNew {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||
httpSession.Save(r, w)
|
||||
|
||||
// 创建一个 Wide 会话
|
||||
wideSession := session.WideSessions.New(httpSession)
|
||||
|
||||
username := httpSession.Values["username"].(string)
|
||||
locale := conf.Wide.GetUser(username).Locale
|
||||
|
||||
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||
"session": wideSession}
|
||||
|
||||
wideSessions := session.WideSessions.GetByUsername(username)
|
||||
|
||||
glog.V(3).Infof("User [%s] has [%d] sessions", username, len(wideSessions))
|
||||
|
||||
t, err := template.ParseFiles("views/shell.html")
|
||||
|
||||
if nil != err {
|
||||
glog.Error(err)
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Execute(w, model)
|
||||
}
|
||||
|
||||
// 建立 Shell 通道.
|
||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||
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()}
|
||||
|
||||
ShellWS[sid] = &wsChan
|
||||
|
||||
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
|
||||
wsChan.Conn.WriteJSON(&ret)
|
||||
|
||||
glog.V(4).Infof("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
|
||||
|
||||
input := map[string]interface{}{}
|
||||
|
||||
for {
|
||||
if err := wsChan.Conn.ReadJSON(&input); err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
if err.Error() == "unexpected EOF" {
|
||||
return
|
||||
}
|
||||
|
||||
glog.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.Conn.WriteJSON(&ret); err != nil {
|
||||
glog.Error("Shell WS ERROR: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 更新通道最近使用时间
|
||||
wsChan.Time = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// 以管道方式执行多个命令.
|
||||
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()
|
||||
|
||||
// 结束进程,释放资源
|
||||
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.Wide.GetUserWorkspace(username)
|
||||
|
||||
cmd.Env = append(cmd.Env,
|
||||
"TERM="+os.Getenv("TERM"),
|
||||
"GOPATH="+userWorkspace,
|
||||
"GOOS="+runtime.GOOS,
|
||||
"GOARCH="+runtime.GOARCH,
|
||||
"GOROOT="+runtime.GOROOT(),
|
||||
"PATH="+os.Getenv("PATH"))
|
||||
|
||||
cmd.Dir = userWorkspace
|
||||
}
|
|
@ -1,32 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#dialogAboutDialog .dialog-main {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
#dialogAbout {
|
||||
margin: 35px 20px;
|
||||
margin: 35px 100px 0 100px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
#dialogAbout .item {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#dialogAbout a {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
|
@ -48,17 +28,23 @@
|
|||
|
||||
#dialogAbout .space {
|
||||
margin-bottom: 6px;
|
||||
border-bottom: 1px solid #919191;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
padding-bottom: 6px;
|
||||
|
||||
}
|
||||
|
||||
#dialogAbout .thx {
|
||||
border-left: 1px solid #f1f1f1;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
#dialogAbout .thx,
|
||||
#dialogAbout .thx ul {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
#dialogAbout .thx a {
|
||||
width: 80px;
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -67,6 +53,5 @@
|
|||
font-size: 12px;
|
||||
line-height: normal;
|
||||
height: 85px;
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow: auto;
|
||||
}
|
|
@ -1,56 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border: solid 0 rgba(0, 0, 0, 0);
|
||||
border-right-width: 4px;
|
||||
border-left-width: 4px;
|
||||
border-radius: 9px;
|
||||
box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.2), inset 0 0 0 4px rgba(128, 128, 128, 0.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;
|
||||
font-family: Helvetica, 'Microsoft Yahei';
|
||||
}
|
||||
|
||||
ul {
|
||||
|
@ -63,22 +17,9 @@ ul {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
font-family: Helvetica;
|
||||
font-family: Helvetica, 'Microsoft Yahei';
|
||||
}
|
||||
|
||||
.fn-left {
|
||||
|
@ -103,203 +44,3 @@ button {
|
|||
display: none;
|
||||
}
|
||||
/* end reset & function */
|
||||
|
||||
/* start common */
|
||||
.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;
|
||||
}
|
||||
/* end common */
|
||||
|
||||
/* 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');
|
||||
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;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-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, 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, 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";
|
||||
}
|
||||
/* end ico */
|
|
@ -1,23 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
@ -40,21 +24,23 @@
|
|||
display: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
box-shadow: 0 2px 10px 1px #000000;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
float: left;
|
||||
line-height: 22px;
|
||||
margin-left: 3px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dialog-header-bg {
|
||||
height: 23px;
|
||||
background-color: #bbb;
|
||||
background-color: #CCD5E5;
|
||||
cursor: move;
|
||||
width: 100%;
|
||||
border: 1px solid #9B9B9B;
|
||||
border-top-color: #8E97A7;
|
||||
border-bottom-color: #8891A1;
|
||||
}
|
||||
|
||||
.dialog-close-icon {
|
||||
|
@ -63,8 +49,10 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dialog-close-icon:hover {
|
||||
text-decoration: none;
|
||||
.dialog-main {
|
||||
border: 1px solid #9B9B9B;
|
||||
border-top-width: 0px;
|
||||
background-color: #F0F0F0;
|
||||
}
|
||||
|
||||
.dialog-main > div {
|
||||
|
@ -76,8 +64,7 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.dialog-footer button,
|
||||
#dialogCloseEditor button {
|
||||
.dialog-footer button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
|
@ -89,34 +76,7 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-main input,
|
||||
.dialog-main select {
|
||||
.dialog-main input {
|
||||
width: 100%;
|
||||
margin: 2px auto;
|
||||
}
|
||||
|
||||
#dialogGoFilePrompt > ul {
|
||||
position: relative;
|
||||
height: 260px;
|
||||
overflow: auto;
|
||||
margin-top: 5px;
|
||||
background-color: #FFF;
|
||||
border: 1px solid #919191;
|
||||
}
|
||||
|
||||
#dialogPreference {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#dialogPreference .tabs-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#dialogPreference .preference {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#dialogPreference img.gravatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
|
@ -3,53 +3,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<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=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="tree" horiz-adv-x="1170" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
|
||||
<glyph unicode="" glyph-name="about" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" />
|
||||
<glyph unicode="" 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="" glyph-name="stop" d="M128 832h768v-768h-768z" />
|
||||
<glyph unicode="" glyph-name="close" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" glyph-name="uniE617" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
|
||||
<glyph unicode="" glyph-name="signout" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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" />
|
||||
<font id="icomoon" horiz-adv-x="512">
|
||||
<font-face units-per-em="512" ascent="480" descent="-32" />
|
||||
<missing-glyph horiz-adv-x="512" />
|
||||
<glyph unicode=" " d="" horiz-adv-x="256" />
|
||||
<glyph unicode="" d="M219.464 448.036q7.428 0 12.857-5.428t5.429-12.857v-64h84l49.428 49.428q5.428 5.428 12.857 5.428t12.857-5.428 5.428-12.857-5.428-12.857l-49.428-49.428v-241.143l49.428-49.428q5.428-5.428 5.428-12.857t-5.428-12.857-12.857-5.428-12.857 5.428l-49.428 49.428h-84v-64q0-7.428-5.428-12.857t-12.857-5.428-12.857 5.428-5.428 12.857v64q-48.857 0-82.857 19.143l-59.715-59.428q-5.428-5.428-12.857-5.428t-12.857 5.428q-5.428 5.143-5.428 12.857t5.428 12.857l56.285 56.572q-1.428 1.428-3.715 4.286t-8.143 12-10.428 18.572-8.285 23.428-3.715 27.714h256v36.572h-256q0 14.572 3.857 29t9.428 24.857 11.143 18.857 9.285 12.428l4 4.286-59.143 52.285q-6 5.715-6 13.715 0 6.857 4.572 12.285 5.143 5.429 12.715 5.857t13.285-4.428l64.857-57.714q32.572 16.571 78.285 16.571v64q0 7.429 5.429 12.857t12.857 5.428zM384.036 310.893q38 0 64.714-26.714t26.714-64.714-26.714-64.714-64.714-26.714v182.857z" horiz-adv-x="476" />
|
||||
<glyph unicode="" d="M512 480v-192l-69.13 69.13-106-106-53.74 53.74 106 106-69.13 69.13zM122.87 410.87l106-106-53.74-53.74-106 106-69.13-69.13v192h192zM442.87 90.87l69.13 69.13v-192h-192l69.13 69.13-106 106 53.74 53.74zM228.87 143.13l-106-106 69.13-69.13h-192v192l69.13-69.13 106 106z" />
|
||||
<glyph unicode="" d="M32 192h192v-192l-69.13 69.13-101-101-53.74 53.74 101 101zM410.87 122.87l101-101-53.74-53.74-101 101-69.13-69.13v192h192zM480 256h-192v192l69.13-69.13 101 101 53.74-53.74-101-101zM154.87 378.87l69.13 69.13v-192h-192l69.13 69.13-101 101 53.74 53.74z" />
|
||||
<glyph unicode="" d="M504.998 65.512l-150.772 150.772c-9.334 9.334-24.607 9.334-33.941 0l-11.313-11.313-92 92 151.028 151.029h-160l-71.029-71.029-7.030 7.029h-33.941v-33.941l7.029-7.029-103.029-103.030 80-80 103.029 103.029 92-92-11.313-11.313c-9.334-9.334-9.334-24.607 0-33.941l150.772-150.772c9.334-9.334 24.607-9.334 33.941 0l56.568 56.568c9.335 9.333 9.335 24.607 0.001 33.941z" />
|
||||
<glyph unicode="" d="M400 416h-288c-26.51 0-48-21.49-48-48v-16h384v16c0 26.51-21.49 48-48 48zM316.16 448l7.058-50.5h-134.436l7.057 50.5h120.321zM320 480h-128c-13.2 0-25.495-10.696-27.321-23.769l-9.357-66.962c-1.827-13.073 7.478-23.769 20.678-23.769h160c13.2 0 22.505 10.696 20.679 23.769l-9.357 66.962c-1.827 13.073-14.122 23.769-27.322 23.769v0zM408 320h-304c-17.6 0-30.696-14.341-29.103-31.869l26.206-288.263c1.593-17.527 17.297-31.868 34.897-31.868h240c17.6 0 33.304 14.341 34.897 31.868l26.205 288.263c1.594 17.528-11.502 31.869-29.102 31.869zM192 32h-48l-16 224h64v-224zM288 32h-64v224h64v-224zM368 32h-48v224h64l-16-224z" />
|
||||
<glyph unicode="" d="M96 416l320-192-320-192z" />
|
||||
<glyph unicode="" d="M360.704 429.209c-29.209 21.069-65.843 31.591-109.978 31.591-33.587 0-61.901-7.424-84.915-22.221-36.531-23.194-55.936-62.566-58.291-118.118h84.633c0 16.179 4.71 31.769 14.157 46.771s25.472 22.502 48.077 22.502c22.963 0 38.81-6.093 47.462-18.253 8.678-12.211 13.005-25.702 13.005-40.499 0-12.877-6.451-24.653-14.233-35.379-4.275-6.247-9.933-11.981-16.921-17.255 0 0-45.901-29.44-66.073-53.095-11.699-13.722-12.749-34.253-13.773-63.719-0.077-2.099 0.717-6.425 8.064-6.425s59.315 0 65.843 0 7.885 4.839 7.987 6.963c0.461 10.726 1.664 16.205 3.635 22.4 3.712 11.699 13.747 21.913 25.063 30.695l23.296 16.077c21.017 16.384 37.811 29.824 45.209 40.371 12.647 17.357 21.529 38.707 21.529 64.025 0 41.344-14.618 72.525-43.776 93.568zM249.369 104.345c-29.184 0.87-53.248-19.303-54.169-50.944-0.922-31.616 21.965-52.505 51.149-53.376 30.464-0.896 53.888 18.637 54.81 50.253 0.896 31.642-21.325 53.171-51.789 54.067z" />
|
||||
<glyph unicode="" d="M80.231 302.016c23.91 18.56 43.725 5.786 70.169-24.832 2.995-3.43 6.989 0.589 9.242 2.56 2.253 1.997 37.171 33.382 38.861 34.841 1.715 1.51 3.763 4.327 1.049 7.475s-12.647 16-19.021 24.32c-46.285 60.544 126.618 101.607 100.045 102.247-13.491 0.358-67.738 0.973-75.827 0.128-32.845-3.481-74.087-34.176-94.848-48.461-27.136-18.662-37.299-29.491-38.963-31.001-7.68-6.733-1.229-22.195-15.155-34.406-14.72-12.902-23.885-3.149-32.41-10.624-4.25-3.738-16.051-12.57-19.456-15.565-3.379-2.969-3.994-8.013-0.538-12.058 0 0 32.333-35.737 35.072-38.886 2.714-3.149 10.009-5.811 14.541-1.817 4.531 3.968 16.154 14.183 18.125 15.923 1.997 1.715-1.28 22.093 9.114 30.157zM226.381 288.807c-3.072 3.558-6.861 3.635-10.163 0.717l-36.736-32.025c-2.867-2.56-3.251-7.271-0.666-10.24l212.327-241.613c4.941-5.735 13.543-6.323 19.251-1.357l24.858 20.787c5.658 4.966 6.247 13.671 1.305 19.379l-210.176 244.352zM509.491 391.642c-1.894 12.647-8.448 9.984-11.853 4.608-3.353-5.351-18.457-28.186-24.653-38.502-6.144-10.291-21.248-30.489-49.51-10.547-29.389 20.787-19.149 35.303-14.055 45.056 5.146 9.805 20.941 37.248 23.245 40.704 2.253 3.456-0.409 13.491-9.498 9.293-9.139-4.224-64.589-26.265-72.295-57.856-7.859-32.179 6.605-60.903-21.76-89.447l-34.355-35.84 34.509-40.141 42.342 40.218c10.112 10.137 31.642 19.994 51.149 15.565 41.805-9.472 64.589 6.247 78.361 32.179 12.339 23.219 10.291 72.064 8.371 84.71zM70.144 41.894c-5.325-5.376-5.325-14.080 0-19.43l24.346-23.808c5.325-5.351 13.773-3.098 19.097 2.253l125.619 123.52-38.502 43.853-130.56-126.387z" />
|
||||
<glyph unicode="" d="M64 416h384v-384h-384z" />
|
||||
<glyph unicode="" d="M181.861 118.974l20.649 28.908-22.627 22.628-28.909-20.648c-5.361 2.997-11.102 5.387-17.133 7.096l-5.841 35.042h-32l-5.84-35.043c-6.031-1.709-11.772-4.099-17.133-7.096l-28.909 20.649-22.628-22.628 20.649-28.908c-2.997-5.36-5.387-11.103-7.096-17.133l-35.043-5.841v-32l35.043-5.841c1.709-6.030 4.099-11.772 7.096-17.133l-20.649-28.908 22.627-22.628 28.909 20.648c5.361-2.997 11.102-5.387 17.133-7.096l5.841-35.042h32l5.84 35.043c6.031 1.709 11.772 4.099 17.133 7.096l28.909-20.648 22.627 22.628-20.649 28.908c2.997 5.36 5.387 11.103 7.096 17.133l35.044 5.84v32l-35.043 5.841c-1.709 6.030-4.099 11.772-7.096 17.133zM112 48c-17.674 0-32 14.327-32 32s14.326 32 32 32 32-14.327 32-32-14.326-32-32-32zM512 288v32l-33.691 6.125c-0.621 4.023-1.416 7.989-2.362 11.895l28.779 18.55-12.246 29.564-33.472-7.234c-2.107 3.455-4.363 6.81-6.746 10.065l19.503 28.171-22.628 22.627-28.171-19.503c-3.256 2.383-6.61 4.638-10.065 6.747l7.234 33.472-29.564 12.247-18.55-28.779c-3.906 0.946-7.872 1.741-11.895 2.362l-6.126 33.691h-32l-6.126-33.691c-4.023-0.621-7.988-1.416-11.895-2.362l-18.549 28.779-29.564-12.246 7.234-33.472c-3.455-2.108-6.81-4.364-10.065-6.747l-28.171 19.503-22.627-22.627 19.503-28.171c-2.383-3.255-4.639-6.61-6.747-10.065l-33.472 7.234-12.246-29.564 28.779-18.55c-0.946-3.906-1.741-7.871-2.362-11.895l-33.692-6.126v-32l33.691-6.125c0.621-4.023 1.416-7.989 2.362-11.895l-28.779-18.55 12.246-29.564 33.472 7.234c2.108-3.455 4.364-6.809 6.747-10.065l-19.503-28.171 22.627-22.628 28.171 19.503c3.255-2.383 6.61-4.638 10.065-6.746l-7.234-33.472 29.564-12.246 18.551 28.779c3.905-0.946 7.871-1.741 11.894-2.362l6.126-33.692h32l6.126 33.691c4.022 0.621 7.988 1.416 11.895 2.362l18.55-28.779 29.564 12.246-7.234 33.472c3.455 2.108 6.81 4.363 10.065 6.746l28.171-19.503 22.628 22.628-19.503 28.171c2.383 3.256 4.638 6.61 6.746 10.065l33.472-7.234 12.246 29.565-28.779 18.55c0.946 3.906 1.741 7.871 2.362 11.895l33.691 6.125zM336 234.4c-38.439 0-69.6 31.161-69.6 69.6s31.16 69.6 69.6 69.6 69.6-31.161 69.6-69.6c0-38.439-31.16-69.6-69.6-69.6z" />
|
||||
<glyph unicode="" d="M367.334 149.709l-70.605 80.691 70.605 80.691c12.007 12.007 12.007 31.463 0 43.443s-31.462 11.981-43.443 0l-67.891-77.593-67.865 77.568c-12.006 12.006-31.463 12.006-43.443 0s-11.981-31.463 0-43.443l70.579-80.666-70.605-80.691c-11.981-12.007-11.981-31.411 0-43.392 12.006-12.007 31.463-12.007 43.443 0l67.891 77.543 67.865-77.543c12.007-12.007 31.462-12.007 43.443 0s12.007 31.385 0.026 43.392z" />
|
||||
<glyph unicode="" d="M224 82.745l-102.627 118.627 29.254 29.255 73.373-57.372 137.372 121.372 29.256-29.254zM415.886 416c0.039-0.033 0.081-0.075 0.114-0.115v-383.771c-0.033-0.039-0.075-0.081-0.114-0.114h-319.772c-0.040 0.033-0.081 0.075-0.114 0.114v383.772c0.033 0.040 0.075 0.081 0.115 0.114h-64.115v-384c0-35.2 28.8-64 64-64h320c35.2 0 64 28.8 64 64v384h-64.114zM320 416v32c0 17.673-14.327 32-32 32h-64c-17.673 0-32-14.327-32-32v-32h-64v-64h256v64h-64zM288 416h-64v32h64v-32z" />
|
||||
<glyph unicode="" d="M64 341.333v64h448v-320h-64v-64h-448v320h64zM448 128h21.334v234.667h-362.666v-21.333h341.334v-213.334zM42.666 64h362.666v234.667h-362.666v-234.667z" />
|
||||
<glyph unicode="" d="M0 272v-96c0-8.836 7.164-16 16-16h480c8.836 0 16 7.164 16 16v96c0 8.836-7.164 16-16 16h-480c-8.836 0-16-7.164-16-16z" />
|
||||
<glyph unicode="" d="M431.714 178.286q0-1.428-0.286-2-18.286-76.572-76.572-124.143t-136.571-47.572q-41.715 0-80.715 15.714t-69.572 44.857l-36.857-36.857q-5.428-5.428-12.857-5.428t-12.857 5.428-5.428 12.857v128q0 7.428 5.428 12.857t12.857 5.428h128q7.428 0 12.857-5.428t5.429-12.857-5.428-12.857l-39.143-39.143q20.285-18.857 46-29.143t53.429-10.286q38.286 0 71.428 18.572t53.143 51.143q3.143 4.857 15.143 33.428 2.286 6.572 8.572 6.572h54.857q3.714 0 6.428-2.714t2.714-6.428zM438.857 406.857v-128q0-7.428-5.428-12.857t-12.857-5.428h-128q-7.428 0-12.857 5.428t-5.428 12.857 5.428 12.857l39.428 39.428q-42.286 39.143-99.714 39.143-38.286 0-71.428-18.571t-53.143-51.143q-3.143-4.857-15.143-33.429-2.285-6.572-8.572-6.572h-56.857q-3.715 0-6.428 2.714t-2.715 6.428v2q18.572 76.572 77.143 124.143t137.143 47.571q41.714 0 81.143-15.857t70-44.714l37.143 36.857q5.428 5.428 12.857 5.428t12.857-5.428 5.428-12.857z" />
|
||||
<glyph unicode="" d="M342 187.428h-31.143q-7.428 0-12.857 5.428t-5.428 12.857v36.572q0 7.428 5.428 12.857t12.857 5.429h31.143q-9.143 30.857-32.143 53.857t-53.857 32.143v-31.143q0-7.429-5.429-12.857t-12.857-5.428h-36.571q-7.428 0-12.857 5.428t-5.429 12.857v31.143q-30.857-9.143-53.857-32.143t-32.143-53.857h31.143q7.429 0 12.857-5.428t5.428-12.857v-36.571q0-7.428-5.428-12.857t-12.857-5.428h-31.143q9.143-30.857 32.143-53.857t53.857-32.143v31.143q0 7.428 5.428 12.857t12.857 5.428h36.571q7.428 0 12.857-5.428t5.429-12.857v-31.143q30.857 9.143 53.857 32.143t32.143 53.857zM438.857 242.286v-36.572q0-7.428-5.428-12.857t-12.857-5.428h-40.857q-10.572-46-44.143-79.572t-79.572-44.143v-40.857q0-7.428-5.428-12.857t-12.857-5.428h-36.571q-7.428 0-12.857 5.428t-5.429 12.857v40.857q-46 10.572-79.572 44.143t-44.143 79.572h-40.857q-7.428 0-12.857 5.428t-5.428 12.857v36.572q0 7.428 5.428 12.857t12.857 5.429h40.857q10.572 46 44.143 79.572t79.572 44.143v40.857q0 7.428 5.428 12.857t12.857 5.428h36.571q7.428 0 12.857-5.428t5.429-12.857v-40.857q46-10.571 79.572-44.143t44.143-79.572h40.857q7.428 0 12.857-5.428t5.428-12.857z" />
|
||||
<glyph unicode="" d="M320 390.857h-237.714q-18.857 0-32.285-13.428t-13.428-32.285v-237.715q0-18.857 13.428-32.286t32.285-13.428h237.714q18.857 0 32.286 13.428t13.428 32.286v237.714q0 18.857-13.428 32.286t-32.286 13.428zM402.286 345.143v-237.715q0-34-24.143-58.143t-58.143-24.143h-237.714q-34 0-58.143 24.143t-24.143 58.143v237.714q0 34 24.143 58.143t58.143 24.143h237.714q34 0 58.143-24.143t24.143-58.143z" horiz-adv-x="403" />
|
||||
<glyph unicode="" d="M109.715 41.143h219.429v109.714h-219.429v-109.714zM365.714 41.143h36.572v256q0 4-2.857 11t-5.714 9.857l-80.286 80.285q-2.857 2.857-9.714 5.715t-11.143 2.857v-118.857q0-11.429-8-19.429t-19.428-8h-164.571q-11.428 0-19.428 8t-8 19.428v118.857h-36.572v-365.715h36.572v118.857q0 11.428 8 19.428t19.428 8h237.715q11.428 0 19.428-8t8-19.428v-118.857zM256 306.286v91.428q0 3.714-2.714 6.429t-6.428 2.714h-54.857q-3.714 0-6.429-2.714t-2.714-6.429v-91.428q0-3.714 2.714-6.428t6.428-2.714h54.857q3.714 0 6.429 2.714t2.714 6.428zM438.857 297.143v-265.143q0-11.428-8-19.428t-19.428-8h-384q-11.428 0-19.428 8t-8 19.428v384q0 11.428 8 19.428t19.428 8h265.143q11.428 0 25.143-5.714t21.714-13.714l80-80q8-8 13.714-21.714t5.714-25.143z" />
|
||||
<glyph unicode="" d="M256 370.286q-58.286 0-109-19.857t-80.572-53.572-29.857-72.857q0-32 20.428-61t57.572-50.143l24.857-14.286-7.714-27.428q-6.857-26-20-49.143 43.428 18 78.572 48.857l12.286 10.857 16.286-1.714q19.714-2.286 37.143-2.286 58.285 0 109 19.857t80.572 53.572 29.857 72.857-29.857 72.857-80.572 53.572-109 19.857zM512 224q0-49.714-34.286-91.857t-93.143-66.572-128.572-24.428q-20 0-41.428 2.286-56.572-50-131.429-69.143-14-4-32.572-6.286h-1.428q-4.285 0-7.715 3t-4.572 7.857v0.286q-0.857 1.143-0.143 3.428t0.572 2.857 1.285 2.714l1.715 2.572t2 2.428 2.285 2.572q2 2.286 8.857 9.857t9.857 10.857 8.857 11.286 9.285 14.572 7.715 16.857 7.428 21.714q-44.857 25.428-70.715 62.857t-25.857 80.286q0 49.714 34.285 91.857t93.143 66.571 128.571 24.429 128.571-24.429 93.143-66.571 34.286-91.857z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,99 @@
|
|||
.wrapper {
|
||||
margin: 0 auto;
|
||||
width: 980px;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
float: left;
|
||||
height: 32px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.header li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.header a {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
padding: 4px 8px;
|
||||
color: #333;
|
||||
line-height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: #4183C4;
|
||||
}
|
||||
|
||||
.content {
|
||||
border-top: 1px solid #A4A4A4;
|
||||
border-bottom: 1px solid #919191;
|
||||
background-color: #202021;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
color: #FFF;
|
||||
font-size: 70px;
|
||||
margin: 0 0 60px;
|
||||
}
|
||||
|
||||
.content h3 {
|
||||
color: #4183c4;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.content .form {
|
||||
width: 320px;
|
||||
margin-top: 28px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content .form input {
|
||||
width: 100%;
|
||||
background-color: #fafafa;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset;
|
||||
color: #333;
|
||||
min-height: 34px;
|
||||
outline: medium none;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
border: 1px solid #FFF;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.content .form input:focus {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
#msg {
|
||||
background-color: #fcdede;
|
||||
border: 1px solid #d2b2b2;
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
color: #911;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: -48px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
line-height: 30px;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
color: #4183c4;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
|
@ -1,158 +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.
|
||||
*/
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-height: 1px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header li {
|
||||
margin-left: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.header .gravatar {
|
||||
width: 26px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
height: 28px;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.header .font-ico {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.share-panel {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
width: 190px;
|
||||
padding: 5px 0px;
|
||||
right: 0px;
|
||||
line-height: normal;
|
||||
top: 38px;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.bottom-window-group {
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
#output {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
padding: 2px 5px;
|
||||
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: 3px 8px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
#editorDivWrap {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#goNews {
|
||||
width: 30%;
|
||||
overflow: auto;
|
||||
border-left: 1px solid #919191;
|
||||
}
|
||||
|
||||
#goNews::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#goNews li a {
|
||||
display: block;
|
||||
padding: 8px 10px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#goNews li a.fn-right {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
#goNews li a:hover {
|
||||
text-decoration: none;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
#goNews li.title {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
background-color: #f9fafb;
|
||||
}
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#shellOutput, #shellInput {
|
||||
width: 100%;
|
||||
}
|
|
@ -1,89 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* themes for side
|
||||
*
|
||||
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
|
||||
* @version 0.1.0.0, Dec 6, 2015
|
||||
*/
|
||||
/* start side */
|
||||
.side {
|
||||
background-color: #FFF;
|
||||
width: 20%;
|
||||
position: absolute;
|
||||
border-right: 1px solid #9B9B9B;
|
||||
height: 100%;
|
||||
z-index: 8;
|
||||
flex-flow: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.side-max {
|
||||
width: 100%;
|
||||
z-index: 11;
|
||||
}
|
||||
/* end side */
|
||||
|
||||
/* start side right */
|
||||
.side-right .tabs-panel > div {
|
||||
.side .tabs-panel {
|
||||
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;
|
||||
}
|
||||
/* end side right */
|
||||
/* start side */
|
||||
|
||||
/* start tree */
|
||||
.ztree {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
outline: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.ztree li a.curSelectedNode {
|
||||
|
@ -91,7 +29,6 @@
|
|||
border-width: 0;
|
||||
color: #fff;
|
||||
height: 18px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ztree li a:hover {
|
||||
|
@ -99,9 +36,7 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
.ztree li > a > span.button.ico-ztree-dir {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
|
@ -114,14 +49,6 @@
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1,241 +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.
|
||||
*/
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin: 0 auto;
|
||||
width: 980px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
float: left;
|
||||
height: 32px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.header li {
|
||||
float: left;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.header a {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
padding: 5px 0;
|
||||
color: #333;
|
||||
line-height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.fn-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
border-top: 1px solid #A4A4A4;
|
||||
border-bottom: 1px solid #919191;
|
||||
background-color: #3b3e43;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
color: #FFF;
|
||||
font-size: 70px;
|
||||
margin: 0 0 60px;
|
||||
}
|
||||
|
||||
.content h3 {
|
||||
color: #4285f4;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.content .form {
|
||||
padding: 24px 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
width: 320px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content a {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
|
||||
.login__icon {
|
||||
width: 200px;
|
||||
transition: all .15s ease-in-out;
|
||||
padding-right: 24px;
|
||||
color: #3b3e43;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.login__icon:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
background-color: #2ebc4f;
|
||||
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;
|
||||
}
|
||||
|
||||
.btn.btn-white,
|
||||
.btn.btn-red {
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn.btn-red {
|
||||
color: #d23f31;
|
||||
}
|
||||
|
||||
.btn.btn-white:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.btn.btn-red:hover {
|
||||
color: #fff;
|
||||
background-color: #d23f31;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.footer {
|
||||
line-height: 30px;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer .github-btns {
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* start sign up */
|
||||
.dir {
|
||||
color: #4285f4;
|
||||
font-size: 18px;
|
||||
word-wrap: break-word;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#dir {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form.sign-up {
|
||||
margin-top: -108px;
|
||||
}
|
||||
|
||||
#loginBtn,
|
||||
#signUpBtn {
|
||||
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 */
|
|
@ -1,25 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#startPage {
|
||||
padding: 50px 70px;
|
||||
line-height: 28px;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#startPage a {
|
||||
|
@ -32,13 +15,12 @@
|
|||
}
|
||||
|
||||
#startPage .title {
|
||||
background-color: #BBB;
|
||||
background-color: #f5f5f5;
|
||||
border-bottom-width: 0 !important;
|
||||
border-radius: 3px 3px 0 0;
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px 10px;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#startPage .details {
|
||||
|
@ -53,7 +35,7 @@
|
|||
#startPage .details li.border {
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid #919191;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
}
|
||||
|
||||
#startPage .details li.border.workspace {
|
||||
|
@ -65,22 +47,19 @@
|
|||
}
|
||||
|
||||
#startPage .news {
|
||||
height: 300px;
|
||||
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;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
}
|
||||
|
||||
#startPage .date {
|
||||
color: #bbb;
|
||||
font-size: 13px;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -1,179 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
.side-right {
|
||||
background-color: #303130;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #181B1D;
|
||||
border-top: 1px solid #000000;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.edit-panel {
|
||||
background-color: #181818;
|
||||
color: #EBEBEB;
|
||||
}
|
||||
|
||||
.font-ico,
|
||||
.ico-restore {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.font-ico:hover,
|
||||
.ico-restore:hover,
|
||||
.frame li:hover .font-ico {
|
||||
color: #e1e1e1;
|
||||
}
|
||||
|
||||
.menu {
|
||||
background: #252525;
|
||||
box-shadow: 0 1px 0 0 #353535 inset;
|
||||
border-bottom: 1px solid #000000;
|
||||
}
|
||||
|
||||
.menu > ul > li > span {
|
||||
color: #cecece;
|
||||
text-shadow: #292a2b 0px 1px 0px;
|
||||
}
|
||||
|
||||
.menu > ul > li.selected {
|
||||
background-color: #494949;
|
||||
color: #d4d4d4;
|
||||
box-shadow: 1px 0 0 0 #000000 inset, 1px 0 0 0 #000000, 0 1px 0 0 rgba(255, 255, 255, 0.15) inset;
|
||||
}
|
||||
|
||||
.menu .split {
|
||||
border-color: #000000;
|
||||
}
|
||||
|
||||
.frame {
|
||||
color: #f1f1f1;
|
||||
border: 1px solid #00040a;
|
||||
box-shadow: 0px 3px 15px 0px rgba(0, 0, 0, 0.65);
|
||||
background-color: #494949;
|
||||
text-shadow: 0px 1px 0px #2c2c2c;
|
||||
}
|
||||
|
||||
.frame li:hover,
|
||||
.zeroclipboard-is-hover {
|
||||
background-color: #3875D7;
|
||||
color: #FFF;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.frame li.disabled:hover {
|
||||
background-color: #494949;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.frame .hr {
|
||||
font-size: 1px;
|
||||
margin: 2px 3px;
|
||||
border-top: 1px solid #353535;
|
||||
border-bottom: 1px solid #565656;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
box-shadow: 0 -1px 0 0 #000000 inset, 0 1px 0 0 rgba(255, 255, 255, 0.06) inset, 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
background-color: #252525;
|
||||
}
|
||||
|
||||
.tabs > div {
|
||||
background-color: #303030;
|
||||
color: #adadad;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
|
||||
border-right: 1px solid #181B1D;
|
||||
}
|
||||
|
||||
.tabs > div:hover {
|
||||
background-color: #363636;
|
||||
}
|
||||
|
||||
.tabs > div.current {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.dialog-header-bg {
|
||||
border-bottom: 1px solid #000000;
|
||||
box-shadow: 0 1px #5b5c5e inset;
|
||||
background-image: -webkit-linear-gradient(top, #404143 0%, #333537 52%, #252729 52%, #252729 100%);
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dialog-main {
|
||||
background-color: #DEDEDF;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
box-shadow: 0 1px 0 0 #353535 inset;
|
||||
border-top: 1px solid #000000;
|
||||
background-color: #2B2B2B;
|
||||
}
|
||||
|
||||
.ztree li a {
|
||||
color: #e1e1e1;
|
||||
}
|
||||
|
||||
.bottom-window-group .tabs-panel {
|
||||
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;
|
||||
}
|
|
@ -1,155 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
.side-right {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid #919191;
|
||||
background-color: #F0F0F0;
|
||||
color: #919191;
|
||||
}
|
||||
|
||||
.edit-panel {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.font-ico,
|
||||
.ico-restore {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.font-ico:hover,
|
||||
.ico-restore:hover,
|
||||
.frame li:hover .font-ico {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu {
|
||||
background: #F0F0F0;
|
||||
box-shadow: 0 1px 0 0 #E7E7E7 inset;
|
||||
border-bottom: 1px solid #919191;
|
||||
}
|
||||
|
||||
.menu > ul > li > span {
|
||||
color: #000;
|
||||
text-shadow: 0px 1px 0px #efefef;
|
||||
}
|
||||
|
||||
.menu > ul > li.selected {
|
||||
background-color: #cfcfcf;
|
||||
box-shadow: 1px 0 0 0 #b6b6b6 inset, 1px 0 0 0 #b6b6b6, 0 1px 0 0 rgba(255, 255, 255, 0.15) inset;
|
||||
color: #393939;
|
||||
}
|
||||
|
||||
.menu .split {
|
||||
border-color: #b6b6b6;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border: 1px solid #5F5F5F;
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
background-color: #F8F8F8;
|
||||
}
|
||||
|
||||
.frame li:hover,
|
||||
.zeroclipboard-is-hover {
|
||||
background-color: #3875D7;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.frame li.disabled:hover {
|
||||
background-color: #F8F8F8;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.frame .hr {
|
||||
font-size: 1px;
|
||||
margin: 2px 3px;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
border-bottom: 1px solid #F8F8F8;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
box-shadow: 0 -1px 0 0 rgba(6, 6, 6, 0.1) inset, 0 1px 0 0 rgba(255, 255, 255, 0.44999999999999996) inset, 0 1px 0 transparent;
|
||||
background-color: #E6E6E6;
|
||||
}
|
||||
|
||||
.tabs > div {
|
||||
background-color: #DDD;
|
||||
color: #8B8B8B;
|
||||
border-right: 1px solid #ADADAD;
|
||||
}
|
||||
|
||||
.tabs > div:hover {
|
||||
background-color: #E4E4E4;
|
||||
}
|
||||
|
||||
.tabs > div.current {
|
||||
background-color: #bbb;
|
||||
color: #FFF;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.dialog-header-bg {
|
||||
border-bottom: 1px solid #8891A1;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dialog-main {
|
||||
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 {
|
||||
border-top: 1px solid #919191;
|
||||
}
|
||||
.ui-layout-resizer-east-closed {
|
||||
border-left: 1px solid #919191;
|
||||
}
|
||||
.ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */
|
||||
background: #919191;
|
||||
}
|
|
@ -1,28 +1,74 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/* start icon */
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src:url('fonts/icomoon.eot?35cb2z');
|
||||
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'),
|
||||
url('fonts/icomoon.woff?35cb2z') format('woff'),
|
||||
url('fonts/icomoon.ttf?35cb2z') format('truetype'),
|
||||
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.font-ico {
|
||||
font-family: 'icomoon';
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.font-ico:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ico-play:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.ico-save:before {
|
||||
content: "\f0c7";
|
||||
}
|
||||
|
||||
.ico-max:before {
|
||||
content: "\f096";
|
||||
}
|
||||
|
||||
.ico-format:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.ico-buildrun:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.ico-stop:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.ico-restore:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.ico-min:before {
|
||||
content: "\e60d";
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.ico-close:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
/* end ico */
|
||||
|
||||
/*
|
||||
* 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;
|
||||
border: 1px solid #5F5F5F;
|
||||
background-color: #F8F8F8;
|
||||
width: 320px;
|
||||
z-index: 21;
|
||||
display: none;
|
||||
|
@ -34,9 +80,17 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.frame li.disabled,
|
||||
.frame li.disabled .font-ico,
|
||||
.frame li.disabled:hover .font-ico {
|
||||
.frame li.disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.frame li:hover {
|
||||
background-color: #3875D7;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.frame li.disabled:hover {
|
||||
background-color: #F8F8F8;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
|
@ -50,51 +104,51 @@
|
|||
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;
|
||||
.frame .hr {
|
||||
background-color: #bdbdbd;
|
||||
height: 1px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
/* end frame */
|
||||
|
||||
/* start tabs */
|
||||
.tabs {
|
||||
height: 21px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
background-color: #E6E6E6;
|
||||
border-top: 1px solid #A4A4A4;
|
||||
border-bottom: 1px solid #9D9D9D;
|
||||
}
|
||||
|
||||
.tabs > div {
|
||||
float: left;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: #DDD;
|
||||
color: #8B8B8B;
|
||||
border-right: 1px solid #ADADAD;
|
||||
}
|
||||
|
||||
.tabs > div > span.changed {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.tabs-panel {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
.tabs > div.current {
|
||||
background-color: #9F9F9F;
|
||||
color: #FFF;
|
||||
}
|
||||
/* end tabs */
|
||||
|
||||
/* start framework */
|
||||
.content {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* end framework */
|
||||
|
||||
/* start menu */
|
||||
.menu {
|
||||
display: block !important;
|
||||
background-color: #F0F0F0;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.menu > ul > li {
|
||||
|
@ -102,67 +156,34 @@
|
|||
}
|
||||
|
||||
.menu > ul > li > span {
|
||||
color: #000;
|
||||
font-size: 12px;
|
||||
line-height: 21px;
|
||||
line-height: 24px;
|
||||
padding: 5px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
.menu .split {
|
||||
float: left;
|
||||
border-left: 1px solid #919191;
|
||||
height: 21px;
|
||||
margin: 0 5px 0 0px
|
||||
}
|
||||
|
||||
.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: 0px;
|
||||
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);
|
||||
}
|
||||
/* end menu */
|
||||
|
||||
/* start editor */
|
||||
.edit-panel {
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
left: 20%;
|
||||
width: 60%;
|
||||
width: 80%;
|
||||
height: 70%;
|
||||
overflow: hidden;
|
||||
flex-flow: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit-panel .tabs > div {
|
||||
background-color: #d1d1d1;
|
||||
border-right-color: #9b9b9b;
|
||||
color: #333;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.edit-panel .tabs > div.current {
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.toolbars {
|
||||
|
@ -171,7 +192,7 @@
|
|||
top: 1px;
|
||||
}
|
||||
|
||||
.ico {
|
||||
.edit-panel .tabs .ico {
|
||||
background-image: url("../images/ico-file.png");
|
||||
float: left;
|
||||
height: 16px;
|
||||
|
@ -179,7 +200,7 @@
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
/* 统一为 static/js/lib/codemirror-x.x/addon/hint/show-hint.css 中的.CodeMirror-hints */
|
||||
/* 统一为 static/js/lib/codemirror-4.5/addon/hint/show-hint.css 中的.CodeMirror-hints */
|
||||
.edit-exprinfo {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
@ -197,6 +218,7 @@
|
|||
|
||||
background: white;
|
||||
font-size: 90%;
|
||||
font-family: Consolas, Courier New, monospace;
|
||||
|
||||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
|
@ -204,11 +226,7 @@
|
|||
|
||||
.CodeMirror,
|
||||
.CodeMirror-hints {
|
||||
font-family: Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-hints .ico {
|
||||
margin: -1px 2px 0 -1px;
|
||||
font-family: Consolas, Courier New, monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
|
@ -226,63 +244,58 @@
|
|||
background: #08f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 2px solid #333;
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
background-color: transparent;
|
||||
}
|
||||
/* end editor */
|
||||
|
||||
/* start bottom-window-group */
|
||||
.bottom-window-group {
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
left: 20%;
|
||||
width: 80%;
|
||||
height: 30%;
|
||||
top: 70%;
|
||||
z-index: 7;
|
||||
background-color: #fff;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.bottom-window-group-max {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.bottom-window-group > div > div {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.bottom-window-group .output {
|
||||
font-family: Consolas, Courier New, monospace;
|
||||
padding: 0 5px;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
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-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 {
|
||||
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 .output .build-failed,
|
||||
.bottom-window-group .output .install-failed,
|
||||
.bottom-window-group .output .get-failed {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bottom-window-group table {
|
||||
|
@ -290,13 +303,8 @@
|
|||
}
|
||||
|
||||
.bottom-window-group td {
|
||||
border-bottom: 1px solid #919191;
|
||||
font-size: 12px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.bottom-window-group .notification {
|
||||
outline: 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.bottom-window-group .notification .type,
|
||||
|
@ -305,19 +313,39 @@
|
|||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.bottom-window-group .search {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
outline: 0;
|
||||
.bottom-window-group .search li {
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
padding: 0 3px;
|
||||
word-wrap: normal;
|
||||
word-break: normal;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bottom-window-group .search li.selected {
|
||||
background-color: #3875d7;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.bottom-window-group .search .path {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bottom-window-group .search li.selected .path {
|
||||
color: #FFF;
|
||||
}
|
||||
/* end bottom-window-group */
|
||||
|
||||
/* start footer */
|
||||
.footer {
|
||||
box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.06) inset;
|
||||
border-top: 1px solid #919191;
|
||||
background-color: #F0F0F0;
|
||||
padding-left: 5px;
|
||||
height: 19px;
|
||||
line-height: 18px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.footer .cursor {
|
||||
|
@ -328,7 +356,7 @@
|
|||
float: right;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
background-color: #9d0000;
|
||||
background-color: red;
|
||||
color: #FFF;
|
||||
margin: 1px 5px;
|
||||
padding: 0 2px;
|
||||
|
|
|
@ -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}
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 15 KiB |
|
@ -1,95 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @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,
|
||||
init: function () {
|
||||
this._initTabs();
|
||||
this._initFrame();
|
||||
|
||||
$('.bottom-window-group .output').click(function () {
|
||||
$(this).focus();
|
||||
});
|
||||
|
||||
$('.bottom-window-group .output').on('click', '.path', function (event) {
|
||||
var $path = $(this),
|
||||
tId = tree.getTIdByPath($path.data("path"));
|
||||
tree.openFile(tree.fileTree.getNodeByTId(tId),
|
||||
CodeMirror.Pos($path.data("line") - 1, $path.data("column") - 1));
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
},
|
||||
_initFrame: function () {
|
||||
$(".bottom-window-group .output").parent().mouseup(function (event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.button === 0) { // 左键
|
||||
$(".bottom-window-group .frame").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// event.button === 2 右键
|
||||
var left = event.screenX,
|
||||
$it = $(this);
|
||||
if ($(".side").css("left") === "auto" || $(".side").css("left") === "0px") {
|
||||
left = event.screenX - $(".side").width();
|
||||
}
|
||||
$(".bottom-window-group .frame").show().css({
|
||||
"left": left + "px",
|
||||
"top": (event.offsetY + event.target.offsetTop - $it.scrollTop() - 10) + "px"
|
||||
});
|
||||
return;
|
||||
});
|
||||
},
|
||||
clear: function (id) {
|
||||
$('.bottom-window-group .' + id + ' > div').text('');
|
||||
},
|
||||
resetOutput: function () {
|
||||
this.clear('output');
|
||||
bottomGroup.tabs.setCurrent("output");
|
||||
windows.flowBottom();
|
||||
},
|
||||
_initTabs: function () {
|
||||
this.tabs = new Tabs({
|
||||
id: ".bottom-window-group",
|
||||
clickAfter: function (id) {
|
||||
this._$tabsPanel.find("." + id).focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
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/>")) {
|
||||
data = Autolinker.link(data);
|
||||
}
|
||||
|
||||
$output.find("div").html(data);
|
||||
$output.parent().scrollTop($output[0].scrollHeight);
|
||||
}
|
||||
};
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2014-present, b3log.org
|
||||
* Copyright (C) 2011, Liyuan Li
|
||||
*
|
||||
* 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,18 +13,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -44,7 +37,7 @@
|
|||
"closeIconHover": "dialog-close-icon-hover",
|
||||
"title": "dialog-title"
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$.extend(Dialog.prototype, {
|
||||
|
@ -141,6 +134,22 @@
|
|||
$($("#" + id + "Dialog ." + styleClass.main + " div").get(0)).append(cloneObj);
|
||||
$(cloneObj).show();
|
||||
|
||||
// Sets position.
|
||||
var top = "", left = "",
|
||||
$dialog = $("#" + id + "Dialog");
|
||||
if (settings.position) {
|
||||
top = settings.position.top;
|
||||
left = settings.position.left;
|
||||
} else {
|
||||
// 20(footer) + 23(header)
|
||||
top = parseInt((windowH - dialogH - 43) / 2);
|
||||
left = parseInt((windowW - dialogW) / 2);
|
||||
}
|
||||
$dialog.css({
|
||||
"top": top + "px",
|
||||
"left": left + "px"
|
||||
});
|
||||
|
||||
// Bind event.
|
||||
$("#" + id + "Dialog ." + styleClass.closeIcon).bind("click", function () {
|
||||
$.dialog._close(id, settings);
|
||||
|
@ -165,15 +174,6 @@
|
|||
$.dialog._close(id, settings);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function () {
|
||||
var height = $("body").height() > $(window).height() ? $("body").height() : $(window).height();
|
||||
$(".dialog-background").height(height);
|
||||
});
|
||||
|
||||
if (typeof settings.afterInit === "function") {
|
||||
settings.afterInit();
|
||||
}
|
||||
},
|
||||
_bindMove: function (id, className) {
|
||||
$("#" + id + "Dialog ." + className).mousedown(function (event) {
|
||||
|
@ -206,12 +206,12 @@
|
|||
if (positionX > $(window).width() - $(dialog).width()) {
|
||||
positionX = $(window).width() - $(dialog).width();
|
||||
}
|
||||
if (positionY > $(window).height() - $(dialog).height()) {
|
||||
positionY = $(window).height() - $(dialog).height();
|
||||
}
|
||||
if (positionY < 0) {
|
||||
positionY = 0;
|
||||
}
|
||||
if (positionY > $(window).height() - $(dialog).height()) {
|
||||
positionY = $(window).height() - $(dialog).height();
|
||||
}
|
||||
dialog.style.left = positionX + "px";
|
||||
dialog.style.top = positionY + "px";
|
||||
};
|
||||
|
@ -227,7 +227,7 @@
|
|||
_document.ondragstart = null;
|
||||
_document.onselectstart = null;
|
||||
_document.onselect = null;
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
_close: function (id, settings) {
|
||||
|
@ -251,41 +251,20 @@
|
|||
_openDialog: function (target, msg) {
|
||||
var inst = this._getInst(target);
|
||||
var id = inst.id,
|
||||
settings = inst.settings,
|
||||
top = "", left = "",
|
||||
$dialog = $("#" + id + "Dialog"),
|
||||
windowH = $(window).height(),
|
||||
windowW = $(window).width(),
|
||||
dialogH = settings.height ? settings.height : parseInt(windowH * 0.6),
|
||||
dialogW = settings.width ? settings.width : parseInt(windowW * 0.6);
|
||||
settings = inst.settings;
|
||||
|
||||
// Sets position.
|
||||
if (settings.position) {
|
||||
top = settings.position.top;
|
||||
left = settings.position.left;
|
||||
} else {
|
||||
// 20(footer) + 23(header)
|
||||
top = parseInt((windowH - dialogH - 43) / 2);
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
left = parseInt((windowW - dialogW) / 2);
|
||||
}
|
||||
$dialog.css({
|
||||
"top": top + "px",
|
||||
"left": left + "px"
|
||||
}).show();
|
||||
$("#" + id + "Dialog").show();
|
||||
|
||||
if (settings.modal) {
|
||||
var styleClass = this._getDefaults($.dialog._defaults, settings, "styleClass");
|
||||
$("." + styleClass.background).show();
|
||||
}
|
||||
|
||||
$("#" + id + "Dialog .dialog-footer button:eq(0)").focus();
|
||||
|
||||
if (typeof settings.afterOpen === "function") {
|
||||
settings.afterOpen(msg);
|
||||
}
|
||||
|
||||
$("#" + id + "Dialog .dialog-footer button:eq(0)").focus();
|
||||
},
|
||||
_updateDialog: function (target, data) {
|
||||
var inst = this._getInst(target);
|
||||
|
|
|
@ -1,224 +1,84 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @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) {
|
||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
||||
if (editors.data[i].editor.options.path === path) {
|
||||
return editors.data[i].editor;
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
$('.edit-panel .tabs > div[data-index="' + $('.edit-panel .frame').data('index') + ']').find('.ico-close').click();
|
||||
},
|
||||
closeOther: function () {
|
||||
var currentIndex = $(".edit-panel .frame").data("index");
|
||||
|
||||
// 设置全部关闭标识
|
||||
var removeData = [];
|
||||
$(".edit-panel .tabs > div").each(function (i) {
|
||||
if (currentIndex !== $(this).data("index")) {
|
||||
removeData.push($(this).data("index"));
|
||||
}
|
||||
});
|
||||
if (removeData.length === 0) {
|
||||
return false;
|
||||
}
|
||||
var firstIndex = removeData.splice(0, 1);
|
||||
$("#dialogCloseEditor").data("removeData", removeData);
|
||||
// 开始关闭
|
||||
$('.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();
|
||||
}
|
||||
if (wide.curEditor) {
|
||||
wide.curEditor.focus();
|
||||
}
|
||||
},
|
||||
_initClose: function () {
|
||||
new ZeroClipboard($("#copyFilePath"));
|
||||
|
||||
// 关闭、关闭其他、关闭所有
|
||||
$(".edit-panel").on("mouseup", '.tabs > div', function (event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.button === 0) { // 左键
|
||||
$(".edit-panel .frame").hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
// event.button === 2 右键
|
||||
var left = event.screenX;
|
||||
if ($(".side").css("left") === "auto" || $(".side").css("left") === "0px") {
|
||||
left = event.screenX - $(".side").width();
|
||||
}
|
||||
$(".edit-panel .frame").show().css({
|
||||
"left": left + "px",
|
||||
"top": "21px"
|
||||
}).data('index', $(this).data("index"));
|
||||
|
||||
$("#copyFilePath").attr('data-clipboard-text', $(this).find("span:eq(0)").attr("title"));
|
||||
return false;
|
||||
});
|
||||
},
|
||||
init: function () {
|
||||
$("#dialogCloseEditor").dialog({
|
||||
"modal": true,
|
||||
"height": 90,
|
||||
"width": 260,
|
||||
"title": config.label.tip,
|
||||
"hideFooter": true,
|
||||
"afterOpen": function (fileName) {
|
||||
$("#dialogCloseEditor > div:eq(0)").html(config.label.file
|
||||
+ ' <b>' + fileName + '</b>. ' + config.label.confirm_save + '?');
|
||||
$("#dialogCloseEditor button:eq(0)").focus();
|
||||
},
|
||||
"afterInit": function () {
|
||||
$("#dialogCloseEditor button.save").click(function () {
|
||||
var i = $("#dialogCloseEditor").data("index");
|
||||
wide.fmt(editors.data[i].id, editors.data[i].editor);
|
||||
editors.tabs.del(editors.data[i].id);
|
||||
$("#dialogCloseEditor").dialog("close");
|
||||
editors._removeAllMarker();
|
||||
});
|
||||
|
||||
$("#dialogCloseEditor button.discard").click(function () {
|
||||
var i = $("#dialogCloseEditor").data("index");
|
||||
editors.tabs.del(editors.data[i].id);
|
||||
$("#dialogCloseEditor").dialog("close");
|
||||
editors._removeAllMarker();
|
||||
});
|
||||
|
||||
$("#dialogCloseEditor button.cancel").click(function (event) {
|
||||
$("#dialogCloseEditor").dialog("close");
|
||||
editors._removeAllMarker();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
editors.tabs = new Tabs({
|
||||
id: ".edit-panel",
|
||||
setAfter: function () {
|
||||
if (wide.curEditor) {
|
||||
wide.curEditor.focus();
|
||||
}
|
||||
},
|
||||
clickAfter: function (id) {
|
||||
if (id === 'startPage') {
|
||||
wide.curEditor = undefined;
|
||||
$(".footer .cursor").text('');
|
||||
wide.refreshOutline();
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
removeBefore: function (id) {
|
||||
if (id === 'startPage') { // 当前关闭的 tab 是起始页
|
||||
editors._removeAllMarker();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (editors.data[i].editor.doc.isClean()) {
|
||||
editors._removeAllMarker();
|
||||
return true;
|
||||
} else {
|
||||
$("#dialogCloseEditor").dialog("open", $('.edit-panel .tabs > div[data-index="'
|
||||
+ editors.data[i].id + '"] > span:eq(0)').text());
|
||||
$("#dialogCloseEditor").data("index", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
wide.curEditor = editors.data[i].editor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wide.curEditor.focus();
|
||||
},
|
||||
removeAfter: function (id, nextId) {
|
||||
if ($(".edit-panel .tabs > div").length === 0) {
|
||||
// 全部 tab 都关闭时才 disables 菜单中“全部关闭”的按钮
|
||||
menu.disabled(['close-all']);
|
||||
if (id === 'startPage') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移除编辑器
|
||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
||||
if (editors.data[i].id === id) {
|
||||
wide.fmt(tree.fileTree.getNodeByTId(editors.data[i].id).path, editors.data[i].editor);
|
||||
editors.data.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (editors.data.length === 0) { // 起始页可能存在,所以用编辑器数据判断
|
||||
menu.disabled(['save-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', '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;
|
||||
|
||||
wide.curEditor = undefined;
|
||||
wide.refreshOutline();
|
||||
$(".footer .cursor").text('');
|
||||
|
||||
menu.disabled(['save-all', 'close-all', 'run', 'go-get', 'go-install']);
|
||||
$(".toolbars").hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextId === editors.tabs.getCurrentId()) {
|
||||
// 关闭的不是当前编辑器
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".edit-panel .tabs").on("dblclick", function () {
|
||||
if ($(".toolbars .ico-max").length === 1) {
|
||||
windows.maxEditor();
|
||||
} else {
|
||||
windows.restoreEditor();
|
||||
}
|
||||
});
|
||||
|
||||
this._initCodeMirrorHotKeys();
|
||||
this.openStartPage();
|
||||
this._initClose();
|
||||
this.openStartPage()
|
||||
},
|
||||
openStartPage: function () {
|
||||
wide.curEditor = undefined;
|
||||
wide.refreshOutline();
|
||||
$(".footer .cursor").text('');
|
||||
|
||||
var dateFormat = function (time, fmt) {
|
||||
var date = new Date(time);
|
||||
var dateObj = {
|
||||
|
@ -242,13 +102,12 @@ var editors = {
|
|||
|
||||
editors.tabs.add({
|
||||
id: "startPage",
|
||||
title: '<span title="' + config.label.start_page
|
||||
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
|
||||
title: '<span title="' + config.label.start_page + '">' + config.label.start_page + '</span>',
|
||||
content: '<div id="startPage"></div>',
|
||||
after: function () {
|
||||
$("#startPage").load('/start?sid=' + config.wideSessionId);
|
||||
$("#startPage").load('/start');
|
||||
$.ajax({
|
||||
url: "https://ld246.com/apis/articles?tags=wide,golang&p=1&size=20",
|
||||
url: "http://symphony.b3log.org/apis/articles?tags=wide,golang&p=1&size=30",
|
||||
type: "GET",
|
||||
dataType: "jsonp",
|
||||
jsonp: "callback",
|
||||
|
@ -258,22 +117,21 @@ 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> <span class='date'>"
|
||||
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd');
|
||||
+"</span></li>";
|
||||
+ dateFormat(article.articleCreateTime, 'yyyy-MM-dd hh:mm');
|
||||
+"</span></li>"
|
||||
}
|
||||
|
||||
$("#startPage .news").html(listHTML + "</ul>");
|
||||
|
@ -283,12 +141,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 +156,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,12 +176,6 @@ var editors = {
|
|||
|
||||
var autocompleteHints = [];
|
||||
|
||||
if (editors.autocompleteMutex && editor.state.completionActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
editors.autocompleteMutex = true;
|
||||
|
||||
$.ajax({
|
||||
async: false, // 同步执行
|
||||
type: 'POST',
|
||||
|
@ -337,72 +187,43 @@ var editors = {
|
|||
|
||||
if (autocompleteArray) {
|
||||
for (var i = 0; i < autocompleteArray.length; i++) {
|
||||
var displayText = '',
|
||||
text = autocompleteArray[i].name;
|
||||
var displayText = '';
|
||||
|
||||
switch (autocompleteArray[i].class) {
|
||||
case "type":
|
||||
displayText = '<span class="fn-clear"><span class="ico-type ico"></span>'// + autocompleteArray[i].class
|
||||
+ '<b>' + autocompleteArray[i].name + '</b> '
|
||||
+ autocompleteArray[i].type + '</span>';
|
||||
break;
|
||||
case "const":
|
||||
displayText = '<span class="fn-clear"><span class="ico-const ico"></span>'// + autocompleteArray[i].class
|
||||
+ '<b>' + autocompleteArray[i].name + '</b> '
|
||||
+ autocompleteArray[i].type + '</span>';
|
||||
break;
|
||||
case "var":
|
||||
displayText = '<span class="fn-clear"><span class="ico-var ico"></span>'// + autocompleteArray[i].class
|
||||
+ '<b>' + autocompleteArray[i].name + '</b> '
|
||||
+ autocompleteArray[i].type + '</span>';
|
||||
break;
|
||||
case "package":
|
||||
displayText = '<span class="fn-clear"><span class="ico-package ico"></span>'// + autocompleteArray[i].class
|
||||
+ '<b>' + autocompleteArray[i].name + '</b> '
|
||||
displayText = '<span class="fn-clear">'// + autocompleteArray[i].class
|
||||
+ '<b class="fn-left">' + autocompleteArray[i].name + '</b> '
|
||||
+ autocompleteArray[i].type + '</span>';
|
||||
|
||||
break;
|
||||
case "func":
|
||||
displayText = '<span><span class="ico-func ico"></span>'// + autocompleteArray[i].class
|
||||
displayText = '<span>'// + autocompleteArray[i].class
|
||||
+ '<b>' + autocompleteArray[i].name + '</b>'
|
||||
+ autocompleteArray[i].type.substring(4) + '</span>';
|
||||
text += '()';
|
||||
|
||||
break;
|
||||
default:
|
||||
console.warn("Can't handle autocomplete [" + autocompleteArray[i].class + "]");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
autocompleteHints[i] = {
|
||||
displayText: displayText,
|
||||
text: text
|
||||
text: autocompleteArray[i].name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
editor.doc.markClean();
|
||||
$(".edit-panel .tabs .current > span:eq(0)").removeClass("changed");
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
editors.autocompleteMutex = false;
|
||||
}, 20);
|
||||
|
||||
return {list: autocompleteHints, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
|
||||
});
|
||||
|
||||
CodeMirror.commands.autocompleteAfterDot = function (cm) {
|
||||
var mode = cm.getMode();
|
||||
if (mode && "go" !== mode.name) {
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
var token = cm.getTokenAt(cm.getCursor());
|
||||
|
||||
if ("comment" === token.type || "string" === token.type) {
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if (!cm.state.completionActive) {
|
||||
cm.showHint({hint: CodeMirror.hint.go, completeSingle: false});
|
||||
|
@ -438,140 +259,18 @@ var editors = {
|
|||
url: '/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>');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
CodeMirror.commands.copyLinesDown = function (cm) {
|
||||
var content = '',
|
||||
selectoion = cm.listSelections()[0];
|
||||
|
||||
var from = selectoion.anchor,
|
||||
to = selectoion.head;
|
||||
if (from.line > to.line) {
|
||||
from = selectoion.head;
|
||||
to = selectoion.anchor;
|
||||
}
|
||||
|
||||
for (var i = from.line, max = to.line; i <= max; i++) {
|
||||
if (to.ch !== 0 || i !== max) { // 下一行选中为0时,不应添加内容
|
||||
content += '\n' + cm.getLine(i);
|
||||
}
|
||||
}
|
||||
// 下一行选中为0时,应添加到上一行末
|
||||
var replaceToLine = to.line;
|
||||
if (to.ch === 0) {
|
||||
replaceToLine = to.line - 1;
|
||||
}
|
||||
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
|
||||
|
||||
var offset = replaceToLine - from.line + 1;
|
||||
cm.setSelection(CodeMirror.Pos(from.line + offset, from.ch),
|
||||
CodeMirror.Pos(to.line + offset, to.ch));
|
||||
};
|
||||
|
||||
CodeMirror.commands.copyLinesUp = function (cm) {
|
||||
var content = '',
|
||||
selectoion = cm.listSelections()[0];
|
||||
|
||||
var from = selectoion.anchor,
|
||||
to = selectoion.head;
|
||||
if (from.line > to.line) {
|
||||
from = selectoion.head;
|
||||
to = selectoion.anchor;
|
||||
}
|
||||
|
||||
for (var i = from.line, max = to.line; i <= max; i++) {
|
||||
if (to.ch !== 0 || i !== max) { // 下一行选中为0时,不应添加内容
|
||||
content += '\n' + cm.getLine(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 下一行选中为0时,应添加到上一行末
|
||||
var replaceToLine = to.line;
|
||||
if (to.ch === 0) {
|
||||
replaceToLine = to.line - 1;
|
||||
}
|
||||
cm.replaceRange(content, CodeMirror.Pos(replaceToLine));
|
||||
|
||||
cm.setSelection(CodeMirror.Pos(from.line, from.ch),
|
||||
CodeMirror.Pos(to.line, to.ch));
|
||||
};
|
||||
|
||||
CodeMirror.commands.moveLinesUp = function (cm) {
|
||||
var selectoion = cm.listSelections()[0];
|
||||
|
||||
var from = selectoion.anchor,
|
||||
to = selectoion.head;
|
||||
if (from.line > to.line) {
|
||||
from = selectoion.head;
|
||||
to = selectoion.anchor;
|
||||
}
|
||||
|
||||
if (from.line === 0) {
|
||||
return false;
|
||||
}
|
||||
// 下一行选中为0时,应添加到上一行末
|
||||
var replaceToLine = to.line;
|
||||
if (to.ch === 0) {
|
||||
replaceToLine = to.line - 1;
|
||||
}
|
||||
cm.replaceRange('\n' + cm.getLine(from.line - 1), CodeMirror.Pos(replaceToLine));
|
||||
if (from.line === 1) {
|
||||
// 移除第一行的换行
|
||||
cm.replaceRange('', CodeMirror.Pos(0, 0),
|
||||
CodeMirror.Pos(1, 0));
|
||||
} else {
|
||||
cm.replaceRange('', CodeMirror.Pos(from.line - 2, cm.getLine(from.line - 2).length),
|
||||
CodeMirror.Pos(from.line - 1, cm.getLine(from.line - 1).length));
|
||||
}
|
||||
|
||||
cm.setSelection(CodeMirror.Pos(from.line - 1, from.ch),
|
||||
CodeMirror.Pos(to.line - 1, to.ch));
|
||||
};
|
||||
|
||||
CodeMirror.commands.moveLinesDown = function (cm) {
|
||||
var selectoion = cm.listSelections()[0];
|
||||
|
||||
var from = selectoion.anchor,
|
||||
to = selectoion.head;
|
||||
if (from.line > to.line) {
|
||||
from = selectoion.head;
|
||||
to = selectoion.anchor;
|
||||
}
|
||||
|
||||
if (to.line === cm.lastLine()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 下一行选中为0时,应添加到上一行末
|
||||
var replaceToLine = to.line;
|
||||
if (to.ch === 0) {
|
||||
replaceToLine = to.line - 1;
|
||||
}
|
||||
// 把选中的下一行添加到选中区域的上一行
|
||||
if (from.line === 0) {
|
||||
cm.replaceRange(cm.getLine(replaceToLine + 1) + '\n', CodeMirror.Pos(0, 0));
|
||||
} else {
|
||||
cm.replaceRange('\n' + cm.getLine(replaceToLine + 1), CodeMirror.Pos(from.line - 1));
|
||||
}
|
||||
// 删除选中的下一行
|
||||
cm.replaceRange('', CodeMirror.Pos(replaceToLine + 1, cm.getLine(replaceToLine + 1).length),
|
||||
CodeMirror.Pos(replaceToLine + 2, cm.getLine(replaceToLine + 2).length));
|
||||
|
||||
cm.setSelection(CodeMirror.Pos(from.line + 1, from.ch),
|
||||
CodeMirror.Pos(to.line + 1, to.ch));
|
||||
};
|
||||
|
||||
CodeMirror.commands.jumpToDecl = function (cm) {
|
||||
var cur = wide.curEditor.getCursor();
|
||||
|
||||
|
@ -586,18 +285,38 @@ var editors = {
|
|||
url: '/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 cursorLine = data.cursorLine;
|
||||
var cursorCh = data.cursorCh;
|
||||
|
||||
var request = newWideRequest();
|
||||
request.path = data.path;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/file',
|
||||
data: JSON.stringify(request),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (!data.succ) {
|
||||
$("#dialogAlert").dialog("open", data.msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var tId = tree.getTIdByPath(data.path);
|
||||
wide.curNode = tree.fileTree.getNodeByTId(tId);
|
||||
tree.fileTree.selectNode(wide.curNode);
|
||||
|
||||
tree.openFile(wide.curNode, CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1));
|
||||
data.cursorLine = cursorLine;
|
||||
data.cursorCh = cursorCh;
|
||||
editors.newEditor(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -616,52 +335,32 @@ var editors = {
|
|||
url: '/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', '');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
CodeMirror.commands.selectIdentifier = function (cm) {
|
||||
var cur = cm.getCursor();
|
||||
var word = cm.findWordAt(cur);
|
||||
cm.extendSelection(word.anchor, word.head);
|
||||
};
|
||||
},
|
||||
appendSearch: function (data, type, key) {
|
||||
var searcHTML = '<ul class="list">',
|
||||
key = key.toLowerCase();
|
||||
var searcHTML = '<ul>';
|
||||
|
||||
for (var i = 0, ii = data.length; i < ii; i++) {
|
||||
var contents = '',
|
||||
lowerCaseContents = data[i].contents[0].toLowerCase(),
|
||||
matches = lowerCaseContents.split(key),
|
||||
startIndex = 0,
|
||||
endIndex = 0;
|
||||
for (var j = 0, max = matches.length; j < max; j++) {
|
||||
startIndex = endIndex + matches[j].length;
|
||||
endIndex = startIndex + key.length;
|
||||
var keyWord = data[i].contents[0].substring(startIndex, endIndex);
|
||||
if (keyWord !== '') {
|
||||
keyWord = '<b>' + keyWord + '</b>';
|
||||
}
|
||||
contents += data[i].contents[0].substring(startIndex - matches[j].length, startIndex) + keyWord;
|
||||
}
|
||||
var contents = data[i].contents[0],
|
||||
index = contents.indexOf(key);
|
||||
contents = contents.substring(0, index)
|
||||
+ '<b>' + key + '</b>'
|
||||
+ contents.substring(index + key.length);
|
||||
|
||||
searcHTML += '<li title="' + data[i].path + '">'
|
||||
+ contents + " <span class='ft-small'>" + data[i].path
|
||||
+ contents + " <span class='path'>" + data[i].path
|
||||
+ '<i class="position" data-line="'
|
||||
+ data[i].line + '" data-ch="' + data[i].ch + '"> (' + data[i].line + ':'
|
||||
+ data[i].ch + ')</i></span></li>';
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
searcHTML += '<li>' + config.label.search_no_match + '</li>';
|
||||
}
|
||||
searcHTML += '</ul>';
|
||||
|
||||
var $search = $('.bottom-window-group .search'),
|
||||
|
@ -670,7 +369,7 @@ var editors = {
|
|||
title = config.label.search_text;
|
||||
}
|
||||
if ($search.find("ul").length === 0) {
|
||||
bottomGroup.searchTab = new Tabs({
|
||||
wide.searchTab = new Tabs({
|
||||
id: ".bottom-window-group .search",
|
||||
removeAfter: function (id, prevId) {
|
||||
if ($search.find("ul").length === 1) {
|
||||
|
@ -690,17 +389,8 @@ var editors = {
|
|||
tree.openFile(tree.fileTree.getNodeByTId(tId));
|
||||
tree.fileTree.selectNode(wide.curNode);
|
||||
|
||||
var line = $it.find(".position").data("line") - 1;
|
||||
var cursor = CodeMirror.Pos(line, $it.find(".position").data("ch") - 1);
|
||||
|
||||
|
||||
var editor = wide.curEditor;
|
||||
editor.setCursor(cursor);
|
||||
|
||||
var half = Math.floor(editor.getScrollInfo().clientHeight / editor.defaultTextHeight() / 2);
|
||||
var cursorCoords = editor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
|
||||
editor.scrollTo(0, cursorCoords.top);
|
||||
|
||||
var cursor = CodeMirror.Pos($it.find(".position").data("line") - 1, $it.find(".position").data("ch") - 1);
|
||||
wide.curEditor.setCursor(cursor);
|
||||
wide.curEditor.focus();
|
||||
});
|
||||
|
||||
|
@ -709,7 +399,7 @@ var editors = {
|
|||
$search.find(".tabs .first").text(title);
|
||||
} else {
|
||||
$search.find(".tabs").show();
|
||||
bottomGroup.searchTab.add({
|
||||
wide.searchTab.add({
|
||||
"id": "search" + (new Date()).getTime(),
|
||||
"title": title,
|
||||
"content": searcHTML
|
||||
|
@ -717,13 +407,31 @@ var editors = {
|
|||
}
|
||||
|
||||
// focus
|
||||
bottomGroup.tabs.setCurrent("search");
|
||||
wide.bottomWindowTab.setCurrent("search");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .search").focus();
|
||||
},
|
||||
// 新建一个编辑器 Tab,如果已经存在 Tab 则切换到该 Tab.
|
||||
newEditor: function (data, cursor) {
|
||||
var id = wide.curNode.id;
|
||||
newEditor: function (data) {
|
||||
$(".toolbars").show();
|
||||
var id = wide.curNode.tId;
|
||||
|
||||
// 光标位置
|
||||
var cursor = CodeMirror.Pos(0, 0);
|
||||
if (data.cursorLine && data.cursorCh) {
|
||||
cursor = CodeMirror.Pos(data.cursorLine - 1, data.cursorCh - 1);
|
||||
}
|
||||
|
||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
||||
if (editors.data[i].id === id) {
|
||||
editors.tabs.setCurrent(id);
|
||||
wide.curEditor = editors.data[i].editor;
|
||||
wide.curEditor.setCursor(cursor);
|
||||
wide.curEditor.focus();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
editors.tabs.add({
|
||||
id: id,
|
||||
|
@ -732,35 +440,25 @@ var editors = {
|
|||
content: '<textarea id="editor' + id + '"></textarea>'
|
||||
});
|
||||
|
||||
menu.undisabled(['save-all', 'close-all', 'build', 'run', 'go-test', 'go-vet', 'go-mod', 'go-install',
|
||||
'find', 'find-next', 'find-previous', 'replace', 'replace-all',
|
||||
'format', 'autocomplete', 'jump-to-decl', 'expr-info', 'find-usages', 'toggle-comment',
|
||||
'edit']);
|
||||
menu.undisabled(['save-all', 'close-all', 'run', 'go-get', 'go-install']);
|
||||
|
||||
var textArea = document.getElementById("editor" + id);
|
||||
textArea.value = data.content;
|
||||
var rulers = [];
|
||||
rulers.push({color: "#ccc", column: 120, lineStyle: "dashed"});
|
||||
|
||||
var editor = CodeMirror.fromTextArea(textArea, {
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById("editor" + id), {
|
||||
lineNumbers: true,
|
||||
autofocus: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
highlightSelectionMatches: {showToken: /\w/},
|
||||
rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
|
||||
rulers: rulers,
|
||||
styleActiveLine: true,
|
||||
theme: config.editorTheme,
|
||||
tabSize: config.editorTabSize,
|
||||
theme: 'wide',
|
||||
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",
|
||||
"Ctrl-/": 'toggleComment',
|
||||
"Ctrl-I": "exprInfo",
|
||||
"Ctrl-L": "gotoLine",
|
||||
"Ctrl-E": "deleteLine",
|
||||
|
@ -770,7 +468,7 @@ var editors = {
|
|||
wide.saveFile();
|
||||
},
|
||||
"Shift-Ctrl-S": function () {
|
||||
menu.saveAllFiles();
|
||||
wide.saveAllFiles();
|
||||
},
|
||||
"Shift-Alt-F": function () {
|
||||
var currentPath = editors.getCurrentPath();
|
||||
|
@ -787,89 +485,30 @@ var editors = {
|
|||
windows.maxEditor();
|
||||
}
|
||||
},
|
||||
"Shift-Ctrl-Up": "copyLinesUp",
|
||||
"Shift-Ctrl-Down": "copyLinesDown",
|
||||
"Shift-Alt-Up": "moveLinesUp",
|
||||
"Shift-Alt-Down": "moveLinesDown",
|
||||
"Shift-Alt-J": "selectIdentifier"
|
||||
}
|
||||
});
|
||||
|
||||
if ("text/html" === data.mode) {
|
||||
emmetCodeMirror(editor);
|
||||
}
|
||||
|
||||
editor.on('cursorActivity', function (cm) {
|
||||
$(".edit-exprinfo").remove();
|
||||
var cursor = cm.getCursor();
|
||||
|
||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
||||
// TODO: 关闭 tab 的时候要重置
|
||||
});
|
||||
|
||||
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
|
||||
$(".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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
editor.setSize('100%', $(".edit-panel").height() - $(".edit-panel .tabs").height());
|
||||
editor.setValue(data.content);
|
||||
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);
|
||||
}
|
||||
|
@ -878,19 +517,12 @@ var editors = {
|
|||
editor.setOption("autoCloseTags", true);
|
||||
}
|
||||
|
||||
editor.setCursor(cursor);
|
||||
|
||||
wide.curEditor = editor;
|
||||
editors.data.push({
|
||||
"editor": editor,
|
||||
"id": id
|
||||
});
|
||||
|
||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
||||
|
||||
var half = Math.floor(wide.curEditor.getScrollInfo().clientHeight / wide.curEditor.defaultTextHeight() / 2);
|
||||
var cursorCoords = wide.curEditor.cursorCoords({line: cursor.line - half, ch: 0}, "local");
|
||||
wide.curEditor.scrollTo(0, cursorCoords.top);
|
||||
|
||||
editor.setCursor(cursor);
|
||||
editor.focus();
|
||||
}
|
||||
};
|
|
@ -1,154 +1,62 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @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
|
||||
// Ctrl+0 焦点切换到当前编辑器
|
||||
goEditor: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 48,
|
||||
fun: function () {
|
||||
if (wide.curEditor) {
|
||||
wide.curEditor.focus();
|
||||
}
|
||||
}
|
||||
which: 48
|
||||
},
|
||||
// Ctrl-1
|
||||
// Ctrl+1 焦点切换到文件树
|
||||
goFileTree: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 49,
|
||||
fun: function () {
|
||||
// 有些元素需设置 tabindex 为 -1 时才可以 focus
|
||||
if (windows.outerLayout.west.state.isClosed) {
|
||||
windows.outerLayout.slideOpen('west');
|
||||
}
|
||||
$("#files").focus();
|
||||
}
|
||||
which: 49
|
||||
},
|
||||
// Ctrl-2
|
||||
goOutline: {
|
||||
// Ctrl+4 焦点切换到输出窗口
|
||||
goOutPut: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 50,
|
||||
fun: function () {
|
||||
if (windows.innerLayout.east.state.isClosed) {
|
||||
windows.innerLayout.slideOpen('east');
|
||||
}
|
||||
|
||||
$("#outline").focus();
|
||||
}
|
||||
which: 52
|
||||
},
|
||||
// Ctrl-4
|
||||
goOutput: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 52,
|
||||
fun: function () {
|
||||
bottomGroup.tabs.setCurrent("output");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .output").focus();
|
||||
}
|
||||
},
|
||||
// Ctrl-5
|
||||
// Ctrl+5 焦点切换到搜索窗口
|
||||
goSearch: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 53,
|
||||
fun: function () {
|
||||
bottomGroup.tabs.setCurrent("search");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .search").focus();
|
||||
}
|
||||
which: 53
|
||||
},
|
||||
// Ctrl-6
|
||||
// Ctrl+6 焦点切换到通知窗口
|
||||
goNotification: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 54,
|
||||
fun: function () {
|
||||
bottomGroup.tabs.setCurrent("notification");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .notification").focus();
|
||||
}
|
||||
which: 54
|
||||
},
|
||||
// Alt-C
|
||||
clearWindow: {
|
||||
ctrlKey: false,
|
||||
altKey: true,
|
||||
shiftKey: false,
|
||||
which: 67
|
||||
},
|
||||
// Ctrl-D 窗口组切换
|
||||
// Ctrl+D 窗口组切换
|
||||
changeEditor: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 68
|
||||
},
|
||||
// Ctrl-F search
|
||||
// Ctrl+F 搜索
|
||||
search: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 70
|
||||
},
|
||||
// Ctrl-Q close current editor
|
||||
// Ctrl+Q 关闭当前编辑器
|
||||
closeCurEditor: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 81
|
||||
},
|
||||
// Ctrl-R
|
||||
rename: {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 82
|
||||
},
|
||||
// Shift-Alt-O 跳转到文件
|
||||
goFile: {
|
||||
ctrlKey: false,
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
which: 79
|
||||
},
|
||||
// F5 Build
|
||||
build: {
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
which: 116
|
||||
},
|
||||
// F6 Build & Run
|
||||
// F6 构建并运行
|
||||
buildRun: {
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
|
@ -156,132 +64,56 @@ var hotkeys = {
|
|||
which: 117
|
||||
}
|
||||
},
|
||||
bindList: function ($source, $list, enterFun) {
|
||||
$list.data("index", 0);
|
||||
$source.keydown(function (event) {
|
||||
var index = $list.data("index"),
|
||||
count = $list.find("li").length;
|
||||
|
||||
if (count === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.which === 38) { // up
|
||||
index--;
|
||||
if (index < 0) {
|
||||
index = count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.which === 40) { // down
|
||||
index++;
|
||||
if (index > count - 1) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var $selected = $list.find("li:eq(" + index + ")");
|
||||
|
||||
if (event.which === 13) { // enter
|
||||
enterFun($selected);
|
||||
}
|
||||
|
||||
$list.find("li").removeClass("selected");
|
||||
$list.data("index", index);
|
||||
$selected.addClass("selected");
|
||||
|
||||
if (index === 0) {
|
||||
$list.scrollTop(0);
|
||||
} else {
|
||||
if ($selected[0].offsetTop + $list.scrollTop() > $list.height()) {
|
||||
if (event.which === 40) {
|
||||
$list.scrollTop($list.scrollTop() + $selected.height());
|
||||
} else {
|
||||
$list.scrollTop($selected[0].offsetTop);
|
||||
}
|
||||
} else {
|
||||
$list.scrollTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 阻止上下键改变光标位置
|
||||
if (event.which === 38 || event.which === 40 || event.which === 13) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
_bindOutput: function () {
|
||||
$(".bottom-window-group .output").keydown(function (event) {
|
||||
var hotKeys = hotkeys.defaultKeyMap;
|
||||
if (event.altKey === hotKeys.clearWindow.altKey
|
||||
&& event.which === hotKeys.clearWindow.which) { // Alt-C clear output
|
||||
bottomGroup.clear('output');
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
_bindFileTree: function () {
|
||||
$("#files").keydown(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var hotKeys = hotkeys.defaultKeyMap;
|
||||
if (event.ctrlKey === hotKeys.search.ctrlKey
|
||||
&& event.which === hotKeys.search.which) { // Ctrl-F 搜索
|
||||
&& event.which === hotKeys.search.which) { // Ctrl+F 搜索
|
||||
$("#dialogSearchForm").dialog("open");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.rename.ctrlKey
|
||||
&& event.which === hotKeys.rename.which) { // Ctrl-R 重命名
|
||||
if (wide.curNode.removable) {
|
||||
$("#dialogRenamePrompt").dialog("open");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.which) {
|
||||
case 46: // delete
|
||||
case 46: // 删除
|
||||
tree.removeIt();
|
||||
break;
|
||||
case 13: // enter
|
||||
case 13: // 回车
|
||||
if (!wide.curNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tree.isDir()) {
|
||||
if (wide.curNode.open) {
|
||||
if (wide.curNode.iconSkin === "ico-ztree-dir ") { // 选中节点是目录
|
||||
// 不做任何处理
|
||||
return false;
|
||||
}
|
||||
|
||||
tree.fileTree.expandNode(wide.curNode, true, false, true);
|
||||
$("#files").focus();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// 模拟点击:打开文件
|
||||
tree.openFile(wide.curNode);
|
||||
|
||||
break;
|
||||
case 38: // up
|
||||
case 38: // 上
|
||||
var node = {};
|
||||
|
||||
if (!wide.curNode) { // select the first one if no node been selected
|
||||
if (!wide.curNode) { // 没有选中节点时,默认选中第一个
|
||||
node = tree.fileTree.getNodeByTId("files_1");
|
||||
} else {
|
||||
if (wide.curNode && wide.curNode.isFirstNode && wide.curNode.level === 0) {
|
||||
// 当前节点为顶部第一个节点
|
||||
return false;
|
||||
}
|
||||
|
||||
node = wide.curNode.getPreNode();
|
||||
if (wide.curNode.isFirstNode && wide.curNode.getParentNode()) {
|
||||
// 当前节点为第一个节点且有父亲
|
||||
node = wide.curNode.getParentNode();
|
||||
}
|
||||
|
||||
var preNode = wide.curNode.getPreNode();
|
||||
if (preNode && tree.isDir() && preNode.open) {
|
||||
if (preNode && preNode.iconSkin === "ico-ztree-dir "
|
||||
&& preNode.open) {
|
||||
// 当前节点的上一个节点是目录且打开时,获取打开节点中的最后一个节点
|
||||
node = tree.getCurrentNodeLastNode(preNode);
|
||||
}
|
||||
}
|
||||
|
@ -290,23 +122,27 @@ var hotkeys = {
|
|||
tree.fileTree.selectNode(node);
|
||||
$("#files").focus();
|
||||
break;
|
||||
case 40: // down
|
||||
case 40: // 下
|
||||
var node = {};
|
||||
|
||||
if (!wide.curNode) { // select the first one if no node been selected
|
||||
if (!wide.curNode) { // 没有选中节点时,默认选中第一个
|
||||
node = tree.fileTree.getNodeByTId("files_1");
|
||||
} else {
|
||||
if (wide.curNode && tree.isBottomNode(wide.curNode)) {
|
||||
// 当前节点为最底部的节点
|
||||
return false;
|
||||
}
|
||||
|
||||
node = wide.curNode.getNextNode();
|
||||
if (tree.isDir() && wide.curNode.open) {
|
||||
if (wide.curNode.iconSkin === "ico-ztree-dir " && wide.curNode.open) {
|
||||
// 当前节点是目录且打开时
|
||||
node = wide.curNode.children[0];
|
||||
}
|
||||
|
||||
var nextShowNode = tree.getNextShowNode(wide.curNode);
|
||||
if (wide.curNode.isLastNode && wide.curNode.level !== 0 && !wide.curNode.open && nextShowNode) {
|
||||
if (wide.curNode.isLastNode && wide.curNode.level !== 0 && !wide.curNode.open
|
||||
&& nextShowNode) {
|
||||
// 当前节点为最后一个叶子节点,但其父或祖先节点还有下一个节点
|
||||
node = nextShowNode;
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +154,7 @@ var hotkeys = {
|
|||
|
||||
$("#files").focus();
|
||||
break;
|
||||
case 37: // left
|
||||
case 37: // 左
|
||||
if (!wide.curNode) {
|
||||
wide.curNode = tree.fileTree.getNodeByTId("files_1");
|
||||
tree.fileTree.selectNode(wide.curNode);
|
||||
|
@ -326,14 +162,14 @@ var hotkeys = {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!tree.isDir() || !wide.curNode.open) {
|
||||
if (wide.curNode.iconSkin !== "ico-ztree-dir " || !wide.curNode.open) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tree.fileTree.expandNode(wide.curNode, false, false, true);
|
||||
$("#files").focus();
|
||||
break;
|
||||
case 39: // right
|
||||
case 39: // 右
|
||||
if (!wide.curNode) {
|
||||
wide.curNode = tree.fileTree.getNodeByTId("files_1");
|
||||
tree.fileTree.selectNode(wide.curNode);
|
||||
|
@ -341,104 +177,114 @@ var hotkeys = {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!tree.isDir() || wide.curNode.open) {
|
||||
if (wide.curNode.iconSkin !== "ico-ztree-dir " || wide.curNode.open) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tree.fileTree.expandNode(wide.curNode, true, false, true);
|
||||
$("#files").focus();
|
||||
|
||||
break;
|
||||
case 116: // F5
|
||||
if (!wide.curNode || !tree.isDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tree.refresh(wide.curNode);
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
_bindDocument: function () {
|
||||
init: function () {
|
||||
this._bindFileTree();
|
||||
|
||||
var hotKeys = this.defaultKeyMap;
|
||||
$(document).keydown(function (event) {
|
||||
if (event.ctrlKey === hotKeys.goEditor.ctrlKey
|
||||
&& event.which === hotKeys.goEditor.which) { // Ctrl-0 焦点切换到当前编辑器
|
||||
hotKeys.goEditor.fun();
|
||||
&& event.which === hotKeys.goEditor.which) { // Ctrl+0 焦点切换到当前编辑器
|
||||
if (wide.curEditor) {
|
||||
wide.curEditor.focus();
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goFileTree.ctrlKey
|
||||
&& event.which === hotKeys.goFileTree.which) { // Ctrl-1 焦点切换到文件树
|
||||
hotKeys.goFileTree.fun();
|
||||
&& event.which === hotKeys.goFileTree.which) { // Ctrl+1 焦点切换到文件树
|
||||
// 有些元素需设置 tabindex 为 -1 时才可以 focus
|
||||
if ($(".footer .ico-restore:eq(0)").css("display") === "inline") {
|
||||
// 当文件树最小化时
|
||||
$(".side").css({
|
||||
"left": "0"
|
||||
});
|
||||
|
||||
if ($(".footer .ico-restore:eq(1)").css("display") === "inline") {
|
||||
// 当底部最小化时
|
||||
$(".bottom-window-group").css("top", "100%").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$("#files").focus();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goOutline.ctrlKey
|
||||
&& event.which === hotKeys.goOutline.which) { // Ctrl-2 焦点切换到大纲
|
||||
hotKeys.goOutline.fun();
|
||||
if (event.ctrlKey === hotKeys.goOutPut.ctrlKey
|
||||
&& event.which === hotKeys.goOutPut.which) { // Ctrl+4 焦点切换到输出窗口
|
||||
wide.bottomWindowTab.setCurrent("output");
|
||||
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .output").focus();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goOutput.ctrlKey
|
||||
&& event.which === hotKeys.goOutput.which) { // Ctrl-4 焦点切换到输出窗口
|
||||
hotKeys.goOutput.fun();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goSearch.ctrlKey
|
||||
&& event.which === hotKeys.goSearch.which) { // Ctrl-5 焦点切换到搜索窗口
|
||||
hotKeys.goSearch.fun();
|
||||
&& event.which === hotKeys.goSearch.which) { // Ctrl+5 焦点切换到搜索窗口
|
||||
wide.bottomWindowTab.setCurrent("search");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .search").focus();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goNotification.ctrlKey
|
||||
&& event.which === hotKeys.goNotification.which) { // Ctrl-6 焦点切换到通知窗口
|
||||
hotKeys.goNotification.fun();
|
||||
&& event.which === hotKeys.goNotification.which) { // Ctrl+6 焦点切换到通知窗口
|
||||
wide.bottomWindowTab.setCurrent("notification");
|
||||
windows.flowBottom();
|
||||
$(".bottom-window-group .notification").focus();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.closeCurEditor.ctrlKey
|
||||
&& event.which === hotKeys.closeCurEditor.which) { // Ctrl-Q 关闭当前编辑器
|
||||
$(".edit-panel .tabs > div.current").find(".ico-close").click();
|
||||
&& event.which === hotKeys.closeCurEditor.which) { // Ctrl+Q 关闭当前编辑器
|
||||
var currentId = editors.getCurrentId();
|
||||
if (currentId) {
|
||||
editors.tabs.del(currentId);
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.changeEditor.ctrlKey
|
||||
&& event.which === hotKeys.changeEditor.which) { // Ctrl-D 窗口组切换
|
||||
&& event.which === hotKeys.changeEditor.which) { // Ctrl+D 窗口组切换
|
||||
if (document.activeElement.className === "notification"
|
||||
|| document.activeElement.className === "output"
|
||||
|| 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();
|
||||
wide.bottomWindowTab.setCurrent(nextId);
|
||||
$(".bottom-window-group ." + nextId).focus();
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -446,16 +292,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,14 +309,10 @@ 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();
|
||||
$(".footer .cursor").text('| ' + (cursor.line + 1) + ':' + (cursor.ch + 1) + ' |');
|
||||
|
||||
wide.curEditor.focus();
|
||||
}
|
||||
|
||||
|
@ -479,31 +321,12 @@ var hotkeys = {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (event.which === hotKeys.build.which) { // F5 Build
|
||||
menu.build();
|
||||
if (event.which === hotKeys.buildRun.which) { // F6 构建并运行
|
||||
wide.run();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.which === hotKeys.buildRun.which) { // F6 Build & Run
|
||||
menu.run();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey === hotKeys.goFile.ctrlKey
|
||||
&& event.altKey === hotKeys.goFile.altKey
|
||||
&& event.shiftKey === hotKeys.goFile.shiftKey
|
||||
&& event.which === hotKeys.goFile.which) { // Shift-Alt-O 跳转到文件
|
||||
$("#dialogGoFilePrompt").dialog("open");
|
||||
}
|
||||
});
|
||||
},
|
||||
init: function () {
|
||||
this._bindFileTree();
|
||||
this._bindOutput();
|
||||
this._bindDocument();
|
||||
}
|
||||
};
|
|
@ -109,7 +109,7 @@
|
|||
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
|
||||
var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end);
|
||||
|
||||
// Try finding line comments
|
||||
var lineString = options.lineComment || mode.lineComment, lines = [];
|
|
@ -56,12 +56,7 @@
|
|||
|
||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||
if (inp) {
|
||||
if (options.value) {
|
||||
inp.value = options.value;
|
||||
if (options.selectValueOnOpen !== false) {
|
||||
inp.select();
|
||||
}
|
||||
}
|
||||
if (options.value) inp.value = options.value;
|
||||
|
||||
if (options.onInput)
|
||||
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
||||
|
@ -75,7 +70,7 @@
|
|||
CodeMirror.e_stop(e);
|
||||
close();
|
||||
}
|
||||
if (e.keyCode == 13) callback(inp.value, e);
|
||||
if (e.keyCode == 13) callback(inp.value);
|
||||
});
|
||||
|
||||
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
||||
|
@ -134,8 +129,8 @@
|
|||
CodeMirror.defineExtension("openNotification", function(template, options) {
|
||||
closeNotification(this, close);
|
||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||
var duration = options && (options.duration === undefined ? 5000 : options.duration);
|
||||
var closed = false, doneTimer;
|
||||
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
||||
|
||||
function close() {
|
||||
if (closed) return;
|
||||
|
@ -148,10 +143,7 @@
|
|||
CodeMirror.e_preventDefault(e);
|
||||
close();
|
||||
});
|
||||
|
||||
if (duration)
|
||||
doneTimer = setTimeout(close, duration);
|
||||
|
||||
return close;
|
||||
doneTimer = setTimeout(close, options.duration);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,158 @@
|
|||
// 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) {
|
||||
if (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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
|
@ -94,33 +94,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
// work for other XML embedded languages (there is no general
|
||||
// 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>";
|
||||
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
|
||||
replacements[i] = head + "style>";
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
} else {
|
||||
if (!state.context || !state.context.tagName ||
|
||||
if (tok.type == "string" || tok.string.charAt(0) != "<" ||
|
||||
tok.start != pos.ch - 1 || inner.mode.name != "xml" ||
|
||||
!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);
|
||||
ranges = cm.listSelections();
|
||||
|
@ -129,13 +115,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)
|
|
@ -0,0 +1,38 @@
|
|||
// 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) {
|
||||
"use strict";
|
||||
|
||||
var listRE = /^(\s*)([*+-]|(\d+)\.)(\s+)/,
|
||||
unorderedBullets = "*+-";
|
||||
|
||||
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
|
||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
var ranges = cm.listSelections(), replacements = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var pos = ranges[i].head, match;
|
||||
var inList = cm.getStateAfter(pos.line).list !== false;
|
||||
|
||||
if (!ranges[i].empty() || !inList || !(match = cm.getLine(pos.line).match(listRE))) {
|
||||
cm.execCommand("newlineAndIndent");
|
||||
return;
|
||||
}
|
||||
var indent = match[1], after = match[4];
|
||||
var bullet = unorderedBullets.indexOf(match[2]) >= 0
|
||||
? match[2]
|
||||
: (parseInt(match[3], 10) + 1) + ".";
|
||||
|
||||
replacements[i] = "\n" + indent + bullet + after;
|
||||
}
|
||||
|
||||
cm.replaceSelections(replacements);
|
||||
};
|
||||
});
|
|
@ -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() {
|