aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/templates
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/templates')
-rw-r--r--internal/web/templates/dashboard/design.html54
-rw-r--r--internal/web/templates/dashboard/layout.html113
-rw-r--r--internal/web/templates/dashboard/module_form.html34
-rw-r--r--internal/web/templates/dashboard/overview.html2
-rw-r--r--internal/web/templates/dashboard/pages.html6
-rw-r--r--internal/web/templates/dashboard/section_form.html14
-rw-r--r--internal/web/templates/dashboard/sections.html2
-rw-r--r--internal/web/templates/layouts/blog.html84
-rw-r--r--internal/web/templates/partials/dashnav.html1
9 files changed, 244 insertions, 66 deletions
diff --git a/internal/web/templates/dashboard/design.html b/internal/web/templates/dashboard/design.html
index af482fe..342cabe 100644
--- a/internal/web/templates/dashboard/design.html
+++ b/internal/web/templates/dashboard/design.html
@@ -5,7 +5,8 @@
<h1>Design</h1>
<p class="lead muted">Changes are live as soon as you save; refresh <a href="{{.BlogURL}}" target="_blank">your blog &#8599;</a> to see them.</p>
</div>
-<p class="jumps"><a href="#schemes">Schemes</a><a href="#background">Background</a><a href="#text">Text</a><a href="#header">Header</a><a href="#menu">Menu</a><a href="#posts">Posts</a><a href="#footer">Footer</a><a href="#icon">Icon</a></p>
+<p class="jumps"><a href="#schemes">Schemes</a><a href="#background">Background</a><a href="#text">Text</a><a href="#header">Header</a><a href="#menu">Menu</a><a href="#posts">Posts</a><a href="#footer">Footer</a><a href="#logo">Logo &amp; icon</a></p>
+<p class="muted small">What appears where — header, columns, footer and their modules — is arranged on the <a href="/b/{{.Blog.Subdomain}}/layout">Layout</a> tab.</p>
<div class="card" id="schemes">
<h2>Colour schemes</h2>
@@ -81,12 +82,13 @@
<option value="card"{{if eq $t.ContentStyle "card"}} selected{{end}}>Card (shadow, rounded corners)</option>
<option value="flat"{{if eq $t.ContentStyle "flat"}} selected{{end}}>Flat</option>
</select></label>
- <label>Padding<br><select name="content_padding">
+ <label>Padding <span class="muted">(inside the box)</span><br><select name="content_padding">
<option value="compact"{{if eq $t.ContentPadding "compact"}} selected{{end}}>Compact</option>
<option value="normal"{{if eq $t.ContentPadding "normal"}} selected{{end}}>Normal</option>
<option value="roomy"{{if eq $t.ContentPadding "roomy"}} selected{{end}}>Roomy</option>
</select></label>
</div>
+ <p class="muted small">Width only applies while there are no side columns; with a left or right column the blog uses the whole page.</p>
</div>
<div class="card" id="header">
@@ -104,43 +106,24 @@
<option value="tall"{{if eq $t.HeaderImageHeight "tall"}} selected{{end}}>Tall</option>
</select></label>
</div>
- <label class="check"><input type="checkbox" name="header_show_title"{{if $t.HeaderShowTitle}} checked{{end}}> Show blog title and tagline in the header</label>
+ <p class="muted small">The title, logo and menu are header modules — add, remove and order them on the <a href="/b/{{.Blog.Subdomain}}/layout#header">Layout</a> tab. The banner below is shown across the top of the header whenever one is chosen.</p>
{{template "imagepick" (dict "name" "header_image" "current" $t.HeaderImage "images" .Data.images "label" "Header image (banner)")}}
</div>
<div class="card" id="menu">
<h2>Menu</h2>
- <p class="muted small">The menu lists the pages you marked "show in menu". Pick where it goes; the options underneath follow your choice.</p>
- <div class="navpos">
- <input type="radio" name="nav_position" value="below-header" id="np-below"{{if eq $t.NavPosition "below-header"}} checked{{end}}>
- <label for="np-below" class="tile"><svg viewBox="0 0 80 56" width="80" height="56" aria-hidden="true"><rect x="0" y="0" width="80" height="56" fill="#f4efe4"/><rect x="0" y="0" width="80" height="14" fill="#1d1a17"/><rect x="0" y="16" width="80" height="6" fill="#c8541a"/><rect x="10" y="28" width="60" height="24" fill="#fffdf8" stroke="#1d1a17"/></svg><span>Below the header</span></label>
- <input type="radio" name="nav_position" value="top-bar" id="np-top"{{if eq $t.NavPosition "top-bar"}} checked{{end}}>
- <label for="np-top" class="tile"><svg viewBox="0 0 80 56" width="80" height="56" aria-hidden="true"><rect x="0" y="0" width="80" height="56" fill="#f4efe4"/><rect x="0" y="0" width="80" height="6" fill="#c8541a"/><rect x="0" y="8" width="80" height="14" fill="#1d1a17"/><rect x="10" y="28" width="60" height="24" fill="#fffdf8" stroke="#1d1a17"/></svg><span>Bar at the very top</span></label>
- <input type="radio" name="nav_position" value="left-sidebar" id="np-side"{{if eq $t.NavPosition "left-sidebar"}} checked{{end}}>
- <label for="np-side" class="tile"><svg viewBox="0 0 80 56" width="80" height="56" aria-hidden="true"><rect x="0" y="0" width="80" height="56" fill="#f4efe4"/><rect x="0" y="0" width="80" height="14" fill="#1d1a17"/><rect x="8" y="20" width="18" height="32" fill="#c8541a"/><rect x="30" y="20" width="42" height="32" fill="#fffdf8" stroke="#1d1a17"/></svg><span>Sidebar on the left</span></label>
- <div class="row only-horizontal">
- <label>Alignment<br><select name="nav_align">
- <option value="left"{{if eq $t.NavAlign "left"}} selected{{end}}>Left</option>
- <option value="center"{{if eq $t.NavAlign "center"}} selected{{end}}>Centred</option>
- <option value="right"{{if eq $t.NavAlign "right"}} selected{{end}}>Right</option>
- </select></label>
- </div>
- <div class="row only-sidebar">
- <label>Sidebar width<br><select name="nav_sidebar_width">
- <option value="narrow"{{if eq $t.NavSidebarWidth "narrow"}} selected{{end}}>Narrow</option>
- <option value="normal"{{if eq $t.NavSidebarWidth "normal"}} selected{{end}}>Normal</option>
- <option value="wide"{{if eq $t.NavSidebarWidth "wide"}} selected{{end}}>Wide</option>
- </select></label>
- </div>
- <p class="muted small only-sidebar">Links stack under each other; announcements placed "in the sidebar" appear beneath them. On phones the sidebar turns into a row of links above the content.</p>
- </div>
+ <p class="muted small">Which links the menu shows, and where it sits, is set on the <a href="/b/{{.Blog.Subdomain}}/layout#menu">Layout</a> tab. Here you choose how it looks.</p>
<div class="row">
+ <label>Alignment <span class="muted">(menu in the header)</span><br><select name="nav_align">
+ <option value="left"{{if eq $t.NavAlign "left"}} selected{{end}}>Left</option>
+ <option value="center"{{if eq $t.NavAlign "center"}} selected{{end}}>Centred</option>
+ <option value="right"{{if eq $t.NavAlign "right"}} selected{{end}}>Right</option>
+ </select></label>
<label>Link style<br><select name="nav_style">
<option value="normal"{{if eq $t.NavStyle "normal"}} selected{{end}}>Normal</option>
<option value="uppercase"{{if eq $t.NavStyle "uppercase"}} selected{{end}}>Uppercase, spaced</option>
</select></label>
</div>
- <label class="check"><input type="checkbox" name="nav_show_home"{{if $t.NavShowHome}} checked{{end}}> Include the home page in the menu</label>
<h3>Menu colours</h3>
<div class="row">
<label>Background<br><input type="color" name="nav_bg" value="{{$t.NavBg}}"></label>
@@ -167,16 +150,23 @@
<div class="card" id="footer">
<h2>Footer</h2>
- <label>Footer text<br><input name="footer_text" value="{{$t.FooterText}}" maxlength="2000" placeholder="© 2026 Me"></label>
+ <p class="muted small">The footer text, RSS link and other footer modules live on the <a href="/b/{{.Blog.Subdomain}}/layout#footer">Layout</a> tab.</p>
<div class="row">
<label>Background<br><input type="color" name="footer_bg" value="{{$t.FooterBg}}"></label>
<label>Text colour<br><input type="color" name="footer_color" value="{{$t.FooterColor}}"></label>
</div>
- <label class="check"><input type="checkbox" name="footer_show_rss"{{if $t.FooterShowRSS}} checked{{end}}> Show the RSS link in the footer</label>
</div>
-<div class="card" id="icon">
- <h2>Site icon</h2>
+<div class="card" id="logo">
+ <h2>Logo &amp; site icon</h2>
+ <div class="row">
+ <label>Logo size<br><select name="logo_size">
+ <option value="small"{{if eq $t.LogoSize "small"}} selected{{end}}>Small (48 px tall)</option>
+ <option value="medium"{{if eq $t.LogoSize "medium"}} selected{{end}}>Medium (80 px)</option>
+ <option value="large"{{if eq $t.LogoSize "large"}} selected{{end}}>Large (140 px)</option>
+ </select></label>
+ </div>
+ {{template "imagepick" (dict "name" "logo" "current" $t.Logo "images" .Data.images "label" "Logo" "hint" "Shown wherever you place a Logo module (Layout tab); it links to your front page.")}}
{{template "imagepick" (dict "name" "favicon" "current" $t.Favicon "images" .Data.images "label" "Favicon" "hint" "The small icon browsers show in the tab. A square PNG or ICO of 32–64 px works best; pick None for the Blogspace icon." "accept" "image/png,image/x-icon,image/gif,image/webp,image/jpeg,.ico")}}
</div>
diff --git a/internal/web/templates/dashboard/layout.html b/internal/web/templates/dashboard/layout.html
new file mode 100644
index 0000000..7691b1e
--- /dev/null
+++ b/internal/web/templates/dashboard/layout.html
@@ -0,0 +1,113 @@
+{{define "title"}}Layout · {{.Blog.Title}}{{end}}
+{{define "content"}}
+{{$t := .Data.theme}}
+{{$u := print "/b/" .Blog.Subdomain "/layout"}}
+<div class="pagehead">
+ <h1>Layout</h1>
+ <p class="lead muted">Decide what goes where on your blog. Every change here is saved straight away; refresh <a href="{{.BlogURL}}" target="_blank">your blog &#8599;</a> to see it. Colours and fonts are on the <a href="/b/{{.Blog.Subdomain}}/design">Design</a> tab.</p>
+</div>
+<p class="jumps"><a href="#columns">Columns</a><a href="#header">Header</a><a href="#left">Left column</a><a href="#main">Main content</a><a href="#right">Right column</a><a href="#footer">Footer</a><a href="#menu">Menu</a></p>
+
+<div class="card" id="columns">
+ <h2>Columns</h2>
+ <div class="lay">
+ <div class="lay-row lay-header{{if not $t.HeaderOn}} off{{end}}">Header{{if not $t.HeaderOn}} (hidden){{end}}</div>
+ <div class="lay-cols">
+ <div class="lay-col lay-side{{if not $t.LeftOn}} off{{end}}{{if not (index .Data.areas "left")}} empty{{end}}" style="width:{{$t.LeftWidth}}%">Left {{$t.LeftWidth}}%</div>
+ <div class="lay-col lay-main" style="width:{{$t.MainWidth}}%">Main content {{$t.MainWidth}}%</div>
+ <div class="lay-col lay-side{{if not $t.RightOn}} off{{end}}{{if not (index .Data.areas "right")}} empty{{end}}" style="width:{{$t.RightWidth}}%">Right {{$t.RightWidth}}%</div>
+ </div>
+ <div class="lay-row lay-footer{{if not $t.FooterOn}} off{{end}}">Footer{{if not $t.FooterOn}} (hidden){{end}}</div>
+ </div>
+ <form method="post" action="{{$u}}/widths">
+ <input type="hidden" name="_csrf" value="{{.CSRF}}">
+ <div class="row">
+ <label>Left column width<br><input type="number" name="left_width" value="{{$t.LeftWidth}}" min="15" max="40" step="5"> %</label>
+ <label>Right column width<br><input type="number" name="right_width" value="{{$t.RightWidth}}" min="15" max="40" step="5"> %</label>
+ </div>
+ <p class="muted small">Between 15% and 40% each; the main content takes the rest. A column only appears when it has at least one module; as soon as one does, the blog spreads over the whole page. On phones the columns stack: main content first, then the left and right columns, then the footer.</p>
+ <label class="check"><input type="checkbox" name="keep_columns"{{if $t.KeepColumns}} checked{{end}}> Always keep the columns' space, even when a column is empty or hidden <span class="muted">(the main content then has the same width on every page)</span></label>
+ <p class="actionrow"><button type="submit">Save columns</button></p>
+ </form>
+</div>
+
+{{template "area" (dict "area" "header" "root" . "hint" "The top of every page. Stack the title, a logo and the menu in the order you like. A banner image can be chosen on the Design tab.")}}
+{{template "area" (dict "area" "left" "root" . "hint" "A narrow column beside your posts. Good for the menu, an archive or recent posts.")}}
+
+<div class="card" id="main">
+ <div class="cardhead"><h2>Main content</h2></div>
+ <p class="muted small">Your posts and pages always show here. You can add your own HTML before and after them — an intro box, a newsletter form, a badge…</p>
+ <table class="modules">
+ {{range $area := .Data.areaOrder}}{{$mods := index $.Data.areas $area}}
+ <tr><th>{{index $.Data.areaNames $area}}</th>
+ {{if $mods}}{{$m := index $mods 0}}
+ <td>{{index $.Data.names $m.Kind}}{{with modsummary $m}} <span class="muted">— {{.}}</span>{{end}}</td>
+ <td class="nowrap actions"><a href="{{$u}}/modules/{{$m.ID}}/edit">edit</a> <a class="danger" href="{{$u}}/modules/{{$m.ID}}/delete">remove</a></td>
+ {{else}}
+ <td class="muted">Nothing extra.</td>
+ <td class="nowrap actions"><form method="post" action="{{$u}}/areas/{{$area}}/add" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="kind" value="html"><button class="mini">+ Add custom HTML</button></form></td>
+ {{end}}
+ </tr>{{end}}
+ </table>
+</div>
+
+{{template "area" (dict "area" "right" "root" . "hint" "A narrow column on the other side of your posts. Same choices as the left one.")}}
+{{template "area" (dict "area" "footer" "root" . "hint" "The bottom of every page: a copyright line, the RSS link, a list of your pages.")}}
+
+<div class="card" id="menu">
+ <h2>Menu</h2>
+ <p class="muted small">The links the Menu module shows, wherever you place it. Pages and outside links can be mixed in any order. Ticking "Show in menu" on a page does the same as adding it here.</p>
+ {{if .Data.menu}}<table class="modules">
+ {{range .Data.menu}}<tr>
+ <td class="nowrap order">
+ <form method="post" action="{{$u}}/menu/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="dir" value="up"><button class="mini" title="Move up">&#9650;</button></form>
+ <form method="post" action="{{$u}}/menu/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="dir" value="down"><button class="mini" title="Move down">&#9660;</button></form>
+ </td>
+ <td>{{.Text}} {{if .PageID}}<span class="tag">page</span>{{else}}<span class="tag">link</span> <span class="muted small">{{.URL}}</span>{{end}}</td>
+ <td class="nowrap actions"><form method="post" action="{{$u}}/menu/{{.ID}}/delete" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><button class="mini danger">Remove</button></form></td>
+ </tr>{{end}}
+ </table>{{else}}<p class="muted">The menu is empty.</p>{{end}}
+ <div class="cols addrow">
+ <form method="post" action="{{$u}}/menu/add-page" class="add">
+ <input type="hidden" name="_csrf" value="{{.CSRF}}">
+ <h3>Add a page</h3>
+ {{if .Data.freePages}}<label>Page<br><select name="page">{{range .Data.freePages}}<option value="{{.ID}}">{{.Title}}</option>{{end}}</select></label>
+ <button type="submit" class="secondary">Add page</button>
+ {{else}}<p class="muted small">Every page is already in the menu.</p>{{end}}
+ </form>
+ <form method="post" action="{{$u}}/menu/add-link" class="add">
+ <input type="hidden" name="_csrf" value="{{.CSRF}}">
+ <h3>Add a link</h3>
+ <label>Text<br><input name="label" maxlength="60" placeholder="My photos"></label>
+ <label>Address<br><input name="url" maxlength="500" placeholder="https://example.org"></label>
+ <button type="submit" class="secondary">Add link</button>
+ </form>
+ </div>
+</div>
+{{end}}
+
+{{/* One area card: on/off switch, its modules in order, and an "add" form. */}}
+{{define "area"}}{{$r := .root}}{{$area := .area}}{{$u := print "/b/" $r.Blog.Subdomain "/layout"}}{{$on := $r.Data.theme.AreaOn $area}}{{$mods := index $r.Data.areas $area}}
+<div class="card{{if not $on}} area-off{{end}}" id="{{$area}}">
+ <div class="cardhead">
+ <h2>{{index $r.Data.areaNames $area}}{{if not $on}} <span class="tag off">hidden</span>{{end}}</h2>
+ <form method="post" action="{{$u}}/areas/{{$area}}/toggle" class="inline switch"><input type="hidden" name="_csrf" value="{{$r.CSRF}}"><button class="mini">{{if $on}}Hide this area{{else}}Show this area{{end}}</button></form>
+ </div>
+ <p class="muted small">{{.hint}}</p>
+ {{if $mods}}<table class="modules">
+ {{range $mods}}<tr>
+ <td class="nowrap order">
+ <form method="post" action="{{$u}}/modules/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$r.CSRF}}"><input type="hidden" name="dir" value="up"><button class="mini" title="Move up">&#9650;</button></form>
+ <form method="post" action="{{$u}}/modules/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$r.CSRF}}"><input type="hidden" name="dir" value="down"><button class="mini" title="Move down">&#9660;</button></form>
+ </td>
+ <td>{{index $r.Data.names .Kind}}{{with modsummary .}} <span class="muted">— {{.}}</span>{{end}}
+ {{if and (eq .Kind "logo") (not $r.Data.theme.Logo)}}<span class="tag off">no logo chosen</span> <span class="muted small">pick one on the <a href="/b/{{$r.Blog.Subdomain}}/design#logo">Design</a> tab</span>{{end}}</td>
+ <td class="nowrap actions">{{if modsettings .Kind}}<a href="{{$u}}/modules/{{.ID}}/edit">edit</a> {{end}}<a class="danger" href="{{$u}}/modules/{{.ID}}/delete">remove</a></td>
+ </tr>{{end}}
+ </table>{{else}}<p class="muted">Nothing here yet{{if ne $area "header"}} — this area is not shown until you add a module{{end}}.</p>{{end}}
+ <form method="post" action="{{$u}}/areas/{{$area}}/add" class="addmod">
+ <input type="hidden" name="_csrf" value="{{$r.CSRF}}">
+ <label>Add a module<br><select name="kind">{{range index $r.Data.kinds $area}}<option value="{{.}}">{{index $r.Data.names .}}</option>{{end}}</select></label>
+ <button type="submit" class="secondary">Add</button>
+ </form>
+</div>{{end}}
diff --git a/internal/web/templates/dashboard/module_form.html b/internal/web/templates/dashboard/module_form.html
new file mode 100644
index 0000000..b63b405
--- /dev/null
+++ b/internal/web/templates/dashboard/module_form.html
@@ -0,0 +1,34 @@
+{{define "title"}}{{.Data.name}} · {{.Blog.Title}}{{end}}
+{{define "content"}}
+{{$m := .Data.module}}
+<div class="card">
+ <h1>{{.Data.name}} <span class="muted small">in the {{lower .Data.area}}</span></h1>
+ <form method="post">
+ <input type="hidden" name="_csrf" value="{{.CSRF}}">
+ {{if or (eq $m.Kind "recent") (eq $m.Kind "archive") (eq $m.Kind "sitemap") (and (eq $m.Kind "html") (or (eq $m.Area "left") (eq $m.Area "right") (eq $m.Area "footer")))}}
+ <label>Heading <span class="muted">(optional)</span><br><input name="title" value="{{$m.Title}}" maxlength="120" placeholder="{{if eq $m.Kind "recent"}}Recent posts{{else if eq $m.Kind "archive"}}Archive{{else if eq $m.Kind "sitemap"}}Pages{{end}}"></label>
+ {{end}}
+ {{if eq $m.Kind "recent"}}
+ <div class="row"><label>How many posts<br><select name="count">{{range $n := .Data.counts}}<option value="{{$n}}"{{if eq $n $m.Count}} selected{{end}}>{{$n}}</option>{{end}}</select></label></div>
+ <p class="muted small">The newest published posts from every page of your blog, newest first.</p>
+ {{else if eq $m.Kind "archive"}}
+ <p class="muted small">Every published post grouped by year and month, newest first. Years and months fold open and shut; the latest year starts open.</p>
+ {{else if eq $m.Kind "sitemap"}}
+ <p class="muted small">Links to every page of your blog.</p>
+ {{else if eq $m.Kind "html"}}
+ <label for="mod-body">HTML</label>
+ <textarea id="mod-body" name="body" rows="14" class="mono" spellcheck="false" placeholder="&lt;p&gt;Hello!&lt;/p&gt;">{{$m.Body}}</textarea>
+ <p class="muted small">Put into your page exactly as written — embeds, badges and scripts all work. Anything broken here (an unclosed tag, say) can break the page around it, so check your blog after saving. 20 KB at most.</p>
+ {{else if eq $m.Kind "text"}}
+ <label for="mod-body">Text</label>
+ <textarea id="mod-body" name="body" rows="4" placeholder="© 2026 Me">{{$m.Body}}</textarea>
+ <p class="muted small">Plain text; each line becomes its own paragraph.</p>
+ {{end}}
+ <p class="actionrow">
+ <button type="submit">Save</button>
+ <a class="btn secondary" href="{{.BlogURL}}" target="_blank">View blog &#8599;</a>
+ <a href="/b/{{.Blog.Subdomain}}/layout#{{if or (eq $m.Area "above") (eq $m.Area "below")}}main{{else}}{{$m.Area}}{{end}}">Back to layout</a>
+ </p>
+ </form>
+</div>
+{{end}}
diff --git a/internal/web/templates/dashboard/overview.html b/internal/web/templates/dashboard/overview.html
index fbe49f9..acd539d 100644
--- a/internal/web/templates/dashboard/overview.html
+++ b/internal/web/templates/dashboard/overview.html
@@ -7,7 +7,7 @@
<div class="card">
<h2>Quick actions</h2>
<p><a class="btn" href="/b/{{.Blog.Subdomain}}/posts/new">Write a new post</a></p>
- <p><a href="/b/{{.Blog.Subdomain}}/pages/new">Add a page</a> &middot; <a href="/b/{{.Blog.Subdomain}}/announcements/new">Post an announcement</a> &middot; <a href="/b/{{.Blog.Subdomain}}/design">Change the look</a> &middot; <a href="/b/{{.Blog.Subdomain}}/images">Upload images</a></p>
+ <p><a href="/b/{{.Blog.Subdomain}}/pages/new">Add a page</a> &middot; <a href="/b/{{.Blog.Subdomain}}/announcements/new">Post an announcement</a> &middot; <a href="/b/{{.Blog.Subdomain}}/layout">Arrange the layout</a> &middot; <a href="/b/{{.Blog.Subdomain}}/design">Change the look</a> &middot; <a href="/b/{{.Blog.Subdomain}}/images">Upload images</a></p>
</div>
<div class="card">
<h2>Pages</h2>
diff --git a/internal/web/templates/dashboard/pages.html b/internal/web/templates/dashboard/pages.html
index 5e9aca5..eeb09c5 100644
--- a/internal/web/templates/dashboard/pages.html
+++ b/internal/web/templates/dashboard/pages.html
@@ -3,18 +3,18 @@
<div class="pagehead">
<h1>Pages</h1>
<div class="actions"><a class="btn" href="/b/{{.Blog.Subdomain}}/pages/new">+ New page</a></div>
- <p class="lead muted">Pages appear in your blog's menu in this order. Each page holds its own list of posts.</p>
+ <p class="lead muted">Each page holds its own list of posts. Which pages are in the menu, and in what order, is set on the <a href="/b/{{.Blog.Subdomain}}/layout#menu">Layout</a> tab.</p>
</div>
<div class="card">
<table>
- <tr><th>Menu order</th><th>Title</th><th>Address</th><th>Posts</th><th></th></tr>
+ <tr><th>Order</th><th>Title</th><th>Address</th><th>Posts</th><th></th></tr>
{{range .Data.pages}}<tr>
<td class="nowrap">
<form method="post" action="/b/{{$.Blog.Subdomain}}/pages/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="dir" value="up"><button class="mini" title="Move up">&#9650;</button></form>
<form method="post" action="/b/{{$.Blog.Subdomain}}/pages/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="dir" value="down"><button class="mini" title="Move down">&#9660;</button></form>
</td>
<td><a href="/b/{{$.Blog.Subdomain}}/pages/{{.ID}}/edit">{{.Title}}</a>
- {{if .IsHome}}<span class="tag">home</span>{{end}}{{if not .ShowInNav}}<span class="tag">hidden</span>{{end}}</td>
+ {{if .IsHome}}<span class="tag">home</span>{{end}}{{if not .ShowInNav}}<span class="tag off">not in menu</span>{{end}}</td>
<td><a href="{{$.BlogURL}}{{if .IsHome}}/{{else}}/{{.Slug}}{{end}}" target="_blank">{{if .IsHome}}/{{else}}/{{.Slug}}{{end}} &#8599;</a></td>
<td><a href="/b/{{$.Blog.Subdomain}}/posts?page={{.ID}}">{{.PostCount}}</a></td>
<td class="nowrap">
diff --git a/internal/web/templates/dashboard/section_form.html b/internal/web/templates/dashboard/section_form.html
index 9d4529c..7eeaf7c 100644
--- a/internal/web/templates/dashboard/section_form.html
+++ b/internal/web/templates/dashboard/section_form.html
@@ -7,10 +7,14 @@
<input type="hidden" name="_csrf" value="{{.CSRF}}">
<label>Title <span class="muted">(optional)</span><br><input name="title" value="{{$s.Title}}" maxlength="120" autofocus placeholder="Next meeting"></label>
<div class="row">
- <label>Where<br><select name="placement">
- <option value="above"{{if eq $s.Placement "above"}} selected{{end}}>Above the posts</option>
- <option value="below"{{if eq $s.Placement "below"}} selected{{end}}>Below the posts</option>
- <option value="sidebar"{{if eq $s.Placement "sidebar"}} selected{{end}}>Sidebar, under the menu</option>
+ <label>Where<br><select name="column">
+ <option value="main"{{if eq $s.Column "main"}} selected{{end}}>Main content (with the posts)</option>
+ <option value="left"{{if eq $s.Column "left"}} selected{{end}}>Left column</option>
+ <option value="right"{{if eq $s.Column "right"}} selected{{end}}>Right column</option>
+ </select></label>
+ <label>Position<br><select name="position">
+ <option value="top"{{if eq $s.Position "top"}} selected{{end}}>At the top</option>
+ <option value="bottom"{{if eq $s.Position "bottom"}} selected{{end}}>At the bottom</option>
</select></label>
<label>Look<br><select name="style">
<option value="note"{{if eq $s.Style "note"}} selected{{end}}>Highlighted</option>
@@ -18,7 +22,7 @@
<option value="plain"{{if eq $s.Style "plain"}} selected{{end}}>Plain</option>
</select></label>
</div>
- <p class="muted small">The sidebar spot only exists when the menu is a sidebar (see <a href="/b/{{.Blog.Subdomain}}/design">Design</a>); otherwise a sidebar announcement is shown above the posts.</p>
+ <p class="muted small">"Top" of a side column means above its modules, "bottom" below them. If that column is not shown on your blog (see <a href="/b/{{.Blog.Subdomain}}/layout">Layout</a>) the announcement moves to the main content instead.</p>
<label for="ed-body">Text <span class="muted">(Markdown)</span></label>
{{template "editor" (dict "name" "body" "value" $s.BodyMD "rows" 10 "tall" false "upload" (print "/b/" .Blog.Subdomain "/images/upload") "csrf" .CSRF)}}
<label class="check"><input type="checkbox" name="enabled"{{if $s.Enabled}} checked{{end}}> Shown on the blog</label>
diff --git a/internal/web/templates/dashboard/sections.html b/internal/web/templates/dashboard/sections.html
index d2fade7..1834229 100644
--- a/internal/web/templates/dashboard/sections.html
+++ b/internal/web/templates/dashboard/sections.html
@@ -16,7 +16,7 @@
<form method="post" action="{{$u}}/{{.ID}}/move" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="dir" value="down"><button class="mini" title="Move down">&#9660;</button></form>
</td>
<td><a href="{{$u}}/{{.ID}}/edit">{{if .Title}}{{.Title}}{{else}}(untitled){{end}}</a>{{if not .Enabled}}<span class="tag off">hidden</span>{{end}}</td>
- <td>{{if eq .Placement "above"}}Above the posts{{else if eq .Placement "below"}}Below the posts{{else}}Sidebar{{end}}</td>
+ <td>{{if eq .Column "main"}}Main content{{else if eq .Column "left"}}Left column{{else}}Right column{{end}}, {{.Position}}</td>
<td>{{if eq .Style "note"}}Highlighted{{else if eq .Style "warning"}}Warning{{else}}Plain{{end}}</td>
<td class="nowrap">
<form method="post" action="{{$u}}/{{.ID}}/toggle" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><button class="mini">{{if .Enabled}}Hide{{else}}Show{{end}}</button></form>
diff --git a/internal/web/templates/layouts/blog.html b/internal/web/templates/layouts/blog.html
index 3447e1f..deaaa75 100644
--- a/internal/web/templates/layouts/blog.html
+++ b/internal/web/templates/layouts/blog.html
@@ -11,30 +11,35 @@
{{end}}<link rel="stylesheet" href="/static/blog.css">
<style>{{css .Data.css}}</style>
</head>
-<body class="nav-{{.Data.theme.NavPosition}}">
-{{if eq .Data.theme.NavPosition "top-bar"}}{{template "blognav" .}}{{end}}
-<div class="site-header">
+{{$l := .Data.layout}}
+<body class="{{if $l.HasLeft}}has-left {{end}}{{if $l.HasRight}}has-right{{end}}">
+{{if or $l.Header .Data.theme.HeaderImage}}{{if .Data.theme.HeaderOn}}<div class="site-header">
{{if .Data.theme.HeaderImage}}<div class="header-image"><a href="/"><img src="/media/{{.Data.theme.HeaderImage}}" alt=""></a></div>{{end}}
- {{if .Data.theme.HeaderShowTitle}}<div class="wrap header-text">
- <h1 class="site-title"><a href="/">{{.Blog.Title}}</a></h1>
- {{if .Blog.Tagline}}<p class="tagline">{{.Blog.Tagline}}</p>{{end}}
- </div>{{end}}
-</div>
-{{if eq .Data.theme.NavPosition "below-header"}}{{template "blognav" .}}{{end}}
-<div class="wrap body-wrap">
- {{if eq .Data.theme.NavPosition "left-sidebar"}}{{template "blognav" .}}{{if .Data.sidebar}}<div class="sidebar-notices">{{template "notices" .Data.sidebar}}</div>{{end}}{{end}}
- <div class="content">
- {{template "notices" .Data.above}}
+ {{range $l.Header}}{{template "module" (dict "m" . "root" $)}}{{end}}
+</div>{{end}}{{end}}
+<div class="wrap body-wrap cols">
+ {{$n := .Data.notices}}
+ <div class="col col-main"><div class="content">
+ {{template "notices" (index $n "main-top")}}
+ {{range $l.Above}}{{template "module" (dict "m" . "root" $)}}{{end}}
{{template "content" .}}
- {{template "notices" .Data.below}}
- </div>
-</div>
-<div class="site-footer">
- <div class="wrap footer-inner">
- {{if .Data.theme.FooterText}}<p>{{.Data.theme.FooterText}}</p>{{end}}
- <p class="small">{{if .Data.theme.FooterShowRSS}}<a href="/feed.xml">RSS</a> &middot; {{end}}{{.Blog.Title}}</p>
- </div>
+ {{range $l.Below}}{{template "module" (dict "m" . "root" $)}}{{end}}
+ {{template "notices" (index $n "main-bottom")}}
+ </div></div>
+ {{if $l.HasLeft}}<div class="col col-left{{if and (not $l.Left) (not (index $n "left-top")) (not (index $n "left-bottom"))}} col-empty{{end}}">
+ {{template "notices" (index $n "left-top")}}
+ {{range $l.Left}}{{template "module" (dict "m" . "root" $)}}{{end}}
+ {{template "notices" (index $n "left-bottom")}}
+ </div>{{end}}
+ {{if $l.HasRight}}<div class="col col-right{{if and (not $l.Right) (not (index $n "right-top")) (not (index $n "right-bottom"))}} col-empty{{end}}">
+ {{template "notices" (index $n "right-top")}}
+ {{range $l.Right}}{{template "module" (dict "m" . "root" $)}}{{end}}
+ {{template "notices" (index $n "right-bottom")}}
+ </div>{{end}}
</div>
+{{if $l.Footer}}<div class="site-footer"><div class="wrap footer-inner">
+ {{range $l.Footer}}{{template "module" (dict "m" . "root" $)}}{{end}}
+</div></div>{{end}}
</body>
</html>{{end}}
@@ -44,6 +49,37 @@
</aside>
{{end}}{{end}}
-{{define "blognav"}}<div class="site-nav"><div class="wrap nav-inner">
-{{range .Data.nav}}<a href="{{if .IsHome}}/{{else}}/{{.Slug}}{{end}}"{{if and $.Data.page (eq $.Data.page.ID .ID)}} class="active"{{end}}>{{.Title}}</a>
-{{end}}</div></div>{{end}}
+{{/* One layout module. .m is the store.Module, .root the page data; the same
+ kind renders differently in the header (big blocks) and elsewhere (boxes). */}}
+{{define "module"}}{{$m := .m}}{{$r := .root}}{{$t := $r.Data.theme}}
+{{- if eq $m.Kind "title"}}<div class="wrap header-text">
+ <h1 class="site-title"><a href="/">{{$r.Blog.Title}}</a></h1>
+ {{if $r.Blog.Tagline}}<p class="tagline">{{$r.Blog.Tagline}}</p>{{end}}
+</div>
+{{- else if eq $m.Kind "logo"}}{{if $t.Logo}}{{if eq $m.Area "header"}}<div class="wrap header-logo"><a href="/"><img class="logo" src="/media/{{$t.Logo}}" alt="{{$r.Blog.Title}}"></a></div>
+{{else}}<div class="mod mod-logo"><a href="/"><img class="logo" src="/media/{{$t.Logo}}" alt="{{$r.Blog.Title}}"></a></div>{{end}}{{end}}
+{{- else if eq $m.Kind "menu"}}{{if eq $m.Area "header"}}<div class="site-nav"><div class="wrap nav-inner">{{template "menulinks" $r}}</div></div>
+{{else}}<div class="mod site-nav mod-menu">{{template "modtitle" $m}}<div class="nav-inner">{{template "menulinks" $r}}</div></div>{{end}}
+{{- else if eq $m.Kind "recent"}}<div class="mod mod-recent">{{template "modtitle" $m}}
+ <ul>{{range $i, $p := $r.Data.recent}}{{if lt $i $m.Count}}<li><a href="/{{$p.PageSlug}}/{{$p.Slug}}">{{$p.Title}}</a></li>{{end}}{{end}}</ul>
+</div>
+{{- else if eq $m.Kind "archive"}}<div class="mod mod-archive">{{template "modtitle" $m}}
+ {{range $i, $y := $r.Data.archive}}<details{{if eq $i 0}} open{{end}}><summary>{{$y.Year}} <span class="muted">({{$y.Count}})</span></summary>
+ {{range $y.Months}}<details><summary>{{.Name}} <span class="muted">({{len .Posts}})</span></summary>
+ <ul>{{range .Posts}}<li><a href="/{{.PageSlug}}/{{.Slug}}">{{.Title}}</a></li>{{end}}</ul>
+ </details>{{end}}
+ </details>{{else}}<p class="muted">No posts yet.</p>{{end}}
+</div>
+{{- else if eq $m.Kind "html"}}{{if not $m.Body}}{{else if or (eq $m.Area "above") (eq $m.Area "below")}}<div class="custom-html custom-html-{{$m.Area}}">{{html $m.Body}}</div>
+{{else}}<div class="mod mod-html">{{template "modtitle" $m}}{{html $m.Body}}</div>{{end}}
+{{- else if eq $m.Kind "rss"}}<p class="mod mod-rss small"><a href="/feed.xml">RSS feed</a></p>
+{{- else if eq $m.Kind "text"}}{{if $m.Body}}<div class="mod mod-text">{{range split $m.Body "\n"}}{{if .}}<p>{{.}}</p>{{end}}{{end}}</div>{{end}}
+{{- else if eq $m.Kind "sitemap"}}<div class="mod mod-sitemap">{{template "modtitle" $m}}
+ <ul>{{range $r.Data.pages}}<li><a href="{{if .IsHome}}/{{else}}/{{.Slug}}{{end}}">{{.Title}}</a></li>{{end}}</ul>
+</div>
+{{- end}}{{end}}
+
+{{define "modtitle"}}{{if .Title}}<h3 class="mod-title">{{.Title}}</h3>{{end}}{{end}}
+
+{{define "menulinks"}}{{range .Data.menu}}<a href="{{.Href}}"{{if and .PageID $.Data.page (eq $.Data.page.ID (deref64 .PageID))}} class="active"{{end}}>{{.Text}}</a>
+{{end}}{{end}}
diff --git a/internal/web/templates/partials/dashnav.html b/internal/web/templates/partials/dashnav.html
index f54cb2d..e46d7c3 100644
--- a/internal/web/templates/partials/dashnav.html
+++ b/internal/web/templates/partials/dashnav.html
@@ -21,6 +21,7 @@
<a href="{{$b}}posts"{{if hasPrefix .Path (print $b "posts")}} class="active"{{end}}>Posts</a>
<a href="{{$b}}pages"{{if hasPrefix .Path (print $b "pages")}} class="active"{{end}}>Pages</a>
<a href="{{$b}}announcements"{{if hasPrefix .Path (print $b "announcements")}} class="active"{{end}}>Announcements</a>
+ <a href="{{$b}}layout"{{if hasPrefix .Path (print $b "layout")}} class="active"{{end}}>Layout</a>
<a href="{{$b}}design"{{if hasPrefix .Path (print $b "design")}} class="active"{{end}}>Design</a>
<a href="{{$b}}images"{{if hasPrefix .Path (print $b "images")}} class="active"{{end}}>Images</a>
<a href="{{$b}}settings"{{if hasPrefix .Path (print $b "settings")}} class="active"{{end}}>Settings</a>