diff options
| author | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
| commit | 85920b8c7a2696115d1f77c046f48f6f00d639f1 (patch) | |
| tree | 14ed2043796eadd6ed5b0a95c55e38e48713d638 /templates/admin | |
| download | iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.tar.xz iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.zip | |
Init
Diffstat (limited to 'templates/admin')
| -rw-r--r-- | templates/admin/error.html | 7 | ||||
| -rw-r--r-- | templates/admin/form.html | 82 | ||||
| -rw-r--r-- | templates/admin/index.html | 36 | ||||
| -rw-r--r-- | templates/admin/list.html | 50 | ||||
| -rw-r--r-- | templates/admin/settings.html | 20 | ||||
| -rw-r--r-- | templates/admin/setup.html | 47 |
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}} |
