summaryrefslogtreecommitdiff
path: root/srv/src/http/tpl
diff options
context:
space:
mode:
Diffstat (limited to 'srv/src/http/tpl')
-rw-r--r--srv/src/http/tpl/assets.html51
-rw-r--r--srv/src/http/tpl/base.html66
-rw-r--r--srv/src/http/tpl/edit-post.html101
-rw-r--r--srv/src/http/tpl/follow.html152
-rw-r--r--srv/src/http/tpl/index.html36
-rw-r--r--srv/src/http/tpl/post.html48
-rw-r--r--srv/src/http/tpl/posts.html61
-rw-r--r--srv/src/http/tpl/redirect.html9
8 files changed, 524 insertions, 0 deletions
diff --git a/srv/src/http/tpl/assets.html b/srv/src/http/tpl/assets.html
new file mode 100644
index 0000000..aa5e422
--- /dev/null
+++ b/srv/src/http/tpl/assets.html
@@ -0,0 +1,51 @@
+{{ define "body" }}
+
+{{ $csrfFormInput := .CSRFFormInput }}
+
+<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">
+ {{ $csrfFormInput }}
+ <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>
+
+<h2>Existing Assets</h2>
+
+<table>
+
+ {{ range .Payload.IDs }}
+ <tr>
+ <td><a href="{{ AssetURL . }}" target="_blank">{{ . }}</a></td>
+ <td>
+ <form
+ action="{{ AssetURL . }}?method=delete"
+ method="POST"
+ style="margin-bottom: 0;"
+ >
+ {{ $csrfFormInput }}
+ <input type="submit" value="Delete" />
+ </form>
+ </td>
+ </tr>
+ {{ end }}
+
+</table>
+
+{{ end }}
+
+{{ template "base.html" . }}
diff --git a/srv/src/http/tpl/base.html b/srv/src/http/tpl/base.html
new file mode 100644
index 0000000..6031919
--- /dev/null
+++ b/srv/src/http/tpl/base.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="/assets/normalize.css">
+ <link rel="stylesheet" href="/assets/skeleton.css">
+ <link rel="stylesheet" href="/assets/friendly.css">
+ <link rel="stylesheet" href="/assets/main.css">
+ <link rel="stylesheet" href="/assets/fontawesome/css/all.css">
+ </head>
+
+ <body>
+
+ <div class="container">
+
+ <header id="title-header" role="banner">
+ <div class="row">
+ <div class="seven columns" style="margin-bottom: 3rem;">
+ <h1 class="title">
+ <a href="{{ BlogURL "/" }}">Mediocre Blog</a>
+ </h1>
+ <div class="light social">
+ <span>By Brian Picciano</span>
+ <span>
+ Even more @
+ <a href="https://mediocregopher.eth.link" target="_blank">https://mediocregopher.eth.link</a>
+ </span>
+ </div>
+ </div>
+
+ <div class="five columns light">
+ <span style="display:block; margin-bottom:0.5rem;">Get notified when new posts are published!</span>
+ <a href="{{ BlogURL "follow.html" }}">
+ <button class="button-primary">
+ <i class="far fa-envelope"></i>
+ Follow
+ </button>
+ </a>
+
+ <a href="{{ BlogURL "feed.xml" }}">
+ <button class="button">
+ <i class="fas fa-rss"></i>
+ RSS
+ </button>
+ </a>
+ </div>
+
+ </div>
+ </header>
+
+ {{ template "body" . }}
+
+ <footer>
+ <p class="license light">
+ Unless otherwised specified, all works are licensed under the
+ <a href="/assets/wtfpl.txt">WTFPL</a>.
+ </p>
+ </footer>
+
+ </div>
+
+ </body>
+
+</html>
+
diff --git a/srv/src/http/tpl/edit-post.html b/srv/src/http/tpl/edit-post.html
new file mode 100644
index 0000000..9ccfa2a
--- /dev/null
+++ b/srv/src/http/tpl/edit-post.html
@@ -0,0 +1,101 @@
+{{ define "body" }}
+
+ <form method="POST" action="{{ BlogURL "posts/" }}">
+
+ {{ .CSRFFormInput }}
+
+ <div class="row">
+
+ <div class="columns six">
+ <label for="idInput">Unique ID</label>
+ {{ if eq .Payload.ID "" }}
+ <input
+ id="idInput"
+ name="id"
+ class="u-full-width"
+ type="text"
+ placeholder="e.g. how-to-fly-a-kite"
+ value="{{ .Payload.ID }}" />
+ {{ else }}
+ <a href="{{ PostURL .Payload.ID }}" target="_blank">{{ .Payload.ID }}</a>
+ <input name="id" type="hidden" value="{{ .Payload.ID }}" />
+ {{ end }}
+ </div>
+
+ <div class="columns three">
+ <label for="tagsInput">Tags (space separated)</label>
+ <input
+ id="tagsInput"
+ name="tags"
+ class="u-full-width"
+ type="text"
+ value="{{ range $i, $tag := .Payload.Tags }}{{ if ne $i 0 }} {{ end }}{{ $tag }}{{ end }}" />
+ </div>
+
+ <div class="columns three">
+ <label for="seriesInput">Series</label>
+ <input
+ id="seriesInput"
+ name="series"
+ class="u-full-width"
+ type="text"
+ value="{{ .Payload.Series }}" />
+ </div>
+
+ </div>
+
+ <div class="row">
+
+ <div class="columns six">
+ <label for="titleInput">Title</label>
+ <input
+ id="titleInput"
+ name="title"
+ class="u-full-width"
+ type="text"
+ value="{{ .Payload.Title }}" />
+ </div>
+
+ <div class="columns six">
+ <label for="descrInput">Description</label>
+ <input
+ id="descrInput"
+ name="description"
+ class="u-full-width"
+ type="text"
+ value="{{ .Payload.Description }}" />
+ </div>
+
+ </div>
+
+ <div class="row">
+ <div class="columns twelve">
+ <textarea
+ name="body"
+ class="u-full-width"
+ placeholder="Blog body"
+ style="height: 50vh;"
+ >
+ {{- .Payload.Body -}}
+ </textarea>
+ </div>
+ </div>
+
+ <input
+ type="submit"
+ value="Preview"
+ formaction="{{ BlogURL "posts/" }}{{ .Payload.ID }}?method=preview"
+ formtarget="_blank"
+ />
+
+ <input type="submit" value="Save" formaction="{{ BlogURL "posts/" }}" />
+
+ <a href="{{ BlogURL "posts/" }}">
+ <button type="button">Cancel</button>
+ </a>
+
+ </form>
+
+{{ end }}
+
+{{ template "base.html" . }}
diff --git a/srv/src/http/tpl/follow.html b/srv/src/http/tpl/follow.html
new file mode 100644
index 0000000..8cf9dc6
--- /dev/null
+++ b/srv/src/http/tpl/follow.html
@@ -0,0 +1,152 @@
+{{ define "body" }}
+
+<script async type="module" src="/assets/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>
+
+<style>
+
+#emailStatus.success {
+ color: green;
+}
+
+#emailStatus.fail {
+ color: red;
+}
+
+</style>
+
+<input type="email" placeholder="name@host.com" id="emailAddress" />
+<input class="button-primary" type="submit" value="Subscribe" id="emailSubscribe" />
+<span id="emailStatus"></span>
+
+<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("/assets/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/srv/src/http/tpl/index.html b/srv/src/http/tpl/index.html
new file mode 100644
index 0000000..e27cbef
--- /dev/null
+++ b/srv/src/http/tpl/index.html
@@ -0,0 +1,36 @@
+{{ define "body" }}
+
+ <ul id="posts-list">
+
+ {{ range .Payload.Posts }}
+ <li>
+ <h2>
+ <a href="{{ PostURL .ID }}">{{ .Title }}</a>
+ </h2>
+ <span>{{ DateTimeFormat .PublishedAt }}</span>
+ {{ if not .LastUpdatedAt.IsZero }}
+ <span>(Updated {{ DateTimeFormat .LastUpdatedAt }})</span>
+ {{ end }}
+ <p>{{ .Description }}</p>
+ </li>
+ {{ end }}
+
+ </ul>
+
+ {{ if or (ge .Payload.PrevPage 0) (ge .Payload.NextPage 0) }}
+ <div id="page-turner">
+
+ {{ if ge .Payload.PrevPage 0 }}
+ <a style="float: left;" href="?p={{ .Payload.PrevPage}}">Newer</a>
+ {{ end }}
+
+ {{ if ge .Payload.NextPage 0 }}
+ <a style="float:right;" href="?p={{ .Payload.NextPage}}">Older</a>
+ {{ end }}
+
+ </div>
+ {{ end }}
+
+{{ end }}
+
+{{ template "base.html" . }}
diff --git a/srv/src/http/tpl/post.html b/srv/src/http/tpl/post.html
new file mode 100644
index 0000000..474d7c2
--- /dev/null
+++ b/srv/src/http/tpl/post.html
@@ -0,0 +1,48 @@
+{{ define "body" }}
+
+<header id="post-header">
+ <h1 id="post-headline">
+ {{ .Payload.Title }}
+ </h1>
+ <div class="light">
+ {{ DateTimeFormat .Payload.PublishedAt }}
+ &nbsp;•&nbsp;
+ {{ if not .Payload.LastUpdatedAt.IsZero }}
+ (Updated {{ DateTimeFormat .Payload.LastUpdatedAt }})
+ &nbsp;•&nbsp;
+ {{ end }}
+ <em>{{ .Payload.Description }}</em>
+ </div>
+</header>
+
+{{ if (or .Payload.SeriesPrevious .Payload.SeriesNext) }}
+<p class="light"><em>
+ This post is part of a series:<br/>
+ {{ if .Payload.SeriesPrevious }}
+ Previously: <a href="{{ PostURL .Payload.SeriesPrevious.ID }}">{{ .Payload.SeriesPrevious.Title }}</a></br>
+ {{ end }}
+ {{ if .Payload.SeriesNext }}
+ Next: <a href="{{ PostURL .Payload.SeriesNext.ID }}">{{ .Payload.SeriesNext.Title }}</a></br>
+ {{ end }}
+</em></p>
+{{ end }}
+
+<div id="post-content">
+ {{ .Payload.Body }}
+</div>
+
+{{ if (or .Payload.SeriesPrevious .Payload.SeriesNext) }}
+<p class="light"><em>
+ If you liked this post, consider checking out other posts in the series:<br/>
+ {{ if .Payload.SeriesPrevious }}
+ Previously: <a href="{{ PostURL .Payload.SeriesPrevious.ID }}">{{ .Payload.SeriesPrevious.Title }}</a></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/srv/src/http/tpl/posts.html b/srv/src/http/tpl/posts.html
new file mode 100644
index 0000000..714cf07
--- /dev/null
+++ b/srv/src/http/tpl/posts.html
@@ -0,0 +1,61 @@
+{{ define "posts-nextprev" }}
+
+ {{ if or (ge .Payload.PrevPage 0) (ge .Payload.NextPage 0) }}
+ <div id="page-turner">
+
+ {{ if ge .Payload.PrevPage 0 }}
+ <a style="float: left;" href="?p={{ .Payload.PrevPage}}">Newer</a>
+ {{ end }}
+
+ {{ if ge .Payload.NextPage 0 }}
+ <a style="float:right;" href="?p={{ .Payload.NextPage}}">Older</a>
+ {{ end }}
+
+ </div>
+ {{ end }}
+
+{{ end }}
+
+{{ define "body" }}
+
+ {{ $csrfFormInput := .CSRFFormInput }}
+
+
+ <p style="text-align: center;">
+ <a href="{{ BlogURL "posts/" }}?method=edit">
+ <button>New Post</button>
+ </a>
+ </p>
+
+ {{ template "posts-nextprev" . }}
+
+ <table style="margin-top: 2rem;">
+
+ {{ range .Payload.Posts }}
+ <tr>
+ <td>{{ .PublishedAt }}</td>
+ <td><a href="{{ PostURL .ID }}" target="_blank">{{ .Title }}</a></td>
+ <td>
+ <a href="{{ PostURL .ID }}?method=edit">
+ <button>Edit</button>
+ </a>
+ </td>
+ <td>
+ <form
+ action="{{ PostURL .ID }}?method=delete"
+ method="POST"
+ >
+ {{ $csrfFormInput }}
+ <input type="submit" value="Delete" />
+ </form>
+ </td>
+ </tr>
+ {{ end }}
+
+ </table>
+
+ {{ template "posts-nextprev" . }}
+
+{{ end }}
+
+{{ template "base.html" . }}
diff --git a/srv/src/http/tpl/redirect.html b/srv/src/http/tpl/redirect.html
new file mode 100644
index 0000000..ed12a2e
--- /dev/null
+++ b/srv/src/http/tpl/redirect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="refresh" content="0; url='{{ BlogURL .Payload.Path }}'" />
+ </head>
+ <body>
+ <p>Redirecting...</p>
+ </body>
+</html>