Compare commits

..

10 Commits

5 changed files with 116 additions and 149 deletions

View File

@ -1,9 +1,11 @@
package frontend package frontend
import ( import (
"encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"io/fs"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -16,11 +18,10 @@ import (
b "streifling.com/jason/cpolis/cmd/backend" b "streifling.com/jason/cpolis/cmd/backend"
) )
type ArticlePreviewHtmlData struct { const (
Title string EditMode = iota
Description string PreviewMode
Content template.HTML )
}
func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
@ -31,14 +32,45 @@ func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
} }
session.Values["article"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int)) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int))
} }
} }
func WriteArticle(c *b.Config, db *b.DB) http.HandlerFunc { func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
tags, err := db.GetTagList() type editorHTMLData struct {
Title string
Description string
Content string
HTMLContent template.HTML
Tags []*b.Tag
Mode int
}
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
var data editorHTMLData
if session.Values["article"] == nil {
data = editorHTMLData{}
} else {
data = session.Values["article"].(editorHTMLData)
}
data.Mode = EditMode
data.Tags, err = db.GetTagList()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -46,7 +78,7 @@ func WriteArticle(c *b.Config, db *b.DB) http.HandlerFunc {
} }
tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html")
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", tags) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data)
} }
} }
@ -59,6 +91,13 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
} }
session.Values["article"] = nil
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
article := &b.Article{ article := &b.Article{
Title: r.PostFormValue("article-title"), Title: r.PostFormValue("article-title"),
Description: r.PostFormValue("article-description"), Description: r.PostFormValue("article-description"),
@ -425,6 +464,12 @@ func UploadImage(c *b.Config) http.HandlerFunc {
return return
} }
if err = os.MkdirAll(fmt.Sprint(c.PicsDir, "/"), fs.FileMode(0755)); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
img, err := os.Create(absFilepath) img, err := os.Create(absFilepath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -439,56 +484,8 @@ func UploadImage(c *b.Config) http.HandlerFunc {
return return
} }
alt := strings.Join(nameStrings[0:len(nameStrings)-1], " ") url := fmt.Sprint(c.Domain, "/pics/", filename)
imgMD := fmt.Sprint("![", alt, "](", c.Domain, "/pics/", filename, ")") w.Header().Set("Content-Type", "application/json")
tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html") json.NewEncoder(w).Encode(url)
template.Must(tmpl, err).ExecuteTemplate(w, "editor-images", imgMD)
}
}
func PreviewArticle(c *b.Config, s *b.CookieStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var err error
data := new(ArticlePreviewHtmlData)
data.Title, err = b.ConvertToPlain(r.PostFormValue("article-title"))
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.Description, err = b.ConvertToPlain(r.PostFormValue("article-description"))
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
content, err := b.ConvertToHTML(r.PostFormValue("article-content"))
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.Content = template.HTML(content)
session, err := s.Get(r, "cookie")
if err != nil {
tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html")
msg := "Session nicht mehr gültig. Bitte erneut anmelden."
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg)
}
session.Values["article"] = data
if err = session.Save(r, w); err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.ParseFiles(c.WebDir + "/templates/editor.html")
tmpl = template.Must(tmpl, err)
tmpl.ExecuteTemplate(w, "preview", data)
} }
} }

View File

@ -12,7 +12,6 @@ import (
func init() { func init() {
gob.Register(b.User{}) gob.Register(b.User{})
gob.Register(f.ArticlePreviewHtmlData{})
} }
func main() { func main() {
@ -68,13 +67,12 @@ func main() {
mux.HandleFunc("GET /show-all-users-delete", f.ShowAllUsers(config, db, store, "delete-user")) mux.HandleFunc("GET /show-all-users-delete", f.ShowAllUsers(config, db, store, "delete-user"))
mux.HandleFunc("GET /this-issue", f.ShowCurrentArticles(config, db)) mux.HandleFunc("GET /this-issue", f.ShowCurrentArticles(config, db))
mux.HandleFunc("GET /unpublished-articles", f.ShowUnpublishedArticles(config, db)) mux.HandleFunc("GET /unpublished-articles", f.ShowUnpublishedArticles(config, db))
mux.HandleFunc("GET /write-article", f.WriteArticle(config, db)) mux.HandleFunc("GET /write-article", f.WriteArticle(config, db, store))
mux.HandleFunc("POST /add-first-user", f.AddFirstUser(config, db, store)) mux.HandleFunc("POST /add-first-user", f.AddFirstUser(config, db, store))
mux.HandleFunc("POST /add-tag", f.AddTag(config, db, store)) mux.HandleFunc("POST /add-tag", f.AddTag(config, db, store))
mux.HandleFunc("POST /add-user", f.AddUser(config, db, store)) mux.HandleFunc("POST /add-user", f.AddUser(config, db, store))
mux.HandleFunc("POST /login", f.Login(config, db, store)) mux.HandleFunc("POST /login", f.Login(config, db, store))
mux.HandleFunc("POST /preview-article", f.PreviewArticle(config, store))
mux.HandleFunc("POST /resubmit-article/{id}", f.ResubmitArticle(config, db, store)) mux.HandleFunc("POST /resubmit-article/{id}", f.ResubmitArticle(config, db, store))
mux.HandleFunc("POST /submit-article", f.SubmitArticle(config, db, store)) mux.HandleFunc("POST /submit-article", f.SubmitArticle(config, db, store))
mux.HandleFunc("POST /update-self", f.UpdateSelf(config, db, store)) mux.HandleFunc("POST /update-self", f.UpdateSelf(config, db, store))

View File

@ -2,28 +2,24 @@
<h2>Editor</h2> <h2>Editor</h2>
<form id="edit-area"> <form id="edit-area">
<div class="btn-area">
<button class="btn" disabled hx-get="/hub" hx-target="#edit-area">Schreiben</button>
<button class="btn" hx-post="/preview-article" hx-target="#edit-area">Vorschau</button>
</div>
<div class="flex flex-col gap-y-1"> <div class="flex flex-col gap-y-1">
<label for="article-title">Titel</label> <label for="article-title">Titel</label>
<input name="article-title" type="text" /> <input name="article-title" type="text" value="{{.Title}}" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<label for="article-description">Beschreibung</label> <label for="article-description">Beschreibung</label>
<textarea name="article-description"></textarea> <textarea name="article-description">{{.Description}}</textarea>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<label for="article-content">Artikel</label> <label for="article-content">Artikel</label>
<textarea name="article-content"></textarea> <textarea id="easyMDE">{{.Content}}</textarea>
</div> </div>
<input id="article-content" name="article-content" type="hidden" />
<div> <div>
<span>Tags</span> <span>Tags</span>
<div class="flex flex-wrap gap-x-4"> <div class="flex flex-wrap gap-x-4">
{{range .}} {{range .Tags}}
<div> <div>
<input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" /> <input id="{{.Name}}" name="tags" type="checkbox" value="{{.ID}}" />
<label for="{{.Name}}">{{.Name}}</label> <label for="{{.Name}}">{{.Name}}</label>
@ -32,11 +28,6 @@
</div> </div>
</div> </div>
<div id="editor-images">
<input class="mb-2" name="article-image" type="file" hx-encoding="multipart/form-data" hx-post="/upload-image"
hx-swap="beforeend" hx-target="#editor-images" />
</div>
<div class="btn-area"> <div class="btn-area">
<input class="action-btn" type="submit" value="Senden" hx-post="/submit-article" hx-target="#page-content" /> <input class="action-btn" type="submit" value="Senden" hx-post="/submit-article" hx-target="#page-content" />
<button class="btn" hx-get="/hub" hx-target="#page-content">Abbrechen</button> <button class="btn" hx-get="/hub" hx-target="#page-content">Abbrechen</button>
@ -44,51 +35,33 @@
</form> </form>
<script> <script>
function copyToClipboard(text) { var easyMDE = new EasyMDE({
event.preventDefault(); // Get-Request verhindern element: document.getElementById('easyMDE'),
hideIcons: ['image'],
imageTexts: {sbInit: ''},
showIcons: ["code", "table", "upload-image"],
uploadImage: true,
var textarea = document.createElement("textarea"); imageUploadFunction: function (file, onSuccess, onError) {
textarea.textContent = text; var formData = new FormData();
document.body.appendChild(textarea); formData.append('article-image', file);
textarea.select(); fetch('/upload-image', {
try { method: 'POST',
document.execCommand('copy'); body: formData
} catch (err) { })
console.warn('Fehler beim Kopieren', err); .then(response => response.json())
} .then(data => {
onSuccess(data);
})
.catch(error => {
onError(error);
});
},
});
document.body.removeChild(textarea); easyMDE.codemirror.on("change", () => {
} document.getElementById('article-content').value = easyMDE.value();
});
</script> </script>
{{end}} {{end}}
{{define "editor-images"}}
{{if gt (len .) 0}}
<div class="border px-2 py-1 rounded-md flex gap-4 justify-between">
<div class="self-center">{{.}}</div>
<button class="bg-slate-50 border my-2 px-3 py-2 rounded-md w-32 hover:bg-slate-100"
onclick="copyToClipboard('{{.}}')">Kopieren</button>
</div>
{{end}}
{{end}}
{{define "preview"}}
<span>Titel</span>
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
{{.Title}}
</div>
<span>Beschreibung</span>
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
{{.Description}}
</div>
<span>Artikel</span>
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
<div class="prose">
{{.Content}}
</div>
</div>
{{end}}

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Orient Editor</title> <title>Orient Editor</title>
<link href="/web/static/css/style.css" rel="stylesheet"> <link href="/web/static/css/style.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
</head> </head>
<body class="flex flex-col justify-between min-h-screen bg-slate-50"> <body class="flex flex-col justify-between min-h-screen bg-slate-50">
@ -25,7 +26,8 @@
</p> </p>
</footer> </footer>
<script src="/web/static/js/htmx.min.js"></script> <script src="https://unpkg.com/htmx.org@2.0.1"></script>
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
</body> </body>
</html> </html>

View File

@ -14,6 +14,7 @@
<label for="article-content">Artikel</label> <label for="article-content">Artikel</label>
<textarea name="article-content" placeholder="Artikel">{{.Article.Content}}</textarea> <textarea name="article-content" placeholder="Artikel">{{.Article.Content}}</textarea>
</div> </div>
<input id="article-content" name="article-content" type="hidden" />
<div> <div>
<span>Tags</span> <span>Tags</span>
@ -28,11 +29,6 @@
</div> </div>
</div> </div>
<div id="editor-images">
<input class="mb-2" name="article-image" type="file" hx-encoding="multipart/form-data" hx-post="/upload-image"
hx-swap="beforeend" hx-target="#editor-images" />
</div>
<div class="btn-area"> <div class="btn-area">
<input class="action-btn" type="submit" value="Senden" hx-post="/resubmit-article/{{.Article.ID}}" <input class="action-btn" type="submit" value="Senden" hx-post="/resubmit-article/{{.Article.ID}}"
hx-target="#page-content" /> hx-target="#page-content" />
@ -41,32 +37,33 @@
</form> </form>
<script> <script>
function copyToClipboard(text) { var easyMDE = new EasyMDE({
event.preventDefault(); // Get-Request verhindern element: document.getElementById('easyMDE'),
hideIcons: ['image'],
imageTexts: {sbInit: ''},
showIcons: ["code", "table", "upload-image"],
uploadImage: true,
var textarea = document.createElement("textarea"); imageUploadFunction: function (file, onSuccess, onError) {
textarea.textContent = text; var formData = new FormData();
document.body.appendChild(textarea); formData.append('article-image', file);
textarea.select(); fetch('/upload-image', {
try { method: 'POST',
document.execCommand('copy'); body: formData
} catch (err) { })
console.warn('Fehler beim Kopieren', err); .then(response => response.json())
} .then(data => {
onSuccess(data);
document.body.removeChild(textarea); })
} .catch(error => {
onError(error);
});
},
});
easyMDE.codemirror.on("change", () => {
document.getElementById('article-content').value = easyMDE.value();
});
</script> </script>
{{end}} {{end}}
{{define "editor-images"}}
{{if gt (len .) 0}}
<div class="border px-2 py-1 rounded-md flex gap-4 justify-between">
<div class="self-center">{{.}}</div>
<button class="bg-slate-50 border my-2 px-3 py-2 rounded-md w-32 hover:bg-slate-100"
onclick="copyToClipboard('{{.}}')">Kopieren</button>
</div>
{{end}}
{{end}}