begin admin panel, create news
This commit is contained in:
parent
af0ba28c58
commit
c7c76b413c
230
assets/admin.css
Normal file
230
assets/admin.css
Normal file
@ -0,0 +1,230 @@
|
||||
@font-face {
|
||||
font-family: 'Seagram';
|
||||
src: url('/assets/fonts/seagram.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
color: #222;
|
||||
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.seagram {
|
||||
font-family: 'Seagram', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
background-image: url("/assets/images/backgrounds/snowstorm.jpg");
|
||||
}
|
||||
|
||||
i { font-style: italic; }
|
||||
b { font-weight: bold; }
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-family: 'Seagram', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1 { font-size: 2rem; }
|
||||
h2 { font-size: 1.7rem; }
|
||||
h3 { font-size: 1.4rem; }
|
||||
h4 { font-size: 1.1rem; }
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div#container {
|
||||
max-width: 1024px;
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
margin: 0px auto;
|
||||
gap: 1rem;
|
||||
|
||||
& > footer {
|
||||
grid-column: 1 / -1
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
border-right: 2px solid black;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
& > section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #663300;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #330000;
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
button.btn {
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
background-color: #808080;
|
||||
background-image: url("/assets/images/overlay.png");
|
||||
border: 1px solid #808080;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
|
||||
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:hover {
|
||||
background-color: #909090;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
background-color: #F2994A;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #F2C94C, #F2994A);
|
||||
border-color: #F2994A;
|
||||
|
||||
&:hover {
|
||||
background-color: #ffb574;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #ffd965, #ffb574);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-blue {
|
||||
background-color: #00c6ff;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #00c6ff, #0072ff);
|
||||
border-color: #0072ff;
|
||||
|
||||
&:hover {
|
||||
background-color: #49d6fd;
|
||||
background-image: url("/assets/images/overlay.png"), linear-gradient(to bottom, #49d6fd, #2987fa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.standard {
|
||||
& > div.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
span.help {
|
||||
font-size: 0.8em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
& > div.actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input.text {
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.25rem 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
textarea.text {
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.25rem 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
@ -1,9 +1,3 @@
|
||||
/*
|
||||
In general, we don't want to stray too far from the original appearance.
|
||||
We'll optimize the layout for modern CSS and use better styles and fonts
|
||||
for legibility and design, but keep the soul of the original project.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Seagram';
|
||||
src: url('/assets/fonts/seagram.ttf') format('truetype');
|
||||
@ -279,7 +273,7 @@ form.standard {
|
||||
}
|
||||
}
|
||||
|
||||
textarea.forum-text {
|
||||
textarea.text {
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
@ -321,8 +315,8 @@ form.standard {
|
||||
}
|
||||
|
||||
div.town {
|
||||
& > section:not(:last-child) {
|
||||
margin-bottom: 2rem;
|
||||
hr {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
& > section.split {
|
||||
|
BIN
data/dk.db
BIN
data/dk.db
Binary file not shown.
@ -6,6 +6,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"dk/internal/models/users"
|
||||
"dk/internal/template"
|
||||
|
||||
sushi "git.sharkk.net/Sharkk/Sushi"
|
||||
@ -44,7 +45,7 @@ func RenderPage(ctx sushi.Ctx, title, tmplPath string, additionalData map[string
|
||||
"_totaltime": totalTime,
|
||||
"_version": "1.0.0",
|
||||
"_build": "dev",
|
||||
"user": ctx.GetCurrentUser(),
|
||||
"user": ctx.GetCurrentUser().(*users.User),
|
||||
"_memalloc": m.Alloc / 1024 / 1024,
|
||||
"_errormsg": sess.GetFlashMessage("error"),
|
||||
"_successmsg": sess.GetFlashMessage("success"),
|
||||
@ -71,3 +72,44 @@ func PageTitle(title string) string {
|
||||
|
||||
return title + " - Dragon Knight"
|
||||
}
|
||||
|
||||
// RenderAdminPage renders a page using the admin layout template with common data and additional custom data
|
||||
func RenderAdminPage(ctx sushi.Ctx, title, tmplPath string, additionalData map[string]any) error {
|
||||
if template.Cache == nil {
|
||||
return fmt.Errorf("template.Cache not initialized")
|
||||
}
|
||||
|
||||
tmpl, err := template.Cache.Load(tmplPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load layout template: %w", err)
|
||||
}
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
sess := ctx.GetCurrentSession()
|
||||
|
||||
seconds := timing.GetRequestDuration(ctx).Seconds()
|
||||
var totalTime string
|
||||
if seconds < 0.001 {
|
||||
totalTime = fmt.Sprintf("%.0f", seconds)
|
||||
} else {
|
||||
totalTime = fmt.Sprintf("%.3f", seconds)
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"_title": PageTitle(title),
|
||||
"csrf": csrf.HiddenField(ctx),
|
||||
"_totaltime": totalTime,
|
||||
"_version": "1.0.0",
|
||||
"_build": "dev",
|
||||
"user": ctx.GetCurrentUser().(*users.User),
|
||||
"_memalloc": m.Alloc / 1024 / 1024,
|
||||
"_errormsg": sess.GetFlashMessage("error"),
|
||||
"_successmsg": sess.GetFlashMessage("success"),
|
||||
}
|
||||
|
||||
maps.Copy(data, additionalData)
|
||||
|
||||
return tmpl.WriteTo(ctx, data)
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
)
|
||||
|
||||
func GenerateTownNews() string {
|
||||
title := `<h4 class="mb-025">Latest News</h4>`
|
||||
title := `<div class="mb-1"><h4 class="mb-025">Latest News</h4>`
|
||||
|
||||
news, err := news.Recent(1)
|
||||
if err == nil && len(news) > 0 {
|
||||
item := news[0]
|
||||
content := fmt.Sprintf(`<span class="light">%s</span>`, item.ReadableTime())
|
||||
content := fmt.Sprintf(`<span class="light">%s</span></div>`, item.ReadableTime())
|
||||
return title + content + fmt.Sprintf(`<div>%s</div>`, markdown.MarkdownToHTML(item.Content))
|
||||
}
|
||||
|
||||
|
83
internal/routes/admin.go
Normal file
83
internal/routes/admin.go
Normal file
@ -0,0 +1,83 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"dk/internal/components"
|
||||
"dk/internal/models/news"
|
||||
"dk/internal/models/users"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sushi "git.sharkk.net/Sharkk/Sushi"
|
||||
"git.sharkk.net/Sharkk/Sushi/auth"
|
||||
)
|
||||
|
||||
func RegisterAdminRoutes(app *sushi.App) {
|
||||
group := app.Group("/admin")
|
||||
group.Use(auth.RequireAuth())
|
||||
group.Use(func(ctx sushi.Ctx, next func()) {
|
||||
if ctx.GetCurrentUser().(*users.User).Auth < 4 {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
group.Get("/", adminIndex)
|
||||
group.Get("/news", adminNewsForm)
|
||||
group.Post("/news", adminNewsCreate)
|
||||
}
|
||||
|
||||
func adminIndex(ctx sushi.Ctx) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
components.RenderAdminPage(ctx, "", "admin/home.html", map[string]any{
|
||||
"alloc_mb": bToMb(m.Alloc),
|
||||
"total_alloc_mb": bToMb(m.TotalAlloc),
|
||||
"sys_mb": bToMb(m.Sys),
|
||||
"heap_alloc_mb": bToMb(m.HeapAlloc),
|
||||
"heap_sys_mb": bToMb(m.HeapSys),
|
||||
"heap_released_mb": bToMb(m.HeapReleased),
|
||||
"gc_cycles": m.NumGC,
|
||||
"gc_pause_total": m.PauseTotalNs / 1000000, // ms
|
||||
"goroutines": runtime.NumGoroutine(),
|
||||
"cpu_cores": runtime.NumCPU(),
|
||||
"go_version": runtime.Version(),
|
||||
})
|
||||
}
|
||||
|
||||
func adminNewsForm(ctx sushi.Ctx) {
|
||||
components.RenderAdminPage(ctx, "", "admin/news.html", map[string]any{})
|
||||
}
|
||||
|
||||
func adminNewsCreate(ctx sushi.Ctx) {
|
||||
sess := ctx.GetCurrentSession()
|
||||
content := strings.TrimSpace(ctx.Form("content").String())
|
||||
|
||||
if content == "" {
|
||||
sess.SetFlash("error", "Content cannot be empty")
|
||||
ctx.Redirect("/admin/news")
|
||||
return
|
||||
}
|
||||
|
||||
user := ctx.GetCurrentUser().(*users.User)
|
||||
newsPost := &news.News{
|
||||
Author: user.ID,
|
||||
Content: content,
|
||||
Posted: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if err := newsPost.Insert(); err != nil {
|
||||
sess.SetFlash("error", "Failed to create news post")
|
||||
ctx.Redirect("/admin/news")
|
||||
return
|
||||
}
|
||||
|
||||
sess.SetFlash("success", "News post created successfully")
|
||||
ctx.Redirect("/admin")
|
||||
}
|
||||
|
||||
func bToMb(b uint64) uint64 {
|
||||
return b / 1024 / 1024
|
||||
}
|
@ -60,9 +60,9 @@ func (r *replyData) PostedTime() time.Time {
|
||||
func RegisterForumRoutes(app *sushi.App) {
|
||||
authed := app.Group("/forum")
|
||||
authed.Use(auth.RequireAuth())
|
||||
authed.Get("/", index)
|
||||
authed.Get("/", forumIndex)
|
||||
authed.Get("/new", showNew)
|
||||
authed.Post("/new", new)
|
||||
authed.Post("/new", newThread)
|
||||
authed.Get("/:id", showThread)
|
||||
authed.Post("/:id/reply", reply)
|
||||
}
|
||||
@ -95,21 +95,21 @@ func getPagination(ctx sushi.Ctx, perPage int) helpers.Pagination {
|
||||
}
|
||||
}
|
||||
|
||||
func index(ctx sushi.Ctx) {
|
||||
func forumIndex(ctx sushi.Ctx) {
|
||||
pagination := getPagination(ctx, 30)
|
||||
|
||||
// Get threads with user data
|
||||
var threads []*threadData
|
||||
query := `
|
||||
SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.level, 1) as author_level,
|
||||
COALESCE(c.name, 'Unknown') as author_class
|
||||
FROM forum f
|
||||
LEFT JOIN users u ON f.author = u.id
|
||||
LEFT JOIN classes c ON u.class_id = c.id
|
||||
WHERE f.parent = 0
|
||||
ORDER BY f.last_post DESC, f.id DESC
|
||||
WHERE f.parent = 0
|
||||
ORDER BY f.last_post DESC, f.id DESC
|
||||
LIMIT %d OFFSET %d`
|
||||
|
||||
err := database.Select(&threads, query, pagination.PerPage, pagination.Offset())
|
||||
@ -129,10 +129,10 @@ func index(ctx sushi.Ctx) {
|
||||
var lastReply LastReply
|
||||
err := database.Get(&lastReply, `
|
||||
SELECT COALESCE(u.username, '[Deleted]') as username
|
||||
FROM forum f
|
||||
LEFT JOIN users u ON f.author = u.id
|
||||
WHERE f.parent = %d
|
||||
ORDER BY f.posted DESC
|
||||
FROM forum f
|
||||
LEFT JOIN users u ON f.author = u.id
|
||||
WHERE f.parent = %d
|
||||
ORDER BY f.posted DESC
|
||||
LIMIT 1`, threads[i].ID)
|
||||
|
||||
if err == nil {
|
||||
@ -163,7 +163,7 @@ func showNew(ctx sushi.Ctx) {
|
||||
components.RenderPage(ctx, "New Forum Thread", "forum/new.html", map[string]any{})
|
||||
}
|
||||
|
||||
func new(ctx sushi.Ctx) {
|
||||
func newThread(ctx sushi.Ctx) {
|
||||
sess := ctx.GetCurrentSession()
|
||||
|
||||
title := strings.TrimSpace(ctx.Form("title").String())
|
||||
@ -212,7 +212,7 @@ func showThread(ctx sushi.Ctx) {
|
||||
var threadData threadData
|
||||
err := database.Get(&threadData, `
|
||||
SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.level, 1) as author_level,
|
||||
COALESCE(c.name, 'Unknown') as author_class
|
||||
FROM forum f
|
||||
@ -232,14 +232,14 @@ func showThread(ctx sushi.Ctx) {
|
||||
var replies []*replyData
|
||||
err = database.Select(&replies, `
|
||||
SELECT f.id, f.posted, f.author, f.title, f.content,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.username, '[Deleted]') as author_username,
|
||||
COALESCE(u.level, 1) as author_level,
|
||||
COALESCE(c.name, 'Unknown') as author_class
|
||||
FROM forum f
|
||||
LEFT JOIN users u ON f.author = u.id
|
||||
LEFT JOIN classes c ON u.class_id = c.id
|
||||
WHERE f.parent = %d
|
||||
ORDER BY f.posted ASC
|
||||
WHERE f.parent = %d
|
||||
ORDER BY f.posted ASC
|
||||
LIMIT %d OFFSET %d`, id, pagination.PerPage, pagination.Offset())
|
||||
|
||||
if err != nil {
|
||||
|
1
main.go
1
main.go
@ -188,6 +188,7 @@ func start(port string) error {
|
||||
routes.RegisterFightRoutes(app)
|
||||
routes.RegisterForumRoutes(app)
|
||||
routes.RegisterHelpRoutes(app)
|
||||
routes.RegisterAdminRoutes(app)
|
||||
|
||||
app.Get("/assets/*path", sushi.Static(cwd))
|
||||
|
||||
|
24
templates/admin/home.html
Normal file
24
templates/admin/home.html
Normal file
@ -0,0 +1,24 @@
|
||||
{include "admin/layout.html"}
|
||||
|
||||
{block "content"}
|
||||
<h1>Admininistration</h1>
|
||||
|
||||
<p>
|
||||
Welcome to the admin panel! Utilize the tools to the left, or <a href="/">head back</a>. Below
|
||||
are the server stats as of loading this page.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<tr><td>Memory Allocated</td> <td>{alloc_mb} MB</td></tr>
|
||||
<tr><td>Total Allocated</td> <td>{total_alloc_mb} MB</td></tr>
|
||||
<tr><td>System Memory</td> <td>{sys_mb} MB</td></tr>
|
||||
<tr><td>Heap Allocated</td> <td>{heap_alloc_mb} MB</td></tr>
|
||||
<tr><td>Heap System</td> <td>{heap_sys_mb} MB</td></tr>
|
||||
<tr><td>Heap Released</td> <td>{heap_released_mb} MB</td></tr>
|
||||
<tr><td>GC Cycles</td> <td>{gc_cycles}</td></tr>
|
||||
<tr><td>GC Pause Total</td> <td>{gc_pause_total} ms</td></tr>
|
||||
<tr><td>Goroutines</td> <td>{goroutines}</td></tr>
|
||||
<tr><td>CPU Cores</td> <td>{cpu_cores}</td></tr>
|
||||
<tr><td>Go Version</td> <td>{go_version}</td></tr>
|
||||
</table>
|
||||
{/block}
|
43
templates/admin/layout.html
Normal file
43
templates/admin/layout.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{_title}</title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/reset.css">
|
||||
<link rel="stylesheet" href="/assets/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<nav>
|
||||
<section>
|
||||
<a href="/admin">Admin Home</a>
|
||||
<a href="/">Game Home</a>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<a href="/admin/control">Main Settings</a>
|
||||
<a href="/admin/news">Add News Post</a>
|
||||
<a href="/admin/users">Edit Users</a>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<a href="/admin/items">Edit Items</a>
|
||||
<a href="/admin/towns">Edit Towns</a>
|
||||
<a href="/admin/monsters">Edit Monsters</a>
|
||||
<a href="/admin/spells">Edit Spells</a>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div>{_totaltime} Seconds, {_memalloc} MiB</div>
|
||||
<div>Version {_version} {_build}</div>
|
||||
</footer>
|
||||
</nav>
|
||||
<main>
|
||||
{include "flashes.html"}
|
||||
{yield "content"}
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
20
templates/admin/news.html
Normal file
20
templates/admin/news.html
Normal file
@ -0,0 +1,20 @@
|
||||
{include "admin/layout.html"}
|
||||
|
||||
{block "content"}
|
||||
<h1>Add News Post</h1>
|
||||
|
||||
<p>
|
||||
Write a new news post that will be displayed in every town! You can use basic markdown to style the post.
|
||||
</p>
|
||||
|
||||
<form class="standard" method="post">
|
||||
{csrf}
|
||||
<div>
|
||||
<textarea name="content" id="content" class="text w-full mb-1" required placholder="Write the news here!"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/admin"><button type="button" class="btn">Cancel</button></a>
|
||||
<button type="submit" class="btn btn-primary">Create Post</button>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
@ -8,7 +8,7 @@
|
||||
|
||||
<div class="mb-1">
|
||||
<div class="mb-025"><input type="text" class="text w-full" placeholder="Thread title" name="title"></div>
|
||||
<textarea class="forum-text w-full" placeholder="Type your message here!" name="content"></textarea>
|
||||
<textarea class="text w-full" placeholder="Type your message here!" name="content"></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -51,9 +51,9 @@
|
||||
{if hasPrev}
|
||||
<a href="/forum/{thread.ID}?page={currentPage - 1}"><button class="btn">Previous</button></a>
|
||||
{/if}
|
||||
|
||||
|
||||
<span>Page {currentPage} of {totalPages}</span>
|
||||
|
||||
|
||||
{if hasNext}
|
||||
<a href="/forum/{thread.ID}?page={currentPage + 1}"><button class="btn">Next</button></a>
|
||||
{/if}
|
||||
@ -66,7 +66,7 @@
|
||||
{csrf}
|
||||
|
||||
<div class="mb-1">
|
||||
<textarea class="forum-text w-full" placeholder="Type your reply here!" name="content" rows="10"></textarea>
|
||||
<textarea class="text w-full" placeholder="Type your reply here!" name="content"></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -74,4 +74,4 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/block}
|
||||
{/block}
|
||||
|
@ -50,6 +50,9 @@
|
||||
<a href="/change-password">Change Password</a>
|
||||
<a class="logout-link">Log Out</a>
|
||||
<a href="/help">Help</a>
|
||||
{if user.Auth >= 4}
|
||||
<a href="/admin">Admin</a>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<form id="logout-form" class="hidden" action="/logout" method="post">
|
||||
|
@ -11,10 +11,14 @@
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
|
||||
<section class="news">
|
||||
{newscontent}
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
|
||||
<section class="split">
|
||||
<section class="whos-online">
|
||||
{whosonline}
|
||||
|
Loading…
x
Reference in New Issue
Block a user