forked from x/icebergs
opt website
This commit is contained in:
parent
03aaa69c3d
commit
16b8cb1ded
@ -158,7 +158,7 @@ func init() {
|
||||
Index.Merge(&ice.Context{Configs: map[string]*ice.Config{
|
||||
RUNTIME: {Name: RUNTIME, Help: "运行环境", Value: kit.Dict()},
|
||||
}, Commands: map[string]*ice.Command{
|
||||
RUNTIME: {Name: "runtime info=ifconfig,hostinfo,hostname,userinfo,procinfo,bootinfo,diskinfo,env auto", Help: "运行环境", Action: map[string]*ice.Action{
|
||||
RUNTIME: {Name: "runtime info=ifconfig,hostinfo,hostname,userinfo,procinfo,bootinfo,diskinfo,env,file,route auto", Help: "运行环境", Action: map[string]*ice.Action{
|
||||
ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) {
|
||||
_runtime_init(m)
|
||||
m.Cmd(RUNTIME, MAXPROCS, "1")
|
||||
@ -202,6 +202,20 @@ func init() {
|
||||
m.Push(mdb.VALUE, ls[1])
|
||||
}
|
||||
}},
|
||||
"file": {Name: "file", Help: "模块文件", Hand: func(m *ice.Message, arg ...string) {
|
||||
for k, v := range ice.Info.File {
|
||||
m.Push(nfs.FILE, k)
|
||||
m.Push(mdb.NAME, v)
|
||||
}
|
||||
m.Sort(nfs.FILE)
|
||||
}},
|
||||
"route": {Name: "route", Help: "接口命令", Hand: func(m *ice.Message, arg ...string) {
|
||||
for k, v := range ice.Info.Route {
|
||||
m.Push(nfs.PATH, k)
|
||||
m.Push(nfs.FILE, v)
|
||||
}
|
||||
m.Sort(nfs.PATH)
|
||||
}},
|
||||
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if len(arg) > 0 && arg[0] == BOOTINFO {
|
||||
arg = arg[1:]
|
||||
|
@ -69,10 +69,11 @@ func CmdAction(fields ...string) map[string]*ice.Action {
|
||||
}
|
||||
|
||||
const (
|
||||
ACTION = "action"
|
||||
INDEX = "index"
|
||||
ARGS = "args"
|
||||
STYLE = "style"
|
||||
ACTION = "action"
|
||||
INDEX = "index"
|
||||
ARGS = "args"
|
||||
STYLE = "style"
|
||||
DISPLAY = "display"
|
||||
)
|
||||
const COMMAND = "command"
|
||||
|
||||
|
@ -143,9 +143,9 @@ func AutoConfig(args ...interface{}) *ice.Action {
|
||||
return &ice.Action{Hand: func(m *ice.Message, arg ...string) {
|
||||
if cs := m.Target().Configs; cs[m.CommandKey()] == nil {
|
||||
cs[m.CommandKey()] = &ice.Config{Value: kit.Data(args...)}
|
||||
m.Load(m.CommandKey())
|
||||
}
|
||||
}}
|
||||
|
||||
}
|
||||
func HashAction(args ...interface{}) map[string]*ice.Action {
|
||||
_key := func(m *ice.Message) string {
|
||||
|
@ -72,9 +72,7 @@ func _link_file(m *ice.Message, name string, from string) {
|
||||
os.Remove(name)
|
||||
MkdirAll(m, path.Dir(name))
|
||||
if e := os.Link(from, name); e != nil {
|
||||
m.Debug("what %v", e)
|
||||
m.Warn(os.Symlink(from, name), ice.ErrFailure, from)
|
||||
m.Debug("what %v", e)
|
||||
}
|
||||
m.Echo(name)
|
||||
}
|
||||
|
@ -337,6 +337,16 @@ func init() {
|
||||
"/volcanos/": {Name: "/volcanos/", Help: "浏览器", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
m.RenderIndex(SERVE, ice.VOLCANOS, arg...)
|
||||
}},
|
||||
"/require/src/": {Name: "/require/src/", Help: "代码库", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if p := path.Join(ice.SRC, path.Join(arg...)); m.Option(ice.POD) != "" {
|
||||
m.RenderResult(m.Cmdx(SPACE, m.Option(ice.POD), nfs.CAT, p))
|
||||
} else {
|
||||
m.RenderDownload(p)
|
||||
}
|
||||
}},
|
||||
"/require/usr/": {Name: "/require/usr/", Help: "代码库", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
m.RenderDownload(path.Join(ice.USR, path.Join(arg...)))
|
||||
}},
|
||||
"/require/": {Name: "/require/", Help: "代码库", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
_share_repos(m, path.Join(arg[0], arg[1], arg[2]), arg[3:]...)
|
||||
}},
|
||||
|
@ -234,10 +234,8 @@ func _space_fork(m *ice.Message) {
|
||||
msg.Sleep300ms(SPACE, name, cli.PWD, name, link, msg.Cmdx(cli.QRCODE, link))
|
||||
case "sso":
|
||||
link := _space_domain(m)
|
||||
m.Debug("what %v", link)
|
||||
ls := strings.Split(kit.ParseURL(link).Path, ice.PS)
|
||||
link = kit.MergeURL2(_space_domain(m), "/chat/sso", "space", kit.Select("", ls, 3), "back", m.Option(ice.MSG_USERWEB))
|
||||
m.Debug("what %v", link)
|
||||
msg.Sleep300ms(SPACE, name, cli.PWD, name, link, msg.Cmdx(cli.QRCODE, link))
|
||||
default:
|
||||
msg.Sleep300ms(SPACE, name, cli.PWD, name)
|
||||
|
@ -35,6 +35,7 @@ func (web *Frame) Begin(m *ice.Message, arg ...string) ice.Server {
|
||||
return web
|
||||
}
|
||||
func (web *Frame) Start(m *ice.Message, arg ...string) bool {
|
||||
list := map[*ice.Context]string{}
|
||||
m.Travel(func(p *ice.Context, s *ice.Context) {
|
||||
if frame, ok := s.Server().(*Frame); ok {
|
||||
if frame.ServeMux != nil {
|
||||
@ -48,6 +49,7 @@ func (web *Frame) Start(m *ice.Message, arg ...string) bool {
|
||||
route := ice.PS + s.Name + ice.PS
|
||||
msg.Log(ROUTE, "%s <= %s", p.Name, route)
|
||||
pframe.Handle(route, http.StripPrefix(path.Dir(route), frame))
|
||||
list[s] = path.Join(list[p], route)
|
||||
}
|
||||
|
||||
// 静态路由
|
||||
@ -62,6 +64,8 @@ func (web *Frame) Start(m *ice.Message, arg ...string) bool {
|
||||
return
|
||||
}
|
||||
msg.Log(ROUTE, "%s <- %s", s.Name, k)
|
||||
m.Debug("what %v", path.Join(list[s], k))
|
||||
ice.Info.Route[path.Join(list[s], k)] = kit.FileLine(x.Hand, 3)
|
||||
frame.HandleFunc(k, func(frame http.ResponseWriter, r *http.Request) {
|
||||
m.TryCatch(msg.Spawn(), true, func(msg *ice.Message) {
|
||||
_serve_handle(k, x, msg, frame, r)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
ice "shylinux.com/x/icebergs"
|
||||
"shylinux.com/x/icebergs/base/ctx"
|
||||
"shylinux.com/x/icebergs/base/nfs"
|
||||
"shylinux.com/x/icebergs/base/web"
|
||||
kit "shylinux.com/x/toolkits"
|
||||
)
|
||||
@ -24,7 +25,11 @@ func init() {
|
||||
return // 节点列表
|
||||
}
|
||||
if len(arg) == 1 {
|
||||
m.RenderIndex(web.SERVE, ice.VOLCANOS)
|
||||
if s := m.Cmdx(web.SPACE, arg[0], nfs.CAT, "/page/index.html"); s != "" {
|
||||
m.RenderResult(s)
|
||||
} else {
|
||||
m.RenderIndex(web.SERVE, ice.VOLCANOS)
|
||||
}
|
||||
return // 节点首页
|
||||
}
|
||||
if arg[1] == WEBSITE {
|
||||
|
@ -22,7 +22,7 @@ func _website_url(m *ice.Message, file string) string {
|
||||
}
|
||||
return strings.Split(kit.MergeURL2(m.Option(ice.MSG_USERWEB), path.Join("/chat", p)), "?")[0]
|
||||
}
|
||||
func _website_parse(m *ice.Message, text string) (map[string]interface{}, bool) {
|
||||
func _website_parse(m *ice.Message, text string, args ...string) (map[string]interface{}, bool) {
|
||||
if text == "" {
|
||||
return nil, false
|
||||
}
|
||||
@ -32,16 +32,52 @@ func _website_parse(m *ice.Message, text string) (map[string]interface{}, bool)
|
||||
"River", kit.Dict("menus", kit.List(), "action", kit.List()),
|
||||
"Action", kit.Dict("menus", kit.List(), "action", kit.List(), "legend_event", "onclick"),
|
||||
"Footer", kit.Dict("style", kit.Dict("display", "none")),
|
||||
args,
|
||||
), kit.Dict(), kit.Dict()
|
||||
m.Cmd(lex.SPLIT, "", mdb.KEY, mdb.NAME, func(deep int, ls []string, meta map[string]interface{}) []string {
|
||||
if len(ls) == 1 {
|
||||
ls = append(ls, ls[0])
|
||||
}
|
||||
data := kit.Dict()
|
||||
switch display := ice.DisplayRequire(1, ls[0])[ctx.DISPLAY]; kit.Ext(ls[0]) {
|
||||
case nfs.JS:
|
||||
key := ice.GetFileKey(display)
|
||||
if key == "" {
|
||||
if ls := strings.Split(display, ice.PS); len(ls) > 4 {
|
||||
ls[3] = ice.USR
|
||||
key = ice.GetFileKey(path.Join(ls[3:]...))
|
||||
}
|
||||
}
|
||||
if key == "" {
|
||||
for p, k := range ice.Info.File {
|
||||
if strings.HasPrefix(p, path.Dir(display)) {
|
||||
key = k
|
||||
}
|
||||
}
|
||||
}
|
||||
ls[0] = kit.Select("can.code.inner.plugin", key)
|
||||
data[ctx.DISPLAY] = display
|
||||
case nfs.GO:
|
||||
ls[0] = ice.GetFileKey(display)
|
||||
case nfs.SH:
|
||||
ls[0] = ice.GetFileKey(display)
|
||||
data[ctx.DISPLAY] = display
|
||||
}
|
||||
|
||||
if ls[0] == "" {
|
||||
return ls
|
||||
} else if len(ls) == 1 && deep == 3 {
|
||||
ls = append(ls, m.Cmd(ctx.COMMAND, ls[0]).Append(mdb.HELP))
|
||||
} else if len(ls) == 1 {
|
||||
ls = append(ls, ls[0])
|
||||
} else if ls[1] == "" {
|
||||
ls[1] = ls[0]
|
||||
}
|
||||
|
||||
for i := 2; i < len(ls); i += 2 {
|
||||
switch ls[i] {
|
||||
case ctx.ARGS:
|
||||
data[ls[i]] = kit.Split(ls[i+1])
|
||||
case ctx.DISPLAY:
|
||||
data[ls[i]] = ice.DisplayRequire(1, ls[i+1])[ctx.DISPLAY]
|
||||
|
||||
case "title", "menus", "action", "style":
|
||||
data[ls[i]] = kit.UnMarshal(ls[i+1])
|
||||
default:
|
||||
@ -121,7 +157,7 @@ func init() {
|
||||
})
|
||||
}},
|
||||
"show": {Hand: func(m *ice.Message, arg ...string) {
|
||||
if res, ok := _website_parse(m, m.Cmdx(nfs.CAT, path.Join(SRC_WEBSITE, arg[0]))); ok {
|
||||
if res, ok := _website_parse(m, m.Cmdx(nfs.CAT, path.Join(SRC_WEBSITE, arg[0])), arg[1:]...); ok {
|
||||
m.Echo(_website_template2, kit.Format(res))
|
||||
}
|
||||
}},
|
||||
|
@ -75,6 +75,11 @@ func init() {
|
||||
if kit.FileExists(path.Join(ice.USR_VOLCANOS, ice.PROTO_JS)) {
|
||||
m.Cmd(BINPACK, mdb.REMOVE)
|
||||
}
|
||||
if kit.FileExists("src/website/index.txt") {
|
||||
if s := m.Cmdx("web.chat.website", "show", "index.txt", "Header.style.display", "block"); s != "" {
|
||||
ice.Info.Pack["/page/index.html"] = []byte(s)
|
||||
}
|
||||
}
|
||||
web.AddRewrite(func(w http.ResponseWriter, r *http.Request) bool {
|
||||
if len(ice.Info.Pack) == 0 {
|
||||
return false
|
||||
|
@ -220,18 +220,22 @@ func init() {
|
||||
"package", "import", "type", "struct", "interface", "const", "var", "func",
|
||||
"if", "else", "for", "range", "break", "continue",
|
||||
"switch", "case", "default", "fallthrough",
|
||||
"go", "select", "defer", "return", "panic", "recover",
|
||||
"go", "select", "defer", "return",
|
||||
),
|
||||
CONSTANT, kit.Simple(
|
||||
"false", "true", "nil", "-1", "0", "1", "2",
|
||||
"false", "true", "nil", "iota", "-1", "0", "1", "2", "3",
|
||||
),
|
||||
DATATYPE, kit.Simple(
|
||||
"int", "int32", "int64", "float64",
|
||||
"string", "byte", "bool", "error", "chan", "map",
|
||||
"int", "int8", "int16", "int32", "int64",
|
||||
"uint", "uint8", "uint16", "uint32", "uint64",
|
||||
"float32", "float64", "complex64", "complex128",
|
||||
"rune", "string", "byte", "uintptr",
|
||||
"bool", "error", "chan", "map",
|
||||
),
|
||||
FUNCTION, kit.Simple(
|
||||
"init", "main", "print",
|
||||
"new", "make", "len", "cap", "copy", "append", "delete", "msg", "m",
|
||||
FUNCTION, kit.Simple("msg", "m",
|
||||
"init", "main", "print", "println", "panic", "recover",
|
||||
"new", "make", "len", "cap", "copy", "append", "delete", "close",
|
||||
"complex", "real", "imag",
|
||||
),
|
||||
), KEYWORD, kit.Dict(),
|
||||
))},
|
||||
|
@ -57,7 +57,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
m.Display(path.Join("/require", ice.Info.Make.Module, path.Join(arg[2], arg[1])))
|
||||
m.Display(path.Join("/require", path.Join(arg[2], arg[1])))
|
||||
m.ProcessCommand(kit.Select("can.code.inner.plugin", key), kit.Simple())
|
||||
}},
|
||||
mdb.ENGINE: {Hand: func(m *ice.Message, arg ...string) {
|
||||
|
@ -32,8 +32,6 @@ func init() {
|
||||
m.Cmdy(cli.SYSTEM, PYTHON2, kit.Path(arg[2], arg[1]))
|
||||
} else if b, ok := ice.Info.Pack[path.Join(arg[2], arg[1])]; ok && len(b) > 0 {
|
||||
m.Cmdy(cli.SYSTEM, PYTHON2, "-c", string(b))
|
||||
} else {
|
||||
m.Debug("what %v %v", b, ok)
|
||||
}
|
||||
m.Echo(ice.NL)
|
||||
}},
|
||||
|
@ -14,14 +14,14 @@ const TEMPLATE = "template"
|
||||
|
||||
func init() {
|
||||
Index.Merge(&ice.Context{Commands: map[string]*ice.Command{
|
||||
TEMPLATE: {Name: "template name auto create", Help: "模板", Action: ice.MergeAction(map[string]*ice.Action{
|
||||
TEMPLATE: {Name: "template name auto", Help: "模板", Action: ice.MergeAction(map[string]*ice.Action{
|
||||
ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) {
|
||||
for _, _template := range _template_list {
|
||||
m.Cmd(TEMPLATE, mdb.CREATE, kit.SimpleKV(kit.Format(_template[0]), _template[1:]...))
|
||||
}
|
||||
}},
|
||||
mdb.CREATE: {Name: "create type name text args", Help: "创建"},
|
||||
nfs.DEFS: {Name: "defs file=hi/hi.go", Help: "生成", Hand: func(m *ice.Message, arg ...string) {
|
||||
nfs.DEFS: {Name: "defs file=hi/hi.js", Help: "生成", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Option("tags", "`"+m.Option("tags")+"`")
|
||||
if buf, err := kit.Render(m.Option(mdb.TEXT), m); !m.Warn(err) {
|
||||
switch m.Cmd(nfs.DEFS, path.Join(m.Option(nfs.PATH), m.Option(nfs.FILE)), string(buf)); kit.Ext(m.Option(nfs.FILE)) {
|
||||
@ -33,7 +33,10 @@ func init() {
|
||||
}
|
||||
}},
|
||||
}, mdb.HashAction(mdb.SHORT, mdb.NAME, mdb.FIELD, "time,type,name,text,args")), Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
mdb.HashSelect(m, arg...).Sort(mdb.NAME).Cut("time,action,type,name,text,args")
|
||||
if mdb.HashSelect(m, arg...).Sort(mdb.NAME); len(arg) == 0 {
|
||||
m.Cut("time,action,type,name,text,args")
|
||||
m.Action(mdb.CREATE)
|
||||
}
|
||||
m.PushAction(nfs.DEFS, mdb.REMOVE)
|
||||
}}},
|
||||
})
|
||||
|
@ -21,6 +21,9 @@ func init() {
|
||||
AUTOGEN: {Name: "create main=src/main.go zone name=hi help type=Zone,Hash,Lists,Data,Code list key", Help: "模块", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Cmdy(AUTOGEN, mdb.CREATE, arg)
|
||||
}},
|
||||
"script": {Name: "script file=hi/hi.js text=", Help: "脚本", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Cmdy(TEMPLATE, nfs.DEFS)
|
||||
}},
|
||||
COMPILE: {Name: "compile", Help: "编译", Hand: func(m *ice.Message, arg ...string) {
|
||||
if msg := m.Cmd(COMPILE, ice.SRC_MAIN_GO, ice.BIN_ICE_BIN); !cli.IsSuccess(msg) {
|
||||
_inner_make(m, msg)
|
||||
|
35
info.go
35
info.go
@ -2,6 +2,7 @@ package ice
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
kit "shylinux.com/x/toolkits"
|
||||
@ -33,10 +34,11 @@ var Info = struct {
|
||||
|
||||
Make MakeInfo
|
||||
|
||||
Help string
|
||||
Pack map[string][]byte
|
||||
File map[string]string
|
||||
Log func(m *Message, p, l, s string)
|
||||
Help string
|
||||
Pack map[string][]byte
|
||||
File map[string]string
|
||||
Route map[string]string
|
||||
Log func(m *Message, p, l, s string)
|
||||
|
||||
render map[string]func(*Message, string, ...interface{}) string
|
||||
names map[string]interface{}
|
||||
@ -49,18 +51,35 @@ report: shylinuxc@gmail.com
|
||||
server: https://shylinux.com
|
||||
source: https://shylinux.com/x/icebergs
|
||||
`,
|
||||
Pack: map[string][]byte{},
|
||||
File: map[string]string{},
|
||||
Pack: map[string][]byte{},
|
||||
File: map[string]string{},
|
||||
Route: map[string]string{},
|
||||
|
||||
render: map[string]func(*Message, string, ...interface{}) string{},
|
||||
names: map[string]interface{}{},
|
||||
}
|
||||
|
||||
func fileKey(dir string) string {
|
||||
dir = strings.Split(dir, DF)[0]
|
||||
dir = strings.ReplaceAll(dir, ".js", ".go")
|
||||
dir = strings.ReplaceAll(dir, ".sh", ".go")
|
||||
if strings.Contains(dir, "go/pkg/mod") {
|
||||
return path.Join("/require", strings.Split(dir, "go/pkg/mod")[1])
|
||||
}
|
||||
dir = strings.TrimPrefix(dir, kit.Path("")+PS)
|
||||
if strings.HasPrefix(dir, SRC) {
|
||||
return path.Join("/require", dir)
|
||||
}
|
||||
if strings.HasPrefix(dir, USR) {
|
||||
return path.Join("/require", dir)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
func AddFileKey(dir, key string) {
|
||||
Info.File[strings.TrimPrefix(dir, kit.Path("")+PS)] = key
|
||||
Info.File[fileKey(dir)] = key
|
||||
}
|
||||
func GetFileKey(dir string) string {
|
||||
return Info.File[strings.TrimPrefix(dir, kit.Path("")+PS)]
|
||||
return Info.File[fileKey(dir)]
|
||||
}
|
||||
func Dump(w io.Writer, name string, cb func(string)) bool {
|
||||
for _, key := range []string{name, strings.TrimPrefix(name, USR_VOLCANOS)} {
|
||||
|
2
logs.go
2
logs.go
@ -172,7 +172,7 @@ func (m *Message) FormatsMeta() string {
|
||||
}
|
||||
func (m *Message) FormatStack() string {
|
||||
pc := make([]uintptr, 100)
|
||||
frames := runtime.CallersFrames(pc[:runtime.Callers(5, pc)])
|
||||
frames := runtime.CallersFrames(pc[:runtime.Callers(2, pc)])
|
||||
|
||||
meta := []string{}
|
||||
for {
|
||||
|
7
misc.go
7
misc.go
@ -369,6 +369,13 @@ func MergeAction(list ...interface{}) map[string]*Action {
|
||||
base[k] = v
|
||||
} else if h.Hand == nil {
|
||||
h.Hand = v.Hand
|
||||
} else if k == CTX_INIT {
|
||||
last := base[k].Hand
|
||||
prev := v.Hand
|
||||
base[k].Hand = func(m *Message, arg ...string) {
|
||||
prev(m, arg...)
|
||||
last(m, arg...)
|
||||
}
|
||||
}
|
||||
}
|
||||
case string:
|
||||
|
@ -62,6 +62,11 @@ func init() {
|
||||
m.Option(mdb.NAME, kit.Select(strings.TrimSuffix(path.Base(m.Option(REPOS)), ".git"), m.Option(mdb.NAME)))
|
||||
m.Option(nfs.PATH, kit.Select(path.Join(ice.USR, m.Option(mdb.NAME)), m.Option(nfs.PATH)))
|
||||
m.Option(REPOS, kit.Select(m.Config(REPOS)+ice.PS+m.Option(mdb.NAME), m.Option(REPOS)))
|
||||
if strings.Contains(m.Option(REPOS), "@") {
|
||||
ls := strings.Split(m.Option(REPOS), "@")
|
||||
m.Option(REPOS, ls[0])
|
||||
m.Option(BRANCH, ls[1])
|
||||
}
|
||||
|
||||
if s, e := os.Stat(path.Join(m.Option(nfs.PATH), ".git")); e == nil && s.IsDir() {
|
||||
return
|
||||
|
@ -272,7 +272,11 @@ func DisplayRequire(n int, file string, arg ...string) map[string]string {
|
||||
file = kit.Keys(kit.FileName(n+1), JS)
|
||||
}
|
||||
if !strings.HasPrefix(file, HTTP) && !strings.HasPrefix(file, PS) {
|
||||
file = path.Join(PS, REQUIRE, kit.ModPath(n+1, file))
|
||||
if kit.FileExists("src/" + file) {
|
||||
file = path.Join(PS, REQUIRE, "src/", file)
|
||||
} else {
|
||||
file = path.Join(PS, REQUIRE, kit.ModPath(n+1, file))
|
||||
}
|
||||
}
|
||||
return DisplayBase(file, arg...)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user