mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
Allow unattaching
This commit is contained in:
parent
36ecf44a2e
commit
0341fa6440
@ -24,6 +24,7 @@ const (
|
||||
TypeEditBinary
|
||||
TypeDeleteHypha
|
||||
TypeRenameHypha
|
||||
TypeUnattachHypha
|
||||
)
|
||||
|
||||
// HistoryOp is an object representing a history operation.
|
||||
|
@ -16,11 +16,65 @@ func init() {
|
||||
http.HandleFunc("/edit/", handlerEdit)
|
||||
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
||||
http.HandleFunc("/rename-ask/", handlerRenameAsk)
|
||||
http.HandleFunc("/unattach-ask/", handlerUnattachAsk)
|
||||
// And those that do mutate something:
|
||||
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
||||
http.HandleFunc("/upload-text/", handlerUploadText)
|
||||
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
||||
http.HandleFunc("/rename-confirm/", handlerRenameConfirm)
|
||||
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
||||
}
|
||||
|
||||
func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "unattach-ask")
|
||||
hd, isOld = HyphaStorage[hyphaName]
|
||||
hasAmnt = hd != nil && hd.binaryPath != ""
|
||||
)
|
||||
if !hasAmnt {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
||||
log.Println("Rejected (no amnt):", rq.URL)
|
||||
return
|
||||
} else if ok := user.CanProceed(rq, "unattach-confirm"); !ok {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
||||
log.Println("Rejected (no rights):", rq.URL)
|
||||
return
|
||||
}
|
||||
util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld)))
|
||||
}
|
||||
|
||||
func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaData != nil && hyphaData.binaryPath != ""
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if !u.CanProceed("unattach-confirm") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
||||
log.Println("Rejected (no rights):", rq.URL)
|
||||
return
|
||||
}
|
||||
if !hasAmnt {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
||||
log.Println("Rejected (no amnt):", rq.URL)
|
||||
return
|
||||
} else if !isOld {
|
||||
// The precondition is to have the hypha in the first place.
|
||||
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
|
||||
"Error: no such hypha",
|
||||
"Could not unattach this hypha because it does not exist")
|
||||
return
|
||||
}
|
||||
if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error: could not unattach hypha",
|
||||
fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
|
@ -80,6 +80,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "page")
|
||||
data, hyphaExists = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaExists && data.binaryPath != ""
|
||||
contents string
|
||||
openGraph string
|
||||
)
|
||||
@ -102,6 +103,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
|
||||
templates.PageHTML(rq, hyphaName,
|
||||
naviTitle(hyphaName),
|
||||
contents,
|
||||
treeHTML, prevHypha, nextHypha),
|
||||
treeHTML, prevHypha, nextHypha,
|
||||
hasAmnt),
|
||||
openGraph))
|
||||
}
|
||||
|
23
hypha.go
23
hypha.go
@ -128,6 +128,29 @@ func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.Histor
|
||||
return hop
|
||||
}
|
||||
|
||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
||||
func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp {
|
||||
hop := history.Operation(history.TypeUnattachHypha).
|
||||
WithFilesRemoved(hd.binaryPath).
|
||||
WithMsg(fmt.Sprintf("Unattach ‘%s’", hyphaName)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
hd, ok := HyphaStorage[hyphaName]
|
||||
if ok {
|
||||
if hd.binaryPath != "" {
|
||||
hd.binaryPath = ""
|
||||
}
|
||||
// If nothing is left of the hypha
|
||||
if hd.textPath == "" {
|
||||
delete(HyphaStorage, hyphaName)
|
||||
hyphae.DecrementCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
func findHyphaeToRename(hyphaName string, recursive bool) []string {
|
||||
hyphae := []string{hyphaName}
|
||||
if recursive {
|
||||
|
2
main.go
2
main.go
@ -154,7 +154,7 @@ func main() {
|
||||
history.Start(WikiDir)
|
||||
|
||||
// See http_readers.go for /page/, /text/, /binary/
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
|
||||
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
||||
// See http_history.go for /history/, /recent-changes
|
||||
http.HandleFunc("/list", handlerList)
|
||||
|
@ -72,6 +72,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
|
||||
.navi-title__separator { margin: 0 .25rem; }
|
||||
.navi-title__colon { margin-right: .5rem; }
|
||||
.upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; }
|
||||
.upload-amnt__unattach { display: block; }
|
||||
aside { clear: both; }
|
||||
|
||||
.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; }
|
||||
|
@ -47,6 +47,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
|
||||
.navi-title__separator { margin: 0 .25rem; }
|
||||
.navi-title__colon { margin-right: .5rem; }
|
||||
.upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; }
|
||||
.upload-amnt__unattach { display: block; }
|
||||
aside { clear: both; }
|
||||
|
||||
.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; }
|
||||
|
@ -28,7 +28,7 @@
|
||||
{% endfunc %}
|
||||
|
||||
If `contents` == "", a helpful message is shown instead.
|
||||
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) %}
|
||||
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %}
|
||||
<main>
|
||||
{%= navHTML(rq, hyphaName, "page") %}
|
||||
<article>
|
||||
@ -51,6 +51,9 @@ If `contents` == "", a helpful message is shown instead.
|
||||
<form action="/upload-binary/{%s hyphaName %}"
|
||||
method="post" enctype="multipart/form-data"
|
||||
class="upload-amnt">
|
||||
{% if hasAmnt %}
|
||||
<a class="upload-amnt__unattach" href="/unattach-ask/{%s hyphaName %}">Unattach current attachment?</a>
|
||||
{% endif %}
|
||||
<label for="upload-binary__input">Upload a new attachment</label>
|
||||
<br>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
|
@ -144,7 +144,7 @@ func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHas
|
||||
// If `contents` == "", a helpful message is shown instead.
|
||||
|
||||
//line templates/http_readers.qtpl:31
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) {
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||
//line templates/http_readers.qtpl:31
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
@ -237,50 +237,65 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi
|
||||
qw422016.N().S(`"
|
||||
method="post" enctype="multipart/form-data"
|
||||
class="upload-amnt">
|
||||
`)
|
||||
//line templates/http_readers.qtpl:54
|
||||
if hasAmnt {
|
||||
//line templates/http_readers.qtpl:54
|
||||
qw422016.N().S(`
|
||||
<a class="upload-amnt__unattach" href="/unattach-ask/`)
|
||||
//line templates/http_readers.qtpl:55
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:55
|
||||
qw422016.N().S(`">Unattach current attachment?</a>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:56
|
||||
}
|
||||
//line templates/http_readers.qtpl:56
|
||||
qw422016.N().S(`
|
||||
<label for="upload-binary__input">Upload a new attachment</label>
|
||||
<br>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:62
|
||||
}
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:62
|
||||
qw422016.N().S(`
|
||||
<aside>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:61
|
||||
//line templates/http_readers.qtpl:64
|
||||
qw422016.N().S(tree)
|
||||
//line templates/http_readers.qtpl:61
|
||||
//line templates/http_readers.qtpl:64
|
||||
qw422016.N().S(`
|
||||
</aside>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:64
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) {
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||
//line templates/http_readers.qtpl:67
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||
//line templates/http_readers.qtpl:67
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:64
|
||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) string {
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string {
|
||||
//line templates/http_readers.qtpl:67
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:64
|
||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||
//line templates/http_readers.qtpl:67
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
return qs422016
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
}
|
||||
|
24
templates/unattach.qtpl
Normal file
24
templates/unattach.qtpl
Normal file
@ -0,0 +1,24 @@
|
||||
{% import "net/http" %}
|
||||
{% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
|
||||
<main>
|
||||
{%= navHTML(rq, hyphaName, "unattach-ask") %}
|
||||
{%- if isOld -%}
|
||||
<section>
|
||||
<h1>Unattach {%s hyphaName %}?</h1>
|
||||
<p>Do you really want to unattach hypha <em>{%s hyphaName %}</em>?</p>
|
||||
<p><a href="/unattach-confirm/{%s hyphaName %}"><strong>Confirm</strong></a></p>
|
||||
<p><a href="/page/{%s hyphaName %}">Cancel</a></p>
|
||||
</section>
|
||||
{%- else -%}
|
||||
{%= cannotUnattachDueToNonExistence(hyphaName) %}
|
||||
{%- endif -%}
|
||||
</main>
|
||||
{% endfunc %}
|
||||
|
||||
{% func cannotUnattachDueToNonExistence(hyphaName string) %}
|
||||
<section>
|
||||
<h1>Cannot unattach {%s hyphaName %}</h1>
|
||||
<p>This hypha does not exist.</p>
|
||||
<p><a href="/page/{%s hyphaName %}">Go back</a></p>
|
||||
</section>
|
||||
{% endfunc %}
|
148
templates/unattach.qtpl.go
Normal file
148
templates/unattach.qtpl.go
Normal file
@ -0,0 +1,148 @@
|
||||
// Code generated by qtc from "unattach.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
//line templates/unattach.qtpl:1
|
||||
package templates
|
||||
|
||||
//line templates/unattach.qtpl:1
|
||||
import "net/http"
|
||||
|
||||
//line templates/unattach.qtpl:2
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/unattach.qtpl:2
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/unattach.qtpl:2
|
||||
func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/unattach.qtpl:2
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/unattach.qtpl:4
|
||||
streamnavHTML(qw422016, rq, hyphaName, "unattach-ask")
|
||||
//line templates/unattach.qtpl:4
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/unattach.qtpl:5
|
||||
if isOld {
|
||||
//line templates/unattach.qtpl:5
|
||||
qw422016.N().S(` <section>
|
||||
<h1>Unattach `)
|
||||
//line templates/unattach.qtpl:7
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:7
|
||||
qw422016.N().S(`?</h1>
|
||||
<p>Do you really want to unattach hypha <em>`)
|
||||
//line templates/unattach.qtpl:8
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:8
|
||||
qw422016.N().S(`</em>?</p>
|
||||
<p><a href="/unattach-confirm/`)
|
||||
//line templates/unattach.qtpl:9
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:9
|
||||
qw422016.N().S(`"><strong>Confirm</strong></a></p>
|
||||
<p><a href="/page/`)
|
||||
//line templates/unattach.qtpl:10
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:10
|
||||
qw422016.N().S(`">Cancel</a></p>
|
||||
</section>
|
||||
`)
|
||||
//line templates/unattach.qtpl:12
|
||||
} else {
|
||||
//line templates/unattach.qtpl:12
|
||||
qw422016.N().S(` `)
|
||||
//line templates/unattach.qtpl:13
|
||||
streamcannotUnattachDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/unattach.qtpl:13
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/unattach.qtpl:14
|
||||
}
|
||||
//line templates/unattach.qtpl:14
|
||||
qw422016.N().S(`</main>
|
||||
`)
|
||||
//line templates/unattach.qtpl:16
|
||||
}
|
||||
|
||||
//line templates/unattach.qtpl:16
|
||||
func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/unattach.qtpl:16
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/unattach.qtpl:16
|
||||
StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld)
|
||||
//line templates/unattach.qtpl:16
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/unattach.qtpl:16
|
||||
}
|
||||
|
||||
//line templates/unattach.qtpl:16
|
||||
func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
|
||||
//line templates/unattach.qtpl:16
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/unattach.qtpl:16
|
||||
WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld)
|
||||
//line templates/unattach.qtpl:16
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/unattach.qtpl:16
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/unattach.qtpl:16
|
||||
return qs422016
|
||||
//line templates/unattach.qtpl:16
|
||||
}
|
||||
|
||||
//line templates/unattach.qtpl:18
|
||||
func streamcannotUnattachDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) {
|
||||
//line templates/unattach.qtpl:18
|
||||
qw422016.N().S(`
|
||||
<section>
|
||||
<h1>Cannot unattach `)
|
||||
//line templates/unattach.qtpl:20
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:20
|
||||
qw422016.N().S(`</h1>
|
||||
<p>This hypha does not exist.</p>
|
||||
<p><a href="/page/`)
|
||||
//line templates/unattach.qtpl:22
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/unattach.qtpl:22
|
||||
qw422016.N().S(`">Go back</a></p>
|
||||
</section>
|
||||
`)
|
||||
//line templates/unattach.qtpl:24
|
||||
}
|
||||
|
||||
//line templates/unattach.qtpl:24
|
||||
func writecannotUnattachDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) {
|
||||
//line templates/unattach.qtpl:24
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/unattach.qtpl:24
|
||||
streamcannotUnattachDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/unattach.qtpl:24
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/unattach.qtpl:24
|
||||
}
|
||||
|
||||
//line templates/unattach.qtpl:24
|
||||
func cannotUnattachDueToNonExistence(hyphaName string) string {
|
||||
//line templates/unattach.qtpl:24
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/unattach.qtpl:24
|
||||
writecannotUnattachDueToNonExistence(qb422016, hyphaName)
|
||||
//line templates/unattach.qtpl:24
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/unattach.qtpl:24
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/unattach.qtpl:24
|
||||
return qs422016
|
||||
//line templates/unattach.qtpl:24
|
||||
}
|
18
user/user.go
18
user/user.go
@ -15,14 +15,16 @@ type User struct {
|
||||
|
||||
// Route — Right (more is more right)
|
||||
var minimalRights = map[string]int{
|
||||
"edit": 1,
|
||||
"upload-binary": 1,
|
||||
"upload-text": 1,
|
||||
"rename-ask": 2,
|
||||
"rename-confirm": 2,
|
||||
"delete-ask": 3,
|
||||
"delete-confirm": 3,
|
||||
"reindex": 4,
|
||||
"edit": 1,
|
||||
"upload-binary": 1,
|
||||
"upload-text": 1,
|
||||
"rename-ask": 2,
|
||||
"rename-confirm": 2,
|
||||
"unattach-ask": 2,
|
||||
"unattach-confirm": 2,
|
||||
"delete-ask": 3,
|
||||
"delete-confirm": 3,
|
||||
"reindex": 4,
|
||||
}
|
||||
|
||||
// Group — Right
|
||||
|
Loading…
Reference in New Issue
Block a user