Merge branch 'devel'
This commit is contained in:
commit
3723b2b5e6
@ -9,18 +9,20 @@ import (
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
Title string
|
||||
Created time.Time
|
||||
Description string
|
||||
Link string
|
||||
EncURL string
|
||||
EncLength int
|
||||
EncType string
|
||||
Published bool
|
||||
Rejected bool
|
||||
ID int64
|
||||
AuthorID int64
|
||||
IssueID int64
|
||||
Title string
|
||||
Created time.Time
|
||||
Description string
|
||||
Link string
|
||||
EncURL string
|
||||
EncLength int
|
||||
EncType string
|
||||
Published bool
|
||||
Rejected bool
|
||||
ID int64
|
||||
AuthorID int64
|
||||
IssueID int64
|
||||
IsInIssue bool
|
||||
AutoGenerated bool
|
||||
}
|
||||
|
||||
func (db *DB) AddArticle(a *Article) (int64, error) {
|
||||
@ -29,8 +31,8 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
||||
selectQuery := "SELECT id FROM issues WHERE published = false"
|
||||
insertQuery := `
|
||||
INSERT INTO articles
|
||||
(title, description, link, enc_url, enc_length, enc_type, published, rejected, author_id, issue_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(title, description, link, enc_url, enc_length, enc_type, published, rejected, author_id, issue_id, is_in_issue, auto_generated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
|
||||
for i := 0; i < TxMaxRetries; i++ {
|
||||
@ -47,8 +49,10 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
||||
return 0, fmt.Errorf("error getting issue ID when adding article to DB: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println(a)
|
||||
result, err := tx.Exec(insertQuery, a.Title, a.Description, a.Link,
|
||||
a.EncURL, a.EncLength, a.EncType, a.Published, a.Rejected, a.AuthorID, id)
|
||||
a.EncURL, a.EncLength, a.EncType, a.Published, a.Rejected, a.AuthorID, id,
|
||||
a.IsInIssue, a.AutoGenerated)
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||
@ -82,7 +86,7 @@ func (db *DB) AddArticle(a *Article) (int64, error) {
|
||||
|
||||
func (db *DB) GetArticle(id int64) (*Article, error) {
|
||||
query := `
|
||||
SELECT title, created, description, link, enc_url, enc_length, enc_type, published, author_id
|
||||
SELECT title, created, description, link, enc_url, enc_length, enc_type, published, author_id, issue_id, is_in_issue, auto_generated
|
||||
FROM articles
|
||||
WHERE id = ?
|
||||
`
|
||||
@ -94,7 +98,8 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
|
||||
|
||||
if err := row.Scan(&article.Title, &created, &article.Description,
|
||||
&article.Link, &article.EncURL, &article.EncLength, &article.EncType,
|
||||
&article.Published, &article.AuthorID); err != nil {
|
||||
&article.Published, &article.AuthorID, &article.IssueID,
|
||||
&article.IsInIssue, &article.AutoGenerated); err != nil {
|
||||
return nil, fmt.Errorf("error scanning article row: %v", err)
|
||||
}
|
||||
|
||||
@ -109,7 +114,7 @@ func (db *DB) GetArticle(id int64) (*Article, error) {
|
||||
|
||||
func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
|
||||
query := `
|
||||
SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, issue_id
|
||||
SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, issue_id, is_in_issue, auto_generated
|
||||
FROM articles
|
||||
WHERE published = ?
|
||||
AND rejected = ?
|
||||
@ -126,7 +131,8 @@ func (db *DB) GetCertainArticles(published, rejected bool) ([]*Article, error) {
|
||||
|
||||
if err = rows.Scan(&article.ID, &article.Title, &created,
|
||||
&article.Description, &article.Link, &article.EncURL, &article.EncLength,
|
||||
&article.EncType, &article.AuthorID, &article.IssueID); err != nil {
|
||||
&article.EncType, &article.AuthorID, &article.IssueID,
|
||||
&article.IsInIssue, &article.AutoGenerated); err != nil {
|
||||
return nil, fmt.Errorf("error scanning article row: %v", err)
|
||||
}
|
||||
|
||||
@ -147,9 +153,9 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
|
||||
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
|
||||
issueQuery := "SELECT id FROM issues WHERE published = false"
|
||||
articlesQuery := `
|
||||
SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id
|
||||
SELECT id, title, created, description, link, enc_url, enc_length, enc_type, author_id, auto_generated
|
||||
FROM articles
|
||||
WHERE issue_id = ? AND published = true
|
||||
WHERE issue_id = ? AND published = true AND is_in_issue = true
|
||||
`
|
||||
|
||||
for i := 0; i < TxMaxRetries; i++ {
|
||||
@ -182,7 +188,7 @@ func (db *DB) GetCurrentIssueArticles() ([]*Article, error) {
|
||||
|
||||
if err = rows.Scan(&article.ID, &article.Title, &created,
|
||||
&article.Description, &article.Link, &article.EncURL, &article.EncLength,
|
||||
&article.EncType, &article.AuthorID); err != nil {
|
||||
&article.EncType, &article.AuthorID, &article.AutoGenerated); err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||
}
|
||||
|
@ -31,7 +31,10 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
|
||||
for _, tag := range tags {
|
||||
tagNames = append(tagNames, tag.Name)
|
||||
}
|
||||
tagNames = append(tagNames, fmt.Sprint("Orient Express ", article.IssueID))
|
||||
|
||||
if article.IsInIssue {
|
||||
tagNames = append(tagNames, fmt.Sprint("Orient Express ", article.IssueID))
|
||||
}
|
||||
|
||||
user, err := db.GetUser(article.AuthorID)
|
||||
if err != nil {
|
||||
@ -57,9 +60,8 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
|
||||
PubDate: article.Created.Format(time.RFC1123Z),
|
||||
Title: articleTitle,
|
||||
}
|
||||
fmt.Println(article.Link, ": ", len(article.Link))
|
||||
|
||||
if article.Title == "Autogenerated cpolis Issue Article" {
|
||||
if article.AutoGenerated {
|
||||
item.Enclosure = &rss.Enclosure{
|
||||
Url: article.EncURL,
|
||||
Lenght: article.EncLength,
|
||||
|
@ -73,11 +73,13 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||
}
|
||||
|
||||
article := &b.Article{
|
||||
Title: r.PostFormValue("article-title"),
|
||||
Description: r.PostFormValue("article-description"),
|
||||
Published: false,
|
||||
Rejected: false,
|
||||
AuthorID: session.Values["id"].(int64),
|
||||
Title: r.PostFormValue("article-title"),
|
||||
Description: r.PostFormValue("article-description"),
|
||||
Published: false,
|
||||
Rejected: false,
|
||||
AuthorID: session.Values["id"].(int64),
|
||||
IsInIssue: r.PostFormValue("issue") == "on",
|
||||
AutoGenerated: false,
|
||||
}
|
||||
|
||||
article.ID, err = db.AddArticle(article)
|
||||
@ -153,6 +155,7 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||
&b.Attribute{Table: "articles", ID: id, AttName: "title", Value: title},
|
||||
&b.Attribute{Table: "articles", ID: id, AttName: "description", Value: description},
|
||||
&b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: false},
|
||||
&b.Attribute{Table: "articles", ID: id, AttName: "is_in_issue", Value: r.PostFormValue("issue") == "on"},
|
||||
); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
@ -240,46 +243,27 @@ func ReviewUnpublishedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand
|
||||
return
|
||||
}
|
||||
|
||||
type htmlData struct {
|
||||
Title string
|
||||
Description string
|
||||
Content template.HTML
|
||||
Tags []*b.Tag
|
||||
ID int64
|
||||
}
|
||||
data := new(struct {
|
||||
Article *b.Article
|
||||
Content template.HTML
|
||||
Tags []*b.Tag
|
||||
})
|
||||
|
||||
var err error
|
||||
data := new(htmlData)
|
||||
|
||||
data.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
article, err := db.GetArticle(data.ID)
|
||||
data.Article, err = db.GetArticle(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data.Title, err = b.ConvertToPlain(article.Title)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data.Description, err = b.ConvertToPlain(article.Description)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
articleAbsName := fmt.Sprint(c.ArticleDir, "/", article.ID, ".md")
|
||||
articleAbsName := fmt.Sprint(c.ArticleDir, "/", data.Article.ID, ".md")
|
||||
contentBytes, err := os.ReadFile(articleAbsName)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -295,7 +279,7 @@ func ReviewUnpublishedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand
|
||||
}
|
||||
data.Content = template.HTML(content)
|
||||
|
||||
data.Tags, err = db.GetArticleTags(data.ID)
|
||||
data.Tags, err = db.GetArticleTags(data.Article.ID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
@ -314,13 +298,12 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
|
||||
return
|
||||
}
|
||||
|
||||
type htmlData struct {
|
||||
data := new(struct {
|
||||
Selected map[int64]bool
|
||||
Article *b.Article
|
||||
Content string
|
||||
Tags []*b.Tag
|
||||
}
|
||||
data := new(htmlData)
|
||||
})
|
||||
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -378,13 +361,20 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
article, err := db.GetArticle(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.AddArticleToCurrentIssue(id); err != nil {
|
||||
if err = db.AddArticleToCurrentIssue(article.ID); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -523,7 +513,7 @@ func ShowPublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler
|
||||
|
||||
filteredArticles := make([]*b.Article, 0)
|
||||
for _, article := range publishedArticles {
|
||||
if article.Title != "Autogenerated cpolis Issue Article" {
|
||||
if !article.AutoGenerated {
|
||||
filteredArticles = append(filteredArticles, article)
|
||||
}
|
||||
}
|
||||
|
@ -59,14 +59,15 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
|
||||
mimeType := mime.TypeByExtension(filepath.Ext(imgAbsName))
|
||||
|
||||
article := &b.Article{
|
||||
Title: "Autogenerated cpolis Issue Article",
|
||||
EncURL: fmt.Sprint(c.Domain, "/image/serve/", imgFileName),
|
||||
EncLength: int(imgSize),
|
||||
EncType: mimeType,
|
||||
Published: true,
|
||||
Rejected: false,
|
||||
Created: time.Now(),
|
||||
AuthorID: session.Values["id"].(int64),
|
||||
Title: r.PostFormValue("issue-title"),
|
||||
EncURL: fmt.Sprint(c.Domain, "/image/serve/", imgFileName),
|
||||
EncLength: int(imgSize),
|
||||
EncType: mimeType,
|
||||
Published: true,
|
||||
Rejected: false,
|
||||
Created: time.Now(),
|
||||
AuthorID: session.Values["id"].(int64),
|
||||
AutoGenerated: true,
|
||||
}
|
||||
fmt.Println(article.Link)
|
||||
|
||||
|
66
cmd/frontend/pdf.go
Normal file
66
cmd/frontend/pdf.go
Normal file
@ -0,0 +1,66 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
b "streifling.com/jason/cpolis/cmd/backend"
|
||||
)
|
||||
|
||||
func UploadPDF(c *b.Config, s *b.CookieStore) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Content-Type:", r.Header.Get("Content-Type"))
|
||||
if _, err := getSession(w, r, c, s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(1)
|
||||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(2)
|
||||
file, _, err := r.FormFile("pdf-upload")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fmt.Println(3)
|
||||
filename := fmt.Sprint(uuid.New(), ".pdf")
|
||||
absFilepath, err := filepath.Abs(fmt.Sprint(c.PDFDir, "/", filename))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(4)
|
||||
pdf, err := os.Create(absFilepath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer pdf.Close()
|
||||
|
||||
fmt.Println(5)
|
||||
if _, err = io.Copy(pdf, file); err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(6)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@ func main() {
|
||||
mux.HandleFunc("POST /issue/publish", f.PublishLatestIssue(config, db, store))
|
||||
mux.HandleFunc("POST /issue/upload-image", f.UploadIssueImage(config, store))
|
||||
mux.HandleFunc("POST /login", f.Login(config, db, store))
|
||||
mux.HandleFunc("POST /pdf/upload", f.UploadPDF(config, store))
|
||||
mux.HandleFunc("POST /tag/add", f.AddTag(config, db, store))
|
||||
mux.HandleFunc("POST /user/add", f.AddUser(config, db, store))
|
||||
mux.HandleFunc("POST /user/add-first", f.AddFirstUser(config, db, store))
|
||||
|
@ -21,18 +21,20 @@ CREATE TABLE issues (
|
||||
);
|
||||
|
||||
CREATE TABLE articles (
|
||||
id INT AUTO_INCREMENT,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
description TEXT NOT NULL,
|
||||
link VARCHAR(255),
|
||||
enc_url VARCHAR(255),
|
||||
enc_length INT,
|
||||
enc_type VARCHAR(255),
|
||||
published BOOL NOT NULL,
|
||||
rejected BOOL NOT NULL,
|
||||
author_id INT NOT NULL,
|
||||
issue_id INT NOT NULL,
|
||||
id INT AUTO_INCREMENT,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
description TEXT NOT NULL,
|
||||
link VARCHAR(255),
|
||||
enc_url VARCHAR(255),
|
||||
enc_length INT,
|
||||
enc_type VARCHAR(255),
|
||||
published BOOL NOT NULL,
|
||||
rejected BOOL NOT NULL,
|
||||
author_id INT NOT NULL,
|
||||
issue_id INT NOT NULL,
|
||||
is_in_issue BOOL NOT NULL,
|
||||
auto_generated BOOL NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY (author_id) REFERENCES users (id),
|
||||
FOREIGN KEY (issue_id) REFERENCES issues (id)
|
||||
|
@ -17,7 +17,18 @@
|
||||
|
||||
<div>
|
||||
<h3>Cover</h3>
|
||||
<input id="image-upload" name="issue-image" type="file" required hx-post="/issue/upload-image" />
|
||||
<div class="flex">
|
||||
<label class="btn text-center" for="image-upload">Bild hochladen</label>
|
||||
<input class="hidden" id="image-upload" name="issue-image" type="file" required
|
||||
hx-post="/issue/upload-image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Titel</h3>
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<input name="issue-title" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -21,6 +21,11 @@
|
||||
<div>
|
||||
<span>Tags</span>
|
||||
<div class="flex flex-wrap gap-x-4">
|
||||
<div>
|
||||
<input id="issue" name="issue" type="checkbox" />
|
||||
<label for="issue">Orient Express</label>
|
||||
</div>
|
||||
|
||||
{{range .Tags}}
|
||||
<div>
|
||||
<input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" />
|
||||
|
@ -16,7 +16,7 @@
|
||||
{{if lt . 3}}
|
||||
<div class="mb-3">
|
||||
<h2>Redakteur</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/article/all-unpublished" hx-target="#page-content">
|
||||
Unveröffentlichte Artikel
|
||||
</button>
|
||||
@ -28,9 +28,14 @@
|
||||
{{if lt . 2}}
|
||||
<div class="mb-3">
|
||||
<h2>Herausgeber</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button>
|
||||
<button class="btn" hx-get="/article/all-published" hx-target="#page-content">Artikel löschen</button>
|
||||
<form class="flex" hx-encoding="multipart/form-data">
|
||||
<label class="btn text-center" for="pdf-upload">PDF hochladen</label>
|
||||
<input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file"
|
||||
hx-post="/pdf/upload" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -21,6 +21,11 @@
|
||||
<div>
|
||||
<span>Tags</span>
|
||||
<div class="flex flex-wrap gap-x-4">
|
||||
<div>
|
||||
<input id="issue" name="issue" type="checkbox" {{if .Article.IsInIssue}}checked{{end}} />
|
||||
<label for="issue">Orient Express</label>
|
||||
</div>
|
||||
|
||||
{{range .Tags}}
|
||||
<div>
|
||||
<input id="tag-{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" {{if index $.Selected
|
||||
|
@ -4,12 +4,12 @@
|
||||
<div>
|
||||
<span>Titel</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{.Title}}
|
||||
{{.Article.Title}}
|
||||
</div>
|
||||
|
||||
<span>Beschreibung</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{.Description}}
|
||||
{{.Article.Description}}
|
||||
</div>
|
||||
|
||||
<span>Artikel</span>
|
||||
@ -21,16 +21,21 @@
|
||||
|
||||
<span>Tags</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{if .Article.IsInIssue}}
|
||||
<span>Orient Express</span>
|
||||
<br>
|
||||
{{end}}
|
||||
{{range .Tags}}
|
||||
{{.Name}}
|
||||
<span>{{.Name}}</span>
|
||||
<br>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="btn-area">
|
||||
<input class="action-btn" type="submit" value="Veröffentlichen" hx-get="/article/publish/{{.ID}}"
|
||||
<input class="action-btn" type="submit" value="Veröffentlichen" hx-get="/article/publish/{{.Article.ID}}"
|
||||
hx-target="#page-content" />
|
||||
<input class="btn" type="submit" value="Ablehnen" hx-get="/article/reject/{{.Article.ID}}"
|
||||
hx-target="#page-content" />
|
||||
<input class="btn" type="submit" value="Ablehnen" hx-get="/article/reject/{{.ID}}" hx-target="#page-content" />
|
||||
<button class="btn" hx-get="/hub" hx-target="#page-content">Zurück</button>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user