summaryrefslogtreecommitdiffstats
path: root/templates/admin
diff options
context:
space:
mode:
Diffstat (limited to 'templates/admin')
-rw-r--r--templates/admin/error.html7
-rw-r--r--templates/admin/form.html82
-rw-r--r--templates/admin/index.html36
-rw-r--r--templates/admin/list.html50
-rw-r--r--templates/admin/settings.html20
-rw-r--r--templates/admin/setup.html47
6 files changed, 242 insertions, 0 deletions
diff --git a/templates/admin/error.html b/templates/admin/error.html
new file mode 100644
index 0000000..36a4a8a
--- /dev/null
+++ b/templates/admin/error.html
@@ -0,0 +1,7 @@
+{{define "error-content"}}
+ <div class="alert">
+ <h2>Error</h2>
+ <p>{{.Message}}</p>
+ <p><a href="javascript:history.back()">Go back</a></p>
+ </div>
+{{end}}
diff --git a/templates/admin/form.html b/templates/admin/form.html
new file mode 100644
index 0000000..133951f
--- /dev/null
+++ b/templates/admin/form.html
@@ -0,0 +1,82 @@
+{{define "form-content"}}
+<form method="POST" action="{{.Action}}" id="postForm">
+ <label for="title">Title</label>
+ <input type="text" id="title" name="title" value="{{.Post.Title}}" required autofocus>
+
+ {{if not .IsNew}}
+ <label for="slug">Slug</label>
+ <input type="text" id="slug" name="slug" value="{{.Post.Slug}}">
+ {{end}}
+
+ <label for="date">Date</label>
+ <input type="date" id="date" name="date" value="{{.Post.Date}}">
+
+ <label for="tags">Tags</label>
+ <input type="text" id="tags" name="tags" value="{{range $i, $t := .Post.Tags}}{{if $i}}, {{end}}{{$t}}{{end}}"
+ placeholder="tag1, tag2, tag3">
+ <label for="draft">
+ <input type="checkbox" id="draft" name="draft" {{if .Post.Draft}} checked{{end}}>
+ Draft (not published)
+ </label>
+ <button type="submit" class="btn btn-primary">
+ {{if .IsNew}}Create Post{{else}}Save Changes{{end}}
+ </button>
+ <a href="/admin/" class="btn btn-secondary">Cancel</a>
+ <div id="editor" style="border: 1px solid #ccc; border-radius: 4px; min-height: 340px;"></div>
+ <input type="hidden" id="blocks" name="blocks" value="{{.Post.Blocks}}">
+</form>
+<script type="module">
+ import { EditorJS, Header, Paragraph, List, Code, Quote, ImageTool } from 'editorjs-bundle'
+ import ComponentTool from '/assets/admin/lib/component-tool.js'
+ import { qs, on } from 'shared'
+
+ const blocksField = qs('#blocks')
+ const form = qs('#postForm')
+
+ let editorData = { blocks: [] }
+ try {
+ const parsed = JSON.parse(blocksField.value || '[]')
+ if (Array.isArray(parsed)) {
+ editorData = { blocks: parsed }
+ } else if (parsed && typeof parsed === 'object') {
+ editorData = parsed
+ }
+ } catch (e) {
+ console.error('Failed to parse blocks JSON:', e)
+ }
+
+ const editor = new EditorJS({
+ holder: 'editor',
+ tools: {
+ header: Header,
+ paragraph: Paragraph,
+ list: List,
+ code: Code,
+ quote: Quote,
+ image: {
+ class: ImageTool,
+ config: {
+ endpoints: { byFile: '/admin/upload/image' },
+ },
+ },
+ component: ComponentTool,
+ },
+ data: editorData,
+ })
+
+ // Sync editor content back to hidden field before form submission
+ on(form, 'submit', async (e) => {
+ e.preventDefault()
+
+ try {
+ const saved = await editor.save()
+ blocksField.value = JSON.stringify(saved)
+ } catch (err) {
+ console.error('Failed to save editor data:', err)
+ return
+ }
+
+ form.submit()
+ })
+</script>
+{{end}} \ No newline at end of file
diff --git a/templates/admin/index.html b/templates/admin/index.html
new file mode 100644
index 0000000..decc138
--- /dev/null
+++ b/templates/admin/index.html
@@ -0,0 +1,36 @@
+{{define "base"}}
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Admin — {{.Title}}</title>
+ <link rel="stylesheet" href="/assets/styles/admin.css">
+ <script type="importmap">
+ {
+ "imports": {
+ "editorjs-bundle": "/assets/admin/lib/dist/build.js",
+ "shared": "/assets/lib/shared.js"
+ }
+ }
+ </script>
+</head>
+
+<body>
+ <nav>
+ <a href="/">forside</a>
+ <a href="/admin">admin</a>
+ <a href="/admin/new">nytt innlegg</a>
+ <a href="/admin/settings">innstillinger</a>
+ </nav>
+ <main>
+ {{if eq .ContentTemplate "list-content"}}{{template "list-content" .}}{{end}}
+ {{if eq .ContentTemplate "form-content"}}{{template "form-content" .}}{{end}}
+ {{if eq .ContentTemplate "error-content"}}{{template "error-content" .}}{{end}}
+ {{if eq .ContentTemplate "settings-content"}}{{template "settings-content" .}}{{end}}
+ </main>
+</body>
+
+</html>
+{{end}} \ No newline at end of file
diff --git a/templates/admin/list.html b/templates/admin/list.html
new file mode 100644
index 0000000..8e488b6
--- /dev/null
+++ b/templates/admin/list.html
@@ -0,0 +1,50 @@
+{{define "list-content"}}
+{{if .Posts}}
+<table>
+ <thead>
+ <tr>
+ <th>Tittel</th>
+ <th>Dato</th>
+ <th>Emne</th>
+ <th>Status</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{range .Posts}}
+ <tr>
+ <td>{{.Title}}</td>
+ <td>{{.Date}}</td>
+ <td>
+ {{if .Tags}}
+ <div>
+ {{range .Tags}}<span class="tag">{{.}} </span>{{end}}
+ </div>
+ {{end}}
+ </td>
+ <td>
+ {{if .Draft}}<span>Ikke publisert</span>{{else}}<span>Publisert</span>{{end}}
+ </td>
+ <td>
+ <div>
+ <a href="/admin/{{.Slug}}">Rediger</a>
+ <button onclick=" deletePost('{{.Slug}}', '{{.Title}}' )">Slett</button>
+ </div>
+ </td>
+ </tr>
+ {{end}}
+ </tbody>
+</table>
+{{else}}
+<div>Ingen innlegg</div>
+{{end}}
+
+<script>
+ function deletePost(slug, title) {
+ if (!confirm('Slett "' + title + '"?')) return;
+ fetch('/admin/' + slug, { method: 'DELETE' })
+ .then(() => window.location.href = '/admin/')
+ .catch(err => alert('Kunne ikke slette: ' + err));
+ }
+</script>
+{{end}} \ No newline at end of file
diff --git a/templates/admin/settings.html b/templates/admin/settings.html
new file mode 100644
index 0000000..fc42ee2
--- /dev/null
+++ b/templates/admin/settings.html
@@ -0,0 +1,20 @@
+{{define "settings-content"}}
+{{if .Success}}<p class="hint">Settings saved.</p>{{end}}
+{{if .Error}}<p style="color:#c0392b">{{.Error}}</p>{{end}}
+<form method="POST" action="/admin/settings" enctype="multipart/form-data">
+ <label for="site_title">Site title</label>
+ <input type="text" id="site_title" name="site_title" value="{{.SiteTitle}}" placeholder="My blog">
+
+ <label for="site_description">Site description</label>
+ <input type="text" id="site_description" name="site_description" value="{{.SiteDescription}}" placeholder="A short description">
+
+ <label for="logo">Logo image</label>
+ <input type="file" id="logo" name="logo" accept="image/png,image/jpeg,image/webp">
+ <p class="hint">Leave blank to keep the current logo.</p>
+
+ <div class="form-actions">
+ <button type="submit" class="btn btn-primary">Save</button>
+ <a href="/admin/" class="btn btn-secondary">Cancel</a>
+ </div>
+</form>
+{{end}}
diff --git a/templates/admin/setup.html b/templates/admin/setup.html
new file mode 100644
index 0000000..4294238
--- /dev/null
+++ b/templates/admin/setup.html
@@ -0,0 +1,47 @@
+{{define "setup.html"}}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Setup</title>
+ <link rel="stylesheet" href="/assets/styles/admin.css">
+ <style>
+ body { max-width: 480px; margin: 4rem auto; padding: 0 1rem; }
+ h1 { margin-bottom: 1.5rem; }
+ .error { color: #c0392b; margin-bottom: 1rem; }
+ .hint { font-size: 0.8rem; color: #888; margin: 0.2rem 0 0.8rem; }
+ .form-actions { margin-top: 1.5rem; }
+ </style>
+</head>
+<body>
+ <h1>Setup</h1>
+ {{if .Error}}<p class="error">{{.Error}}</p>{{end}}
+ <form method="POST" action="/setup" enctype="multipart/form-data">
+
+ <label for="username">Admin username</label>
+ <input type="text" id="username" name="username" value="{{.Username}}" required autofocus autocomplete="username">
+
+ <label for="password">Password</label>
+ <input type="password" id="password" name="password" required autocomplete="new-password">
+
+ <label for="confirm">Confirm password</label>
+ <input type="password" id="confirm" name="confirm" required autocomplete="new-password">
+
+ <label for="site_title">Site title</label>
+ <input type="text" id="site_title" name="site_title" value="{{.SiteTitle}}" placeholder="My blog">
+
+ <label for="site_description">Site description</label>
+ <input type="text" id="site_description" name="site_description" value="{{.SiteDescription}}" placeholder="A short description">
+
+ <label for="logo">Logo image</label>
+ <input type="file" id="logo" name="logo" accept="image/png,image/jpeg,image/webp">
+ <p class="hint">Shown on the front page. Leave blank to keep the default.</p>
+
+ <div class="form-actions">
+ <button type="submit" class="btn btn-primary">Create site</button>
+ </div>
+ </form>
+</body>
+</html>
+{{end}}