Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
c722135a56 | |||
391b3bf157 | |||
0aa479763d | |||
ff0e229f03 | |||
8ef6ff729e | |||
e4624b8705 | |||
887fa863bc | |||
4592bdf970 | |||
dadd610b2d | |||
74d71cfb6a | |||
4004bcb8f0 | |||
9e0182ed03 | |||
e554174c28 | |||
8597f1b849 | |||
b04e0e5e81 | |||
ca7e7cddd3 | |||
62921c8e2a | |||
94431a2aa9 | |||
c7761e2dc8 | |||
5b1f20c5bc | |||
013cddc157 | |||
523ff9d2db | |||
84960fdd44 | |||
d0c566f8df | |||
0a387c1087 | |||
38caf02b2c | |||
435157a42f | |||
4163afb0b7 | |||
5e586aa49a | |||
6cc0fb40d7 |
@ -5,13 +5,16 @@ tmp_dir = "tmp"
|
||||
[build]
|
||||
args_bin = [
|
||||
"-articles tmp/articles",
|
||||
"-config tmp/config.toml",
|
||||
"-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'",
|
||||
"-domain localhost",
|
||||
"-firebase tmp/firebase.json",
|
||||
"-key tmp/key.gob",
|
||||
"-link https://distrikt-ni-st.de",
|
||||
"-log tmp/cpolis.log",
|
||||
"-pdfs tmp/pdfs",
|
||||
"-pics tmp/pics",
|
||||
"-port 8080",
|
||||
"-rss tmp/orientexpress_alle.rss",
|
||||
"-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'",
|
||||
"-web web",
|
||||
|
@ -9,18 +9,18 @@ import (
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
Title string
|
||||
Created time.Time
|
||||
Title string
|
||||
Description string
|
||||
Link string
|
||||
EncURL string
|
||||
EncLength int
|
||||
EncType string
|
||||
Published bool
|
||||
Rejected bool
|
||||
ID int64
|
||||
AuthorID int64
|
||||
IssueID int64
|
||||
EncLength int
|
||||
Published bool
|
||||
Rejected bool
|
||||
IsInIssue bool
|
||||
AutoGenerated bool
|
||||
}
|
||||
@ -49,7 +49,6 @@ 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.IsInIssue, a.AutoGenerated)
|
||||
|
@ -3,6 +3,7 @@ package backend
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
|
||||
type Config struct {
|
||||
ArticleDir string
|
||||
ConfigFile string
|
||||
DBName string
|
||||
Description string
|
||||
Domain string
|
||||
@ -30,56 +32,72 @@ type Config struct {
|
||||
func newConfig() *Config {
|
||||
return &Config{
|
||||
ArticleDir: "/var/www/cpolis/articles",
|
||||
ConfigFile: "/etc/cpolis/config.toml",
|
||||
DBName: "cpolis",
|
||||
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
|
||||
KeyFile: "/var/www/cpolis/cpolis.key",
|
||||
LogFile: "/var/log/cpolis.log",
|
||||
PDFDir: "/var/www/cpolis/pdfs",
|
||||
PicsDir: "/var/www/cpolis/pics",
|
||||
Port: ":8080",
|
||||
RSSFile: "/var/www/cpolis/cpolis.rss",
|
||||
WebDir: "/var/www/cpolis/web",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) readFile() error {
|
||||
cfgFile, err := filepath.Abs(os.Getenv("HOME") + "/.config/cpolis/config.toml")
|
||||
func mkDir(path string, perm fs.FileMode) (string, error) {
|
||||
var err error
|
||||
|
||||
stringSlice := strings.Split(path, "/")
|
||||
name := stringSlice[len(stringSlice)-1]
|
||||
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting absolute path for config file: %v", err)
|
||||
return "", fmt.Errorf("error finding absolute path for %v directory: %v", name, err)
|
||||
}
|
||||
if err = os.MkdirAll(path, perm); err != nil {
|
||||
return "", fmt.Errorf("error creating %v directory: %v", name, err)
|
||||
}
|
||||
|
||||
_, err = os.Stat(cfgFile)
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func mkFile(path string, filePerm, dirPerm fs.FileMode) (string, error) {
|
||||
var err error
|
||||
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error finding absolute path for %v: %v", path, err)
|
||||
}
|
||||
|
||||
stringSlice := strings.Split(path, "/")
|
||||
_, err = os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
fileStrings := strings.Split(cfgFile, "/")
|
||||
|
||||
dir := strings.Join(fileStrings[0:len(fileStrings)-1], "/")
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating config directory: %v", err)
|
||||
dir := strings.Join(stringSlice[:len(stringSlice)-1], "/")
|
||||
if err = os.MkdirAll(dir, dirPerm); err != nil {
|
||||
return "", fmt.Errorf("error creating %v: %v", dir, err)
|
||||
}
|
||||
|
||||
fileName := fileStrings[len(fileStrings)-1]
|
||||
fileName := stringSlice[len(stringSlice)-1]
|
||||
file, err := os.Create(dir + "/" + fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating config file: %v", err)
|
||||
return "", fmt.Errorf("error creating %v: %v", fileName, err)
|
||||
}
|
||||
defer file.Close()
|
||||
if err = file.Chmod(0644); err != nil {
|
||||
return fmt.Errorf("error setting permissions for config file: %v", err)
|
||||
}
|
||||
} else {
|
||||
_, err = toml.DecodeFile(cfgFile, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading config file: %v", err)
|
||||
if err = file.Chmod(filePerm); err != nil {
|
||||
return "", fmt.Errorf("error setting permissions for %v: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (c *Config) handleCliArgs() error {
|
||||
var port int
|
||||
var err error
|
||||
port := 8080
|
||||
|
||||
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
|
||||
flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
|
||||
flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
|
||||
flag.StringVar(&c.Description, "desc", c.Description, "channel description")
|
||||
flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
|
||||
@ -95,58 +113,114 @@ func (c *Config) handleCliArgs() error {
|
||||
flag.IntVar(&port, "port", port, "port")
|
||||
flag.Parse()
|
||||
|
||||
c.ArticleDir, err = filepath.Abs(c.ArticleDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for articles directory: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(c.ArticleDir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating articles directory: %v", err)
|
||||
}
|
||||
|
||||
c.FirebaseKey, err = filepath.Abs(c.FirebaseKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for Firebase service account key file: %v", err)
|
||||
}
|
||||
|
||||
c.KeyFile, err = filepath.Abs(c.KeyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for key file: %v", err)
|
||||
}
|
||||
|
||||
c.LogFile, err = filepath.Abs(c.LogFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for log file: %v", err)
|
||||
}
|
||||
|
||||
c.PDFDir, err = filepath.Abs(c.PDFDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for pdfs directory: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(c.PDFDir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating pdfs directory: %v", err)
|
||||
}
|
||||
|
||||
c.PicsDir, err = filepath.Abs(c.PicsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for pics directory: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(c.PicsDir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating pics directory: %v", err)
|
||||
}
|
||||
|
||||
c.Port = fmt.Sprint(":", port)
|
||||
|
||||
c.RSSFile, err = filepath.Abs(c.RSSFile)
|
||||
c.ConfigFile, err = mkFile(c.ConfigFile, 0600, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for RSS file: %v", err)
|
||||
return fmt.Errorf("error setting up file: %v", err)
|
||||
}
|
||||
|
||||
c.WebDir, err = filepath.Abs(c.WebDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) handleFile(configFile string) error {
|
||||
_, err := toml.DecodeFile(configFile, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding absolute path for web directory: %v", err)
|
||||
return fmt.Errorf("error reading config file: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(c.WebDir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating web directory: %v", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) setupConfig(cliConfig *Config) error {
|
||||
var err error
|
||||
defaultConfig := newConfig()
|
||||
|
||||
if cliConfig.ArticleDir != defaultConfig.ArticleDir {
|
||||
c.ArticleDir = cliConfig.ArticleDir
|
||||
}
|
||||
c.ArticleDir, err = mkDir(c.ArticleDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up directory: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.DBName != defaultConfig.DBName {
|
||||
c.DBName = cliConfig.DBName
|
||||
}
|
||||
|
||||
if cliConfig.Description != defaultConfig.Description {
|
||||
c.Description = cliConfig.Description
|
||||
}
|
||||
|
||||
if cliConfig.Domain != defaultConfig.Domain {
|
||||
c.Domain = cliConfig.Domain
|
||||
}
|
||||
|
||||
if cliConfig.FirebaseKey != defaultConfig.FirebaseKey {
|
||||
c.FirebaseKey = cliConfig.FirebaseKey
|
||||
}
|
||||
c.FirebaseKey, err = mkFile(c.FirebaseKey, 0600, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up file: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.KeyFile != defaultConfig.KeyFile {
|
||||
c.KeyFile = cliConfig.KeyFile
|
||||
}
|
||||
c.KeyFile, err = mkFile(c.KeyFile, 0600, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up file: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.Link != defaultConfig.Link {
|
||||
c.Link = cliConfig.Link
|
||||
}
|
||||
|
||||
if cliConfig.LogFile != defaultConfig.LogFile {
|
||||
c.LogFile = cliConfig.LogFile
|
||||
}
|
||||
c.LogFile, err = mkFile(c.LogFile, 0600, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up file: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.PDFDir != defaultConfig.PDFDir {
|
||||
c.PDFDir = cliConfig.PDFDir
|
||||
}
|
||||
c.PDFDir, err = mkDir(c.PDFDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up directory: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.PicsDir != defaultConfig.PicsDir {
|
||||
c.PicsDir = cliConfig.PicsDir
|
||||
}
|
||||
c.PicsDir, err = mkDir(c.PicsDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up directory: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.Port != defaultConfig.Port {
|
||||
c.Port = cliConfig.Port
|
||||
}
|
||||
|
||||
if cliConfig.RSSFile != defaultConfig.RSSFile {
|
||||
c.RSSFile = cliConfig.RSSFile
|
||||
}
|
||||
c.RSSFile, err = mkFile(c.RSSFile, 0600, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up file: %v", err)
|
||||
}
|
||||
|
||||
if cliConfig.Title != defaultConfig.Title {
|
||||
c.Title = cliConfig.Title
|
||||
}
|
||||
|
||||
if cliConfig.WebDir != defaultConfig.WebDir {
|
||||
c.WebDir = cliConfig.WebDir
|
||||
}
|
||||
c.WebDir, err = mkDir(c.WebDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up directory: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -154,13 +228,19 @@ func (c *Config) handleCliArgs() error {
|
||||
|
||||
func HandleConfig() (*Config, error) {
|
||||
config := newConfig()
|
||||
cliConfig := newConfig()
|
||||
|
||||
if err := config.readFile(); err != nil {
|
||||
return nil, fmt.Errorf("error reading config file: %v", err)
|
||||
if err := cliConfig.handleCliArgs(); err != nil {
|
||||
return nil, fmt.Errorf("error handling cli arguments: %v", err)
|
||||
}
|
||||
|
||||
if err := config.handleCliArgs(); err != nil {
|
||||
return nil, fmt.Errorf("error handling cli arguments: %v", err)
|
||||
err := config.handleFile(cliConfig.ConfigFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading configuration file: %v", err)
|
||||
}
|
||||
|
||||
if err = config.setupConfig(cliConfig); err != nil {
|
||||
return nil, fmt.Errorf("error setting up files: %v", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
@ -8,9 +8,7 @@ import (
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*auth.Client
|
||||
}
|
||||
type Client struct{ *auth.Client }
|
||||
|
||||
func NewClient(c *Config) (*Client, error) {
|
||||
var err error
|
||||
|
@ -10,9 +10,7 @@ import (
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type CookieStore struct {
|
||||
sessions.CookieStore
|
||||
}
|
||||
type CookieStore struct{ sessions.CookieStore }
|
||||
|
||||
func NewKey() ([]byte, error) {
|
||||
key := make([]byte, 32)
|
||||
|
@ -29,7 +29,7 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
type editorHTMLData struct {
|
||||
type htmlData struct {
|
||||
Title string
|
||||
Description string
|
||||
Content string
|
||||
@ -37,12 +37,12 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
|
||||
Tags []*b.Tag
|
||||
Mode int
|
||||
}
|
||||
var data htmlData
|
||||
|
||||
var data editorHTMLData
|
||||
if session.Values["article"] == nil {
|
||||
data = editorHTMLData{}
|
||||
data = htmlData{}
|
||||
} else {
|
||||
data = session.Values["article"].(editorHTMLData)
|
||||
data = session.Values["article"].(htmlData)
|
||||
}
|
||||
data.Mode = EditMode
|
||||
|
||||
@ -211,11 +211,10 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF
|
||||
return
|
||||
}
|
||||
|
||||
type htmlData struct {
|
||||
data := new(struct {
|
||||
MyIDs map[int64]bool
|
||||
RejectedArticles []*b.Article
|
||||
}
|
||||
data := new(htmlData)
|
||||
})
|
||||
|
||||
data.RejectedArticles, err = db.GetCertainArticles(false, true)
|
||||
if err != nil {
|
||||
@ -530,16 +529,14 @@ func ReviewArticleForDeletion(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand
|
||||
return
|
||||
}
|
||||
|
||||
type htmlData struct {
|
||||
var err error
|
||||
data := new(struct {
|
||||
Title string
|
||||
Description string
|
||||
Content template.HTML
|
||||
Tags []*b.Tag
|
||||
ID int64
|
||||
}
|
||||
|
||||
var err error
|
||||
data := new(htmlData)
|
||||
})
|
||||
|
||||
data.ID, err = strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
|
@ -277,12 +277,12 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H
|
||||
return
|
||||
}
|
||||
|
||||
type htmlData struct {
|
||||
data := new(struct {
|
||||
Users map[int64]*b.User
|
||||
Action string
|
||||
}
|
||||
})
|
||||
|
||||
data := &htmlData{Action: action}
|
||||
data.Action = action
|
||||
data.Users, err = db.GetAllUsers()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
@ -7,4 +7,5 @@ module.exports = {
|
||||
plugins: [
|
||||
require('@tailwindcss/typography')
|
||||
],
|
||||
darkMode: 'selector',
|
||||
}
|
||||
|
@ -2,40 +2,71 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
width: 800px;
|
||||
@apply mx-auto text-slate-900;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-bold mb-2 text-2xl;
|
||||
}
|
||||
|
||||
form {
|
||||
@apply flex flex-col gap-y-3;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
@apply border rounded-md w-full;
|
||||
h3 {
|
||||
@apply font-bold mb-2 text-xl;
|
||||
}
|
||||
|
||||
input[type="password"],
|
||||
input[type="text"] {
|
||||
@apply border h-8 rounded-md;
|
||||
@apply bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 h-8 rounded-md;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@apply border h-32 rounded-md;
|
||||
@apply bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 h-32 rounded-md;
|
||||
}
|
||||
|
||||
.btn-area-1 {
|
||||
@apply grid grid-cols-1 gap-x-4 gap-y-1 mt-4;
|
||||
}
|
||||
|
||||
.btn-area {
|
||||
@apply flex gap-4 mt-4;
|
||||
@apply grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1 mt-4;
|
||||
}
|
||||
|
||||
.btn-area-3 {
|
||||
@apply grid grid-cols-1 md:grid-cols-3 gap-x-4 gap-y-1 mt-4;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply bg-slate-200 border my-2 px-3 py-2 rounded-md w-full hover:bg-slate-100;
|
||||
@apply bg-slate-200 dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-900 border border-slate-200 dark:border-slate-800 my-2 px-3 py-2 rounded-md w-full;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@apply bg-slate-800 border my-2 px-3 py-2 rounded-md text-slate-50 w-full hover:bg-slate-700;
|
||||
@apply bg-slate-800 dark:bg-slate-200 hover:bg-slate-700 dark:hover:bg-slate-300 my-2 px-3 py-2 rounded-md text-slate-50 dark:text-slate-950 w-full;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror {
|
||||
@apply bg-slate-50 dark:bg-slate-950 border-slate-200 dark:border-slate-800 text-slate-900 dark:text-slate-100
|
||||
}
|
||||
|
||||
.EasyMDEContainer .cm-s-easymde .CodeMirror-cursor {
|
||||
@apply border-slate-900 dark:border-slate-100
|
||||
}
|
||||
|
||||
.EasyMDEContainer .editor-toolbar > * {
|
||||
@apply text-slate-900 dark:text-slate-100
|
||||
}
|
||||
|
||||
.EasyMDEContainer .editor-toolbar > .active, .editor-toolbar > button:hover, .editor-preview pre, .cm-s-easymde .cm-comment {
|
||||
@apply bg-slate-100 dark:bg-slate-900
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror-fullscreen {
|
||||
@apply bg-slate-50 dark:bg-slate-950
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
@apply border border-slate-200 dark:border-slate-800
|
||||
}
|
||||
|
||||
.editor-toolbar.fullscreen {
|
||||
@apply bg-slate-50 dark:bg-slate-950
|
||||
}
|
||||
|
||||
.editor-preview {
|
||||
@apply bg-slate-50 dark:bg-slate-950
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2>Neuer Tag</h2>
|
||||
|
||||
<form>
|
||||
<input required name="tag" placeholder="Tag eingeben" type="text" />
|
||||
<input class="w-full" name="tag" placeholder="Tag eingeben" required type="text" />
|
||||
<div class="btn-area">
|
||||
<input class="action-btn" type="submit" value="Anlegen" hx-post="/tag/add" hx-target="#page-content" />
|
||||
<button class="btn" hx-get="/hub" hx-target="#page-content">Abbrechen</button>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2>Neuer Benutzer</h2>
|
||||
|
||||
<form>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="username">Benutzername</label>
|
||||
<input class="w-full" required name="username" type="text" value="{{.UserName}}" />
|
||||
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div>
|
||||
<input required id="author" name="role" type="radio" value="3" {{if eq .Role 3 }}checked{{end}} />
|
||||
<label for="author">Autor</label>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h3>Aktuelle Artikel</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
{{range .}}
|
||||
<div class="border px-2 py-1 rounded-md">
|
||||
<div class="border border-slate-200 dark:border-slate-800 px-2 py-1 rounded-md">
|
||||
<h1 class="font-bold text-2xl">{{.Title}}</h1>
|
||||
<p>{{.Description}}</p>
|
||||
</div>
|
||||
@ -16,21 +16,15 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Cover</h3>
|
||||
<div class="flex">
|
||||
<h3>Titelseite</h3>
|
||||
<div class="grid grid-cols-2 gap-4 items-center">
|
||||
<input class="h-full" name="issue-title" placeholder="Titel" type="text" />
|
||||
<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>
|
||||
<h3>Über diese Ausgabe</h3>
|
||||
<div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2>Profil bearbeiten</h2>
|
||||
|
||||
<form>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="username">Benutzername</label>
|
||||
<input class="w-full" name="username" type="text" value="{{.UserName}}" />
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2>Profil von {{.FirstName}} {{.LastName}} bearbeiten</h2>
|
||||
|
||||
<form>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="username">Benutzername</label>
|
||||
<input class="w-full" name="username" type="text" value="{{.UserName}}" />
|
||||
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div>
|
||||
<input required id="author" name="role" type="radio" value="3" {{if eq .Role 3 }}checked{{end}} />
|
||||
<label for="author">Autor</label>
|
||||
|
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-area">
|
||||
<div class="btn-area-1">
|
||||
<input class="action-btn" type="submit" value="Anlegen" hx-post="/user/add-first" hx-target="#page-content" />
|
||||
</div>
|
||||
</form>
|
||||
|
@ -4,49 +4,49 @@
|
||||
|
||||
{{if lt . 4}}
|
||||
<div class="mb-3">
|
||||
<h2>Autor</h2>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<h2>Artikel</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button>
|
||||
<button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Abgelehnte Artikel</button>
|
||||
<button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Profil bearbeiten</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if lt . 3}}
|
||||
<div class="mb-3">
|
||||
<h2>Redakteur</h2>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/article/all-unpublished" hx-target="#page-content">
|
||||
{{if lt . 3}}<button class="btn" hx-get="/article/all-unpublished" hx-target="#page-content">
|
||||
Unveröffentlichte Artikel
|
||||
</button>
|
||||
<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer Tag</button>
|
||||
</button>{{end}}
|
||||
{{if lt . 2}}<button class="btn" hx-get="/article/all-published" hx-target="#page-content">
|
||||
Artikel löschen
|
||||
</button>{{end}}
|
||||
{{if lt . 3}}<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer Tag</button>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if lt . 2}}
|
||||
<div class="mb-3">
|
||||
<h2>Herausgeber</h2>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<h2>Ausgabe</h2>
|
||||
<div class="grid grid-cols-1 md: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}}
|
||||
</div>
|
||||
|
||||
{{if eq . 0}}
|
||||
{{if lt . 4}}
|
||||
<div class="mb-3">
|
||||
<h2>Administrator</h2>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer hinzufügen</button>
|
||||
<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer bearbeiten</button>
|
||||
<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer löschen</button>
|
||||
<h2>Benutzer</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
|
||||
<button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Mein Profil bearbeiten</button>
|
||||
{{if eq . 0}}<button class="btn" hx-get="/user/create" hx-target="#page-content">
|
||||
Benutzer hinzufügen
|
||||
</button>{{end}}
|
||||
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">
|
||||
Benutzer bearbeiten
|
||||
</button>{{end}}
|
||||
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">
|
||||
Benutzer löschen
|
||||
</button>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -5,13 +5,26 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Orient Editor</title>
|
||||
|
||||
<link href="/web/static/css/style.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col justify-between min-h-screen bg-slate-50">
|
||||
<body
|
||||
class="bg-slate-50 dark:bg-slate-950 container flex flex-col justify-between max-w-screen-lg min-h-screen mx-auto text-slate-900 dark:text-slate-100">
|
||||
<header class="my-8">
|
||||
<h1 class="font-bold text-4xl text-center">Orient Editor</h1>
|
||||
|
||||
<div class="ml-4">
|
||||
<label class="cursor-pointer flex items-center relative" for="theme-toggle">
|
||||
<div class="bg-slate-200 dark:bg-slate-800 block h-6 w-12 rounded-full"></div>
|
||||
<div
|
||||
class="absolute bg-slate-800 dark:bg-slate-50 dot left-1 top-1 h-4 w-4 rounded-full transform transition">
|
||||
</div>
|
||||
<span class="ml-2">Dunkel</span>
|
||||
</label>
|
||||
<input type="checkbox" id="theme-toggle" class="sr-only">
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="mx-4">
|
||||
@ -20,17 +33,44 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="my-8">
|
||||
<p class="text-center text-gray-500 dark:text-gray-400">
|
||||
<footer class="text-center text-gray-500 my-8">
|
||||
<p>
|
||||
© 2024 Jason Streifling. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
<p class="text-center text-gray-500 dark:text-gray-400">
|
||||
<strong>Hinweis:</strong> Diese Software befindet sich noch in der Entwicklung und kann Fehler enthalten.
|
||||
<p>
|
||||
v0.10.2 - <strong>Hinweis:</strong> Diese Software befindet sich noch in der Entwicklung und kann Fehler
|
||||
enthalten.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="https://unpkg.com/htmx.org@2.0.1"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.2"></script>
|
||||
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggleSwitch = document.getElementById('theme-toggle');
|
||||
const dot = document.querySelector('.dot');
|
||||
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
toggleSwitch.checked = true;
|
||||
dot.classList.add('translate-x-6');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
|
||||
toggleSwitch.addEventListener('change', () => {
|
||||
if (toggleSwitch.checked) {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.theme = 'dark';
|
||||
dot.classList.add('translate-x-6');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.theme = 'light';
|
||||
dot.classList.remove('translate-x-6');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -2,11 +2,13 @@
|
||||
<h2>Anmeldung</h2>
|
||||
|
||||
<form>
|
||||
<div class="btn-area">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-1">
|
||||
<input class="w-full" name="username" placeholder="Benutzername" type="text" />
|
||||
<input class="w-full" name="password" placeholder="Passwort" type="password" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<input class="action-btn" type="submit" value="Anmelden" hx-post="/login" hx-target="#page-content" />
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<label for="easyMDE">Artikel</label>
|
||||
<textarea id="easyMDE">{{.Content}}</textarea>
|
||||
<input id="article-content" name="article-content" type="hidden" />
|
||||
<input id="article-content" name="article-content" type="hidden" value="{{.Content}}" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -44,8 +44,6 @@
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.getElementById('article-content').value = easyMDE.value();
|
||||
|
||||
var easyMDE = new EasyMDE({
|
||||
element: document.getElementById('easyMDE'),
|
||||
hideIcons: ['image'],
|
||||
|
@ -3,24 +3,24 @@
|
||||
|
||||
<div>
|
||||
<span>Titel</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 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">
|
||||
<div class="border border-slate-200 dark:border-slate-800 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">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="prose text-slate-900 dark:text-slate-100">
|
||||
{{.Content}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span>Tags</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{range .Tags}}
|
||||
{{.Name}}
|
||||
<br>
|
||||
|
@ -3,24 +3,24 @@
|
||||
|
||||
<div>
|
||||
<span>Titel</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{.Article.Title}}
|
||||
</div>
|
||||
|
||||
<span>Beschreibung</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{.Article.Description}}
|
||||
</div>
|
||||
|
||||
<span>Artikel</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="prose">
|
||||
{{.Content}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span>Tags</span>
|
||||
<div class="bg-white border mb-3 px-2 py-2 rounded-md w-full">
|
||||
<div class="border border-slate-200 dark:border-slate-800 mb-3 px-2 py-2 rounded-md w-full">
|
||||
{{if .Article.IsInIssue}}
|
||||
<span>Orient Express</span>
|
||||
<br>
|
||||
@ -31,7 +31,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="btn-area">
|
||||
<div class="btn-area-3">
|
||||
<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}}"
|
||||
|
Reference in New Issue
Block a user