aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/templates/partials
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/templates/partials')
-rw-r--r--internal/web/templates/partials/editor.html11
-rw-r--r--internal/web/templates/partials/imagelib.html72
-rw-r--r--internal/web/templates/partials/imagepick.html13
-rw-r--r--internal/web/templates/partials/mdhelp.html2
-rw-r--r--internal/web/templates/partials/postlist.html6
5 files changed, 91 insertions, 13 deletions
diff --git a/internal/web/templates/partials/editor.html b/internal/web/templates/partials/editor.html
index a0f8c7f..cfc5642 100644
--- a/internal/web/templates/partials/editor.html
+++ b/internal/web/templates/partials/editor.html
@@ -1,10 +1,13 @@
+{{/* A second editor in the same form (a page's outro) shares the first one's
+ Format row: pass "formatof" with that editor's name and no radios are
+ rendered, the script watches the other row instead. */}}
{{define "editor"}}<div class="editor{{if eq .format "html"}} html{{end}}" id="ed-{{.name}}-wrap">
-<p class="ed-format" id="ed-{{.name}}-format">
+{{if not .formatof}}<p class="ed-format" id="ed-{{.name}}-format">
<span class="muted">{{t "Format"}}:</span>
<label><input type="radio" name="format" value="markdown"{{if ne .format "html"}} checked{{end}}> Markdown</label>
<label><input type="radio" name="format" value="html"{{if eq .format "html"}} checked{{end}}> HTML</label>
<span class="muted small ed-htmlnote">{{t "Put on your blog exactly as written — embeds and scripts all work. An unclosed tag can break the page around it, so check your blog after saving."}}</span>
-</p>
+</p>{{end}}
<div class="ed-toolbar" id="ed-{{.name}}-bar" hidden>
<span class="ed-group ed-md">
<button type="button" data-cmd="bold" title="{{t "Bold"}} (Ctrl+B)"><svg viewBox="0 0 16 16"><path d="M4 2h5a3.5 3.5 0 0 1 2.4 6 3.75 3.75 0 0 1-2.2 6.8H4zM6 4v3.5h3a1.75 1.75 0 0 0 0-3.5zm0 5.5V13h3.2a1.75 1.75 0 0 0 0-3.5z"/></svg></button>
@@ -41,7 +44,7 @@
<div class="ed-preview" id="ed-{{.name}}-preview" hidden></div>
<iframe class="ed-preview-frame" id="ed-{{.name}}-frame" sandbox title="{{t "Preview"}}" hidden></iframe>
<div class="editor-tools">
- <label class="upload">{{t "Insert file"}} <input type="file" name="inline_file" id="ed-{{.name}}-file"></label>
+ <label class="upload">{{t "Insert file"}} <input type="file" name="{{.name}}_file" id="ed-{{.name}}-file"></label>
<span class="upload-status" id="ed-{{.name}}-status"></span>
<span class="muted small">{{t "…or paste / drop a file into the text. Images are shown, other files linked."}}</span>
<noscript><span class="muted small">{{t "The file is added at the end of the text when you save."}}</span></noscript>
@@ -52,7 +55,7 @@
(function () {
var id = "ed-{{.name}}", ta = document.getElementById(id), inp = document.getElementById(id + "-file"), st = document.getElementById(id + "-status"),
wrap = document.getElementById(id + "-wrap"), bar = document.getElementById(id + "-bar"), lk = document.getElementById(id + "-link"), pv = document.getElementById(id + "-preview"),
- frame = document.getElementById(id + "-frame"), fmt = document.getElementById(id + "-format");
+ frame = document.getElementById(id + "-frame"), fmt = document.getElementById({{if .formatof}}"ed-{{.formatof}}-format"{{else}}id + "-format"{{end}});
if (!window.fetch || !window.FormData || !ta || !inp) return; // old browser: plain textarea, the file is uploaded on save
bar.hidden = false;
diff --git a/internal/web/templates/partials/imagelib.html b/internal/web/templates/partials/imagelib.html
new file mode 100644
index 0000000..bc94f2b
--- /dev/null
+++ b/internal/web/templates/partials/imagelib.html
@@ -0,0 +1,72 @@
+{{/* The image library panel behind every imagepick's "Choose from library…":
+ one per form, fetched page by page from the Files page's JSON. Its script
+ is self-contained: a choice goes into the imagepick's select and fires a
+ change event there, so the form sees it like any other edit. Without
+ fetch the buttons hide and the select stands in. .sub is the blog,
+ .count how many images the library has. */}}
+{{define "imagelib"}}<div class="picker" id="picker" data-sub="{{.sub}}" hidden>
+ <div class="picker-box">
+ <div class="picker-head">
+ <input type="search" placeholder="{{t "Filter by name…"}}" aria-label="{{t "Filter by name…"}}">
+ <span class="muted small pcount">{{tf "%d images" .count}}</span>
+ <button type="button" class="secondary small" data-pick="close">{{t "Close"}}</button>
+ </div>
+ <div class="picks"></div>
+ <p class="muted small pnone" hidden>{{t "No images match."}}</p>
+ <div class="pager"><button type="button" class="mini" data-pick="prev">&larr; {{t "Newer"}}</button> <span class="pnum"></span> <button type="button" class="mini" data-pick="next">{{t "Older"}} &rarr;</button></div>
+ </div>
+</div>
+<script>
+(function () {
+ var panel = document.getElementById("picker"), sub = panel.getAttribute("data-sub"), pick = null, page = 1, filter = "", timer;
+ function q(sel, el) { return (el || document).querySelector(sel); }
+ function qa(sel, el) { return Array.prototype.slice.call((el || document).querySelectorAll(sel)); }
+ if (!window.fetch || !panel.closest) return; // the select stands in
+ qa(".imagepick").forEach(function (ip) { ip.className += " js"; });
+ document.addEventListener("click", function (e) {
+ var b = e.target.closest("[data-pick]"), ip = b && b.closest(".imagepick");
+ if (!ip) return;
+ if (b.getAttribute("data-pick") === "open") { pick = ip; panel.hidden = false; q("input", panel).value = filter = ""; load(1); q("input", panel).focus(); }
+ else if (b.getAttribute("data-pick") === "clear") { choose(ip, "none", ""); }
+ });
+ panel.addEventListener("click", function (e) {
+ var b = e.target.closest("[data-pick],.pick");
+ if (!b) return;
+ if (b.className.indexOf("pick") === 0) { choose(pick, b.getAttribute("data-id"), b.getAttribute("data-name")); panel.hidden = true; return; }
+ switch (b.getAttribute("data-pick")) {
+ case "close": panel.hidden = true; break;
+ case "prev": load(page - 1); break;
+ case "next": load(page + 1); break;
+ }
+ });
+ panel.addEventListener("keydown", function (e) { if (e.key === "Escape") panel.hidden = true; });
+ q("input", panel).addEventListener("input", function () { clearTimeout(timer); timer = setTimeout(function () { filter = q("input", panel).value.trim(); load(1); }, 250); });
+ function load(n) {
+ var url = "/b/" + sub + "/files?kind=image&p=" + n + (filter ? "&q=" + encodeURIComponent(filter) : "");
+ fetch(url, { headers: { Accept: "application/json" }, credentials: "same-origin" }).then(function (r) { return r.json(); }).then(function (d) {
+ page = d.page;
+ var grid = q(".picks", panel); grid.innerHTML = "";
+ (d.files || []).forEach(function (f) {
+ var b = document.createElement("button"), img = document.createElement("img"), s = document.createElement("span");
+ b.type = "button"; b.className = "pick"; b.setAttribute("data-id", f.id); b.setAttribute("data-name", f.filename);
+ img.loading = "lazy"; img.src = "/b/" + sub + "/media/" + f.id; img.alt = "";
+ s.className = "name"; s.textContent = f.filename;
+ b.appendChild(img); b.appendChild(s); grid.appendChild(b);
+ });
+ q(".pnone", panel).hidden = !!(d.files && d.files.length);
+ q(".pnum", panel).textContent = d.last > 1 ? d.page + " / " + d.last : "";
+ q("[data-pick=prev]", panel).disabled = d.page <= 1; q("[data-pick=next]", panel).disabled = d.page >= d.last;
+ });
+ }
+ function choose(ip, id, name) { // the select stays the value the form sends
+ var sel = q("select", ip), pv = q(".preview", ip), found = false;
+ for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value === id) found = true;
+ if (!found) { var o = document.createElement("option"); o.value = id; o.textContent = name; sel.appendChild(o); }
+ sel.value = id;
+ pv.innerHTML = id === "none" ? '<span class="swatch">&times;</span>' : "";
+ if (id !== "none") { var img = document.createElement("img"); img.src = "/b/" + sub + "/media/" + id; img.alt = ""; pv.appendChild(img); }
+ var ev; try { ev = new Event("change", { bubbles: true }); } catch (_) { ev = document.createEvent("Event"); ev.initEvent("change", true, false); }
+ sel.dispatchEvent(ev);
+ }
+})();
+</script>{{end}}
diff --git a/internal/web/templates/partials/imagepick.html b/internal/web/templates/partials/imagepick.html
index 47c2146..ed9368c 100644
--- a/internal/web/templates/partials/imagepick.html
+++ b/internal/web/templates/partials/imagepick.html
@@ -1,19 +1,20 @@
-{{/* One image field of the design form. The select lists the recent images
- (plus the chosen one) so it works without the script; with it the select is
- hidden and "Choose from library…" opens the shared panel at the end of
- design.html, which fetches the library page by page. */}}
+{{/* One image field of a form (the design page, a post's featured image).
+ The select lists the recent images (plus the chosen one) so it works
+ without the script; with it the select is hidden and "Choose from
+ library…" opens the form's imagelib panel, which fetches the library
+ page by page and marks the pickers with .js. */}}
{{define "imagepick"}}{{$cur := .current}}<div class="imagepick" data-name="{{.name}}">
<label>{{.label}}</label>
{{if .hint}}<p class="muted small">{{.hint}}</p>{{end}}
<div class="pickrow">
<span class="preview">{{if $cur}}<img src="/b/{{.sub}}/media/{{$cur}}" alt="">{{else}}<span class="swatch">&times;</span>{{end}}</span>
<span class="pickctl">
- <select name="{{.name}}" class="nojs">
+ <select name="{{.name}}" class="ip-nojs">
<option value="none"{{if not $cur}} selected{{end}}>{{t "None"}}</option>
{{range .images}}<option value="{{.ID}}"{{if eq .ID.String $cur}} selected{{end}}>{{.Filename}}</option>{{end}}
{{if and $cur (not (index .names $cur))}}<option value="{{$cur}}" selected>{{t "Current image"}}</option>{{end}}
</select>
- <span class="jsonly"><button type="button" class="secondary small" data-pick="open">{{t "Choose from library…"}}</button> <button type="button" class="secondary small" data-pick="clear">{{t "None"}}</button></span>
+ <span class="ip-jsonly"><button type="button" class="secondary small" data-pick="open">{{t "Choose from library…"}}</button> <button type="button" class="secondary small" data-pick="clear">{{t "None"}}</button></span>
<label class="upload">{{t "…or upload a new one"}}<br><input type="file" name="{{.name}}_file" accept="{{if .accept}}{{.accept}}{{else}}image/*{{end}}"></label>
</span>
</div>
diff --git a/internal/web/templates/partials/mdhelp.html b/internal/web/templates/partials/mdhelp.html
index 5489510..cbf9b02 100644
--- a/internal/web/templates/partials/mdhelp.html
+++ b/internal/web/templates/partials/mdhelp.html
@@ -5,5 +5,5 @@
<tr><td><code>[{{t "link text"}}](https://example.org)</code></td><td><code>![{{t "description"}}](/media/…)</code>, <code>[{{t "file name"}}](/media/…)</code> — <em>{{t "Insert file"}}</em> {{t "writes this for you"}}</td></tr>
<tr><td><code>- {{t "list item"}}</code> / <code>1. {{t "numbered"}}</code></td><td><code>&gt; {{t "quote"}}</code>, <code>`{{t "code"}}`</code>, <code>---</code> {{t "for a line"}}</td></tr>
</table>
- <p class="muted small">{{t "Blank line = new paragraph. Press Enter once for a line break."}}</p>
+ <p class="muted small">{{t "Blank line = new paragraph. Press Enter once for a line break."}} {{t "A line with"}} <code>&lt;!--more--&gt;</code> {{t "marks where the summary ends on listings that show one."}}</p>
</details>{{end}}
diff --git a/internal/web/templates/partials/postlist.html b/internal/web/templates/partials/postlist.html
index 01e1037..b1e43c6 100644
--- a/internal/web/templates/partials/postlist.html
+++ b/internal/web/templates/partials/postlist.html
@@ -3,11 +3,13 @@
own path, which ?p=N is appended to). */}}
{{define "postlist"}}
{{range .Data.posts}}
-<article class="post">
+<article class="post{{if .Image}} has-thumb{{end}}">
+ {{if .Image}}<a class="post-thumb" href="/{{.PageSlug}}/{{.Slug}}"><img src="/media/{{.Image}}" alt=""></a>{{end}}
<h2 class="post-title"><a href="/{{.PageSlug}}/{{.Slug}}">{{.Title}}</a></h2>
{{if $.Data.theme.ShowDates}}<p class="post-date">{{postdate $.Data.theme.DateFormat .CreatedAt}}</p>{{end}}
{{template "posttags" .Tags}}
- <div class="post-body">{{html .BodyHTML}}</div>
+ <div class="post-body">{{html .Excerpt}}</div>
+ {{if .HasMore}}<p class="read-more"><a href="/{{.PageSlug}}/{{.Slug}}">{{t "Read more"}} &rarr;</a></p>{{end}}
</article>
{{end}}
{{if gt .Data.lastPage 1}}