mirror of https://github.com/jlelse/GoBlog
One editor per blog
This commit is contained in:
parent
f3744227e1
commit
89b48b1d37
|
@ -38,7 +38,7 @@ func serveBlogStats(blog, statsPath string) func(w http.ResponseWriter, r *http.
|
||||||
counts = append(counts, count)
|
counts = append(counts, count)
|
||||||
}
|
}
|
||||||
render(w, templateBlogStats, &renderData{
|
render(w, templateBlogStats, &renderData{
|
||||||
blogString: blog,
|
BlogString: blog,
|
||||||
Canonical: statsPath,
|
Canonical: statsPath,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"total": totalCount,
|
"total": totalCount,
|
||||||
|
|
132
editor.go
132
editor.go
|
@ -10,72 +10,78 @@ import (
|
||||||
|
|
||||||
const editorPath = "/editor"
|
const editorPath = "/editor"
|
||||||
|
|
||||||
func serveEditor(w http.ResponseWriter, _ *http.Request) {
|
func serveEditor(blog string) func(w http.ResponseWriter, _ *http.Request) {
|
||||||
render(w, templateEditor, &renderData{
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
Data: map[string]interface{}{
|
render(w, templateEditor, &renderData{
|
||||||
"Drafts": loadDrafts(),
|
BlogString: blog,
|
||||||
},
|
Data: map[string]interface{}{
|
||||||
})
|
"Drafts": loadDrafts(blog),
|
||||||
}
|
},
|
||||||
|
})
|
||||||
func serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if action := r.FormValue("editoraction"); action != "" {
|
|
||||||
switch action {
|
|
||||||
case "loadupdate":
|
|
||||||
parsedURL, err := url.Parse(r.FormValue("url"))
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
post, err := getPost(parsedURL.Path)
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mf := post.toMfItem()
|
|
||||||
render(w, templateEditor, &renderData{
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"UpdatePostURL": parsedURL.String(),
|
|
||||||
"UpdatePostContent": mf.Properties.Content[0],
|
|
||||||
"Drafts": loadDrafts(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
case "updatepost":
|
|
||||||
urlValue := r.FormValue("url")
|
|
||||||
content := r.FormValue("content")
|
|
||||||
mf := map[string]interface{}{
|
|
||||||
"action": actionUpdate,
|
|
||||||
"url": urlValue,
|
|
||||||
"replace": map[string][]string{
|
|
||||||
"content": {
|
|
||||||
content,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jsonBytes, err := json.Marshal(mf)
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(jsonBytes))
|
|
||||||
if err != nil {
|
|
||||||
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Header.Set(contentType, contentTypeJSON)
|
|
||||||
editorMicropubPost(w, req, false)
|
|
||||||
case "upload":
|
|
||||||
editorMicropubPost(w, r, true)
|
|
||||||
default:
|
|
||||||
serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
editorMicropubPost(w, r, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDrafts() []*post {
|
func serveEditorPost(blog string) func(w http.ResponseWriter, r *http.Request) {
|
||||||
ps, _ := getPosts(&postsRequestConfig{status: statusDraft})
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if action := r.FormValue("editoraction"); action != "" {
|
||||||
|
switch action {
|
||||||
|
case "loadupdate":
|
||||||
|
parsedURL, err := url.Parse(r.FormValue("url"))
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
post, err := getPost(parsedURL.Path)
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mf := post.toMfItem()
|
||||||
|
render(w, templateEditor, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"UpdatePostURL": parsedURL.String(),
|
||||||
|
"UpdatePostContent": mf.Properties.Content[0],
|
||||||
|
"Drafts": loadDrafts(blog),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case "updatepost":
|
||||||
|
urlValue := r.FormValue("url")
|
||||||
|
content := r.FormValue("content")
|
||||||
|
mf := map[string]interface{}{
|
||||||
|
"action": actionUpdate,
|
||||||
|
"url": urlValue,
|
||||||
|
"replace": map[string][]string{
|
||||||
|
"content": {
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jsonBytes, err := json.Marshal(mf)
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set(contentType, contentTypeJSON)
|
||||||
|
editorMicropubPost(w, req, false)
|
||||||
|
case "upload":
|
||||||
|
editorMicropubPost(w, r, true)
|
||||||
|
default:
|
||||||
|
serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editorMicropubPost(w, r, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDrafts(blog string) []*post {
|
||||||
|
ps, _ := getPosts(&postsRequestConfig{status: statusDraft, blog: blog})
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -62,7 +62,7 @@ require (
|
||||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
|
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 // indirect
|
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -497,8 +497,8 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnf
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k=
|
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
|
||||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
|
14
http.go
14
http.go
|
@ -106,13 +106,6 @@ func buildHandler() (http.Handler, error) {
|
||||||
mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
|
mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Editor
|
|
||||||
r.Route("/editor", func(mpRouter chi.Router) {
|
|
||||||
mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware)
|
|
||||||
mpRouter.Get("/", serveEditor)
|
|
||||||
mpRouter.Post("/", serveEditorPost)
|
|
||||||
})
|
|
||||||
|
|
||||||
// IndieAuth
|
// IndieAuth
|
||||||
r.Route("/indieauth", func(indieauthRouter chi.Router) {
|
r.Route("/indieauth", func(indieauthRouter chi.Router) {
|
||||||
indieauthRouter.Use(middleware.NoCache, minifier.Middleware)
|
indieauthRouter.Use(middleware.NoCache, minifier.Middleware)
|
||||||
|
@ -325,6 +318,13 @@ func buildHandler() (http.Handler, error) {
|
||||||
r.With(minifier.Middleware).Get(cp.Path, handler)
|
r.With(minifier.Middleware).Get(cp.Path, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
r.Route(blogPath+"/editor", func(mpRouter chi.Router) {
|
||||||
|
mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware)
|
||||||
|
mpRouter.Get("/", serveEditor(blog))
|
||||||
|
mpRouter.Post("/", serveEditorPost(blog))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sitemap
|
// Sitemap
|
||||||
|
|
4
posts.go
4
posts.go
|
@ -67,7 +67,7 @@ func servePost(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
w.Header().Add("Link", fmt.Sprintf("<%s>; rel=shortlink", p.shortURL()))
|
w.Header().Add("Link", fmt.Sprintf("<%s>; rel=shortlink", p.shortURL()))
|
||||||
render(w, template, &renderData{
|
render(w, template, &renderData{
|
||||||
blogString: p.Blog,
|
BlogString: p.Blog,
|
||||||
Canonical: canonical,
|
Canonical: canonical,
|
||||||
Data: p,
|
Data: p,
|
||||||
})
|
})
|
||||||
|
@ -279,7 +279,7 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
|
||||||
summaryTemplate = templateSummary
|
summaryTemplate = templateSummary
|
||||||
}
|
}
|
||||||
render(w, templateIndex, &renderData{
|
render(w, templateIndex, &renderData{
|
||||||
blogString: ic.blog,
|
BlogString: ic.blog,
|
||||||
Canonical: appConfig.Server.PublicAddress + path,
|
Canonical: appConfig.Server.PublicAddress + path,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Title": title,
|
"Title": title,
|
||||||
|
|
16
render.go
16
render.go
|
@ -222,7 +222,7 @@ func initRendering() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type renderData struct {
|
type renderData struct {
|
||||||
blogString string
|
BlogString string
|
||||||
Canonical string
|
Canonical string
|
||||||
Blog *configBlog
|
Blog *configBlog
|
||||||
Data interface{}
|
Data interface{}
|
||||||
|
@ -231,10 +231,18 @@ type renderData struct {
|
||||||
func render(w http.ResponseWriter, template string, data *renderData) {
|
func render(w http.ResponseWriter, template string, data *renderData) {
|
||||||
// Check render data
|
// Check render data
|
||||||
if data.Blog == nil {
|
if data.Blog == nil {
|
||||||
if len(data.blogString) == 0 {
|
if len(data.BlogString) == 0 {
|
||||||
data.blogString = appConfig.DefaultBlog
|
data.BlogString = appConfig.DefaultBlog
|
||||||
|
}
|
||||||
|
data.Blog = appConfig.Blogs[data.BlogString]
|
||||||
|
}
|
||||||
|
if data.BlogString == "" {
|
||||||
|
for s, b := range appConfig.Blogs {
|
||||||
|
if b == data.Blog {
|
||||||
|
data.BlogString = s
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data.Blog = appConfig.Blogs[data.blogString]
|
|
||||||
}
|
}
|
||||||
if data.Data == nil {
|
if data.Data == nil {
|
||||||
data.Data = map[string]interface{}{}
|
data.Data = map[string]interface{}{}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func serveSearch(blog string, path string) func(w http.ResponseWriter, r *http.R
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render(w, templateSearch, &renderData{
|
render(w, templateSearch, &renderData{
|
||||||
blogString: blog,
|
BlogString: blog,
|
||||||
Canonical: appConfig.Server.PublicAddress + path,
|
Canonical: appConfig.Server.PublicAddress + path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ func serveTaxonomy(blog string, tax *taxonomy) func(w http.ResponseWriter, r *ht
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render(w, templateTaxonomy, &renderData{
|
render(w, templateTaxonomy, &renderData{
|
||||||
blogString: blog,
|
BlogString: blog,
|
||||||
Canonical: appConfig.Server.PublicAddress + r.URL.Path,
|
Canonical: appConfig.Server.PublicAddress + r.URL.Path,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Taxonomy": tax,
|
"Taxonomy": tax,
|
||||||
|
|
|
@ -9,10 +9,13 @@
|
||||||
<form class="fw-form p" method="post">
|
<form class="fw-form p" method="post">
|
||||||
<input type="hidden" name="h" value="entry">
|
<input type="hidden" name="h" value="entry">
|
||||||
<textarea name="content" class="monospace h400p">---
|
<textarea name="content" class="monospace h400p">---
|
||||||
title:
|
status: draft
|
||||||
|
blog: {{ .BlogString }}
|
||||||
section:
|
section:
|
||||||
slug:
|
slug:
|
||||||
blog:
|
title:
|
||||||
|
tags:
|
||||||
|
-
|
||||||
---
|
---
|
||||||
</textarea>
|
</textarea>
|
||||||
<input class="fw" type="submit" value="{{ string .Blog.Lang "create" }}">
|
<input class="fw" type="submit" value="{{ string .Blog.Lang "create" }}">
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
publishedon: "Veröffentlicht am"
|
anoncomment: "Du kannst auch einen anonymen Kommentar verfassen."
|
||||||
updatedon: "Aktualisiert am"
|
count: "Anzahl"
|
||||||
next: "Weiter"
|
create: "Erstellen"
|
||||||
prev: "Zurück"
|
delete: "Löschen"
|
||||||
view: "Anschauen"
|
drafts: "Entwürfe"
|
||||||
replyto: "Antwort an"
|
editor: "Editor"
|
||||||
|
interactions: "Interaktionen & Kommentare"
|
||||||
|
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
|
||||||
likeof: "Gefällt mir von"
|
likeof: "Gefällt mir von"
|
||||||
translations: "Übersetzungen"
|
next: "Weiter"
|
||||||
|
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
|
||||||
|
prev: "Zurück"
|
||||||
|
publishedon: "Veröffentlicht am"
|
||||||
|
replyto: "Antwort an"
|
||||||
|
search: "Suchen"
|
||||||
|
send: "Senden (zur Überprüfung)"
|
||||||
share: "Teilen"
|
share: "Teilen"
|
||||||
|
shorturl: "Kurz-Link:"
|
||||||
speak: "Lies mir bitte vor."
|
speak: "Lies mir bitte vor."
|
||||||
stopspeak: "Hör auf zu sprechen!"
|
stopspeak: "Hör auf zu sprechen!"
|
||||||
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
|
|
||||||
search: "Suchen"
|
|
||||||
shorturl: "Kurz-URL:"
|
|
||||||
year: "Jahr"
|
|
||||||
count: "Anzahl"
|
|
||||||
total: "Gesamt"
|
total: "Gesamt"
|
||||||
anoncomment: "Du kannst auch einen anonymen Kommentar verfassen."
|
translations: "Übersetzungen"
|
||||||
interactions: "Interaktionen & Kommentare"
|
update: "Aktualisieren"
|
||||||
send: "Senden (zur Überprüfung)"
|
updatedon: "Aktualisiert am"
|
||||||
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
|
upload: "Hochladen"
|
||||||
|
view: "Anschauen"
|
||||||
|
year: "Jahr"
|
|
@ -1,37 +1,37 @@
|
||||||
publishedon: "Published on"
|
anoncomment: "You can also create an anonymous comment."
|
||||||
updatedon: "Updated on"
|
approve: "Approve"
|
||||||
next: "Next"
|
approved: "Approved"
|
||||||
prev: "Previous"
|
|
||||||
view: "View"
|
|
||||||
authenticate: "Authenticate"
|
authenticate: "Authenticate"
|
||||||
scopes: "Scopes"
|
count: "Count"
|
||||||
|
create: "Create"
|
||||||
|
delete: "Delete"
|
||||||
|
drafts: "Drafts"
|
||||||
|
editor: "Editor"
|
||||||
indieauth: "IndieAuth"
|
indieauth: "IndieAuth"
|
||||||
replyto: "Reply to"
|
interactions: "Interactions & Comments"
|
||||||
|
interactionslabel: "Have you published a response to this? Paste the URL here."
|
||||||
likeof: "Like of"
|
likeof: "Like of"
|
||||||
translations: "Translations"
|
login: "Login"
|
||||||
|
next: "Next"
|
||||||
|
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
||||||
|
password: "Password"
|
||||||
|
prev: "Previous"
|
||||||
|
publishedon: "Published on"
|
||||||
|
replyto: "Reply to"
|
||||||
|
scopes: "Scopes"
|
||||||
|
search: "Search"
|
||||||
|
send: "Send (to review)"
|
||||||
share: "Share"
|
share: "Share"
|
||||||
|
shorturl: "Short link:"
|
||||||
speak: "Read to me, please."
|
speak: "Read to me, please."
|
||||||
stopspeak: "Stop speaking!"
|
stopspeak: "Stop speaking!"
|
||||||
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
|
||||||
webmentions: "Webmentions"
|
|
||||||
verified: "Verified"
|
|
||||||
approved: "Approved"
|
|
||||||
delete: "Delete"
|
|
||||||
approve: "Approve"
|
|
||||||
anoncomment: "You can also create an anonymous comment."
|
|
||||||
interactions: "Interactions & Comments"
|
|
||||||
send: "Send (to review)"
|
|
||||||
interactionslabel: "Have you published a response to this? Paste the URL here."
|
|
||||||
search: "Search"
|
|
||||||
editor: "Editor"
|
|
||||||
create: "Create"
|
|
||||||
update: "Update"
|
|
||||||
upload: "Upload"
|
|
||||||
login: "Login"
|
|
||||||
username: "Username"
|
|
||||||
password: "Password"
|
|
||||||
shorturl: "Short URL:"
|
|
||||||
year: "Year"
|
|
||||||
count: "Count"
|
|
||||||
total: "Total"
|
total: "Total"
|
||||||
drafts: "Drafts"
|
translations: "Translations"
|
||||||
|
update: "Update"
|
||||||
|
updatedon: "Updated on"
|
||||||
|
upload: "Upload"
|
||||||
|
username: "Username"
|
||||||
|
verified: "Verified"
|
||||||
|
view: "View"
|
||||||
|
webmentions: "Webmentions"
|
||||||
|
year: "Year"
|
Loading…
Reference in New Issue