diff options
Diffstat (limited to 'src/http/tpl')
-rw-r--r-- | src/http/tpl/admin.html | 18 | ||||
-rw-r--r-- | src/http/tpl/assets.html | 57 | ||||
-rw-r--r-- | src/http/tpl/base.html | 28 | ||||
-rw-r--r-- | src/http/tpl/draft-posts.html | 50 | ||||
-rw-r--r-- | src/http/tpl/edit-post.html | 142 | ||||
-rw-r--r-- | src/http/tpl/finalize.html | 45 | ||||
-rw-r--r-- | src/http/tpl/follow.html | 160 | ||||
-rw-r--r-- | src/http/tpl/image.html | 5 | ||||
-rw-r--r-- | src/http/tpl/index.html | 37 | ||||
-rw-r--r-- | src/http/tpl/post.html | 44 | ||||
-rw-r--r-- | src/http/tpl/posts.html | 47 | ||||
-rw-r--r-- | src/http/tpl/redirect.html | 9 | ||||
-rw-r--r-- | src/http/tpl/unsubscribe.html | 44 |
13 files changed, 686 insertions, 0 deletions
diff --git a/src/http/tpl/admin.html b/src/http/tpl/admin.html new file mode 100644 index 0000000..f2ba4d6 --- /dev/null +++ b/src/http/tpl/admin.html @@ -0,0 +1,18 @@ +{{ define "body" }} + +<h1>Admin</h1> + +This is a directory of pages which are used for managing blog content. They are +mostly left open to inspection, but you will not able to change +anything without providing credentials. + +<ul> + <li><a href="{{ BlogURL "posts" }}">Posts</a></li> + <li><a href="{{ BlogURL "assets" }}">Assets</a></li> + <li><a href="{{ BlogURL "drafts" }}">Drafts</a> (private)</li> +</ul> + +{{ end }} + +{{ template "base.html" . }} + diff --git a/src/http/tpl/assets.html b/src/http/tpl/assets.html new file mode 100644 index 0000000..f21717a --- /dev/null +++ b/src/http/tpl/assets.html @@ -0,0 +1,57 @@ +{{ define "body" }} + +<p> + <a href="{{ BlogURL "admin" }}">Back to Admin</a> +</p> + +<h1>Assets</h1> + +<h2>Upload Asset</h2> + +<p> + If the given ID is the same as an existing asset's ID, then that asset will be + overwritten. +</p> + +<form action="{{ BlogURL "assets/" }}" method="POST" enctype="multipart/form-data"> + <div class="row"> + <div class="four columns"> + <input type="text" placeholder="Unique ID" name="id" /> + </div> + <div class="four columns"> + <input type="file" name="file" /><br/> + </div> + <div class="four columns"> + <input type="submit" value="Upload" /> + </div> + </div> +</form> + +{{ if gt (len .Payload.IDs) 0 }} + +<h2>Existing Assets</h2> + +<table> + + {{ range .Payload.IDs }} + <tr> + <td><a href="{{ AssetURL . }}">{{ . }}</a></td> + <td> + <form + action="{{ AssetURL . }}?method=delete" + method="POST" + style="margin-bottom: 0;" + > + <input type="submit" value="Delete" /> + </form> + </td> + </tr> + {{ end }} + +</table> + +{{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/base.html b/src/http/tpl/base.html new file mode 100644 index 0000000..f286222 --- /dev/null +++ b/src/http/tpl/base.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> + + <head> + <style>{{ StaticInlineCSS "new.css" }}</style> + <style>{{ StaticInlineCSS "mediocre.css" }}</style> + </head> + + <body> + + <header> + <a href="{{ BlogURL "/" }}"><strong>Mediocre Blog</strong></a> + by + <a href="https://mediocregopher.com">mediocregopher</a> + // + <a href="{{ BlogURL "follow" }}">Follow</a> + / + <a href="{{ BlogURL "feed.xml" }}">RSS</a> + / + <a href="{{ StaticURL "wtfpl.txt" }}">License</a> + </header> + + {{ template "body" . }} + + </body> + +</html> + diff --git a/src/http/tpl/draft-posts.html b/src/http/tpl/draft-posts.html new file mode 100644 index 0000000..53261b9 --- /dev/null +++ b/src/http/tpl/draft-posts.html @@ -0,0 +1,50 @@ +{{ define "body" }} + + <p> + <a href="{{ BlogURL "admin" }}">Back to Admin</a> + </p> + + <h1>Drafts</h1> + + <p> + <a href="{{ BlogURL "drafts/" }}?edit">New Draft</a> + </p> + + {{ if ge .Payload.PrevPage 0 }} + <p> + <a href="?p={{ .Payload.PrevPage}}">< < Previous Page</a> + </p> + {{ end }} + + <table> + + {{ range .Payload.Posts }} + <tr> + <td><a href="{{ DraftURL .ID }}">{{ .Title }}</a></td> + <td> + <a href="{{ DraftURL .ID }}?edit"> + Edit + </a> + </td> + <td> + <form + action="{{ DraftURL .ID }}?method=delete" + method="POST" + > + <input type="submit" value="Delete" /> + </form> + </td> + </tr> + {{ end }} + + </table> + + {{ if ge .Payload.NextPage 0 }} + <p> + <a href="?p={{ .Payload.NextPage}}">Next Page > ></a> + </p> + {{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/edit-post.html b/src/http/tpl/edit-post.html new file mode 100644 index 0000000..f8e2730 --- /dev/null +++ b/src/http/tpl/edit-post.html @@ -0,0 +1,142 @@ +{{ define "body" }} + +<p> + {{ if .Payload.IsDraft }} + <a href="{{ BlogURL "drafts/" }}"> + Back to Drafts + </a> + {{ else }} + <a href="{{ BlogURL "posts/" }}"> + Back to Posts + </a> + {{ end }} +</p> + +<form method="POST" action="{{ BlogURL "posts/" }}"> + + <table> + + <tr> + <td> + Unique ID + </td> + <td> + {{ if eq .Payload.Post.ID "" }} + <input + name="id" + type="text" + placeholder="e.g. how-to-fly-a-kite" + value="{{ .Payload.Post.ID }}" /> + {{ else if .Payload.IsDraft }} + {{ .Payload.Post.ID }} + <input name="id" type="hidden" value="{{ .Payload.Post.ID }}" /> + {{ else }} + <a href="{{ PostURL .Payload.Post.ID }}">{{ .Payload.Post.ID }}</a> + <input name="id" type="hidden" value="{{ .Payload.Post.ID }}" /> + {{ end }} + </td> + </tr> + + <tr> + <td>Tags (space separated)</td> + <td> + <input + name="tags" + type="text" + value="{{- range $i, $tag := .Payload.Post.Tags -}} + {{- if ne $i 0 }} {{ end }}{{ $tag -}} + {{- end -}} + "/> + + {{ if gt (len .Payload.Tags) 0 }} + <em> + Existing tags: + {{ range $i, $tag := .Payload.Tags }} + {{ if ne $i 0 }} {{ end }}{{ $tag }} + {{ end }} + </em> + {{ end }} + </td> + </tr> + + <tr> + <td>Series</td> + <td> + <input + name="series" + type="text" + value="{{ .Payload.Post.Series }}" /> + </td> + </tr> + + <tr> + <td>Title</td> + <td> + <input + name="title" + type="text" + value="{{ .Payload.Post.Title }}" /> + </td> + </tr> + + <tr> + <td>Description</td> + <td> + <input + name="description" + type="text" + value="{{ .Payload.Post.Description }}" /> + </td> + </tr> + + </table> + + <p> + <textarea + name="body" + placeholder="Blog body" + style="width:100%;height: 75vh;" + > + {{- .Payload.Post.Body -}} + </textarea> + </p> + + <p> + + <input + type="submit" + value="Preview" + formaction="{{ BlogURL "posts/" }}{{ .Payload.Post.ID }}?method=preview" + formtarget="_blank" + /> + + {{ if .Payload.IsDraft }} + <input type="submit" value="Save" formaction="{{ BlogURL "drafts/" }}" /> + + + <script> + function confirmPublish(event) { + if (!confirm("Are you sure you're ready to publish?")) + event.preventDefault(); + } + </script> + + + <input + type="submit" + value="Publish" + formaction="{{ BlogURL "posts/" }}" + onclick="confirmPublish(event)" + /> + + {{ else }} + <input type="submit" value="Update" formaction="{{ BlogURL "posts/" }}" /> + {{ end }} + + </p> + +</form> + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/finalize.html b/src/http/tpl/finalize.html new file mode 100644 index 0000000..8bdfceb --- /dev/null +++ b/src/http/tpl/finalize.html @@ -0,0 +1,45 @@ +{{ define "body" }} + +<script async type="module" src="{{ StaticURL "api.js" }}"></script> + +<style> +#result.success { color: green; } +#result.fail { color: red; } +</style> + +<span id="result"></span> + +<script> + +(async () => { + + const resultSpan = document.getElementById("result"); + + try { + + const urlParams = new URLSearchParams(window.location.search); + const subToken = urlParams.get('subToken'); + + if (!subToken) throw "No subscription token provided"; + + const api = await import("{{ StaticURL "api.js" }}"); + + await api.call('/api/mailinglist/finalize', { + body: { subToken }, + }); + + resultSpan.className = "success"; + resultSpan.innerHTML = "Your email subscription has been finalized! Please go on about your day."; + + } catch (e) { + resultSpan.className = "fail"; + resultSpan.innerHTML = e; + } + +})(); + +</script> + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/follow.html b/src/http/tpl/follow.html new file mode 100644 index 0000000..23c30a6 --- /dev/null +++ b/src/http/tpl/follow.html @@ -0,0 +1,160 @@ +{{ define "body" }} + +<script async type="module" src="{{ StaticURL "api.js" }}"></script> + +<p> + Here's your options for receiving updates about new blog posts: +</p> + +<h2>Option 1: Email</h2> + +<p> + Email is by far my preferred option for notifying followers of new posts. +</p> + +<p> + The entire email list system for this blog, from storing subscriber email + addresses to the email server which sends the notifications out, has been + designed from scratch and is completely self-hosted in my living room. +</p> + +<p> + I solemnly swear that: +</p> + +<ul> + + <li> + You will never receive an email from this blog except to notify of a new + post. + </li> + + <li> + Your email will never be provided or sold to anyone else for any reason. + </li> + +</ul> + +<p> + With all that said, if you'd like to receive an email everytime a new blog + post is published then input your email below and smash that subscribe button! + You will need to verify your email, so be sure to check your spam folder to + complete the process if you don't immediately see anything in your inbox. +</p> + +<p style="color: var(--nc-ac-1);"> + Unfortunately Google considers all emails from my mail server to be spam. I'm + tired of seeing the bounce errors on my side, so I'm disabling the ability to + sign up for the mailing list with a GMail address. Sorry (not sorry). +</p> + +<style> + +#emailStatus.success { + color: green; +} + +#emailStatus.fail { + color: red; +} + +</style> + +<form action="javascript:void(0);"> + <input type="email" placeholder="name@host.com" id="emailAddress" /> + <input class="button-primary" type="submit" value="Subscribe" id="emailSubscribe" /> + <span id="emailStatus"></span> +</form> + +<script> + +const emailAddress = document.getElementById("emailAddress"); +const emailSubscribe = document.getElementById("emailSubscribe"); +const emailSubscribeOrigValue = emailSubscribe.value; +const emailStatus = document.getElementById("emailStatus"); + +emailSubscribe.onclick = async () => { + + const api = await import("{{ StaticURL "api.js" }}"); + + emailSubscribe.disabled = true; + emailSubscribe.className = ""; + emailSubscribe.value = "Please hold..."; + emailStatus.innerHTML = ''; + + try { + + if (!window.isSecureContext) { + throw "The browser environment is not secure."; + } + + await api.call('/api/mailinglist/subscribe', { + body: { email: emailAddress.value }, + requiresPow: true, + }); + + emailStatus.className = "success"; + emailStatus.innerHTML = "Verification email sent (check your spam folder)"; + + } catch (e) { + emailStatus.className = "fail"; + emailStatus.innerHTML = e; + + } finally { + emailSubscribe.disabled = false; + emailSubscribe.className = "button-primary"; + emailSubscribe.value = emailSubscribeOrigValue; + } + +}; + +</script> + +<h2>Option 2: RSS</h2> + +<p> + RSS is the classic way to follow any blog. It comes from a time before + aggregators like reddit and twitter stole the show, when people felt capable + to manage their own content feeds. We should use it again. +</p> + +<p> + To follow over RSS give any RSS reader the following URL... +</p> + +<p> + <a href="{{ BlogURL "feed.xml" }}">{{ BlogURL "feed.xml" }}</a> +</p> + +<p> + ...and posts from this blog will show up in your RSS feed as soon as they are + published. There are literally thousands of RSS readers out there. Here's some + recommendations: +</p> + +<ul> + <li> + <a href="https://chrome.google.com/webstore/detail/rss-feed-reader/pnjaodmkngahhkoihejjehlcdlnohgmp"> + Google Chrome Browser Extension + </a> + </li> + + <li> + <a href="https://f-droid.org/en/packages/net.etuldan.sparss.floss/"> + spaRSS + </a> + is my preferred android RSS reader, but you'll need to install + <a href="https://f-droid.org/">f-droid</a> on your device to use it (a + good thing to do anyway, imo). + </li> + + <li> + <a href="https://ranchero.com/netnewswire/">NetNewsWire</a> + is a good reader for iPhone/iPad/Mac devices, so I'm told. Their homepage + description makes a much better sales pitch for RSS than I ever could. + </li> +</ul> + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/image.html b/src/http/tpl/image.html new file mode 100644 index 0000000..ba9b75d --- /dev/null +++ b/src/http/tpl/image.html @@ -0,0 +1,5 @@ +<div style="text-align: center;"> + <a href="{{ AssetURL .ID }}" target="_blank"> + <img src="{{ AssetURL .ID }}{{ if .Resizable }}?w=800{{ end }}" /> + </a> +</div> diff --git a/src/http/tpl/index.html b/src/http/tpl/index.html new file mode 100644 index 0000000..ce5f264 --- /dev/null +++ b/src/http/tpl/index.html @@ -0,0 +1,37 @@ +{{ define "body" }} + + {{ if ge .Payload.PrevPage 0 }} + <p> + <a href="?p={{ .Payload.PrevPage}}">< < Previous Page</a> + </p> + {{ else }} + <p> + Welcome to the Mediocre Blog! Posts are listed in chronological order. If + you aren't sure of where to start I recommend picking at random. + </p> + {{ end }} + + <table> + + <colgroup> + <col span="1" style="width: 5rem;"> + </colgroup> + + {{ range .Payload.Posts }} + <tr> + <td>{{ DateTimeFormat .PublishedAt }}</td> + <td><a href="{{ PostURL .ID }}">{{ .Title }}</td> + <td><em>{{ .Description }}</em></td> + </tr> + {{ end }} + </table> + + {{ if ge .Payload.NextPage 0 }} + <p> + <a href="?p={{ .Payload.NextPage}}">Next Page > ></a> + </p> + {{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/post.html b/src/http/tpl/post.html new file mode 100644 index 0000000..23500eb --- /dev/null +++ b/src/http/tpl/post.html @@ -0,0 +1,44 @@ +{{ define "body" }} + +<h1 id="post-headline"> + {{ .Payload.Title }} +</h1> + +<p> + <em>- {{ .Payload.Description }}</em> +</p> + +<hr/> + +{{ .Payload.Body }} + +<p><em> + Published {{ DateTimeFormat .Payload.PublishedAt }} + {{ if not .Payload.LastUpdatedAt.IsZero }} + <br/>Last updated {{ DateTimeFormat .Payload.LastUpdatedAt }} + {{ end }} +</em></p> + +{{ if (or .Payload.SeriesPrevious .Payload.SeriesNext) }} +<hr/> +<p><em> + This post is part of a series.<br/> + + {{ if .Payload.SeriesPrevious }} + Previously: <a href="{{ PostURL .Payload.SeriesPrevious.ID }}">{{ .Payload.SeriesPrevious.Title }}</a> + {{ end }} + + {{ if (and .Payload.SeriesNext .Payload.SeriesPrevious) }} + </br> + {{ end }} + + {{ if .Payload.SeriesNext }} + Next: <a href="{{ PostURL .Payload.SeriesNext.ID }}">{{ .Payload.SeriesNext.Title }}</a></br> + {{ end }} + +</em></p> +{{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/posts.html b/src/http/tpl/posts.html new file mode 100644 index 0000000..fbeaa41 --- /dev/null +++ b/src/http/tpl/posts.html @@ -0,0 +1,47 @@ +{{ define "body" }} + + <p> + <a href="{{ BlogURL "admin" }}">Back to Admin</a> + </p> + + <h1>Posts</h1> + + {{ if ge .Payload.PrevPage 0 }} + <p> + <a href="?p={{ .Payload.PrevPage}}">< < Previous Page</a> + </p> + {{ end }} + + <table> + + {{ range .Payload.Posts }} + <tr> + <td>{{ .PublishedAt.Local.Format "2006-01-02 15:04:05 MST" }}</td> + <td><a href="{{ PostURL .ID }}">{{ .Title }}</a></td> + <td> + <a href="{{ PostURL .ID }}?edit"> + Edit + </a> + </td> + <td> + <form + action="{{ PostURL .ID }}?method=delete" + method="POST" + > + <input type="submit" value="Delete" /> + </form> + </td> + </tr> + {{ end }} + + </table> + + {{ if ge .Payload.NextPage 0 }} + <p> + <a href="?p={{ .Payload.NextPage}}">Next Page > ></a> + </p> + {{ end }} + +{{ end }} + +{{ template "base.html" . }} diff --git a/src/http/tpl/redirect.html b/src/http/tpl/redirect.html new file mode 100644 index 0000000..a50b324 --- /dev/null +++ b/src/http/tpl/redirect.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="refresh" content="0; url='{{ .Payload.URL }}'" /> + </head> + <body> + <p>Redirecting...</p> + </body> +</html> diff --git a/src/http/tpl/unsubscribe.html b/src/http/tpl/unsubscribe.html new file mode 100644 index 0000000..ad01735 --- /dev/null +++ b/src/http/tpl/unsubscribe.html @@ -0,0 +1,44 @@ +{{ define "body" }} + +<script async type="module" src="{{ StaticURL "api.js" }}"></script> + +<style> +#result.success { color: green; } +#result.fail { color: red; } +</style> + +<span id="result"></span> + +<script> + +(async () => { + + const resultSpan = document.getElementById("result"); + + try { + const urlParams = new URLSearchParams(window.location.search); + const unsubToken = urlParams.get('unsubToken'); + + if (!unsubToken) throw "No unsubscribe token provided"; + + const api = await import("{{ StaticURL "api.js" }}"); + + await api.call('/api/mailinglist/unsubscribe', { + body: { unsubToken }, + }); + + resultSpan.className = "success"; + resultSpan.innerHTML = "You have been unsubscribed! Please go on about your day."; + + } catch (e) { + resultSpan.className = "fail"; + resultSpan.innerHTML = e; + } + +})(); + +</script> + +{{ end }} + +{{ template "base.html" . }} |