aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/templates
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/templates')
-rw-r--r--internal/web/templates/admin/delete_user.html2
-rw-r--r--internal/web/templates/admin/index.html9
-rw-r--r--internal/web/templates/dashboard/files.html95
-rw-r--r--internal/web/templates/dashboard/images.html24
-rw-r--r--internal/web/templates/dashboard/overview.html2
-rw-r--r--internal/web/templates/dashboard/page_form.html2
-rw-r--r--internal/web/templates/dashboard/post_form.html2
-rw-r--r--internal/web/templates/dashboard/section_form.html2
-rw-r--r--internal/web/templates/partials/dashnav.html2
-rw-r--r--internal/web/templates/partials/editor.html18
-rw-r--r--internal/web/templates/partials/mdhelp.html2
11 files changed, 121 insertions, 39 deletions
diff --git a/internal/web/templates/admin/delete_user.html b/internal/web/templates/admin/delete_user.html
index 886ff1a..79bf2fc 100644
--- a/internal/web/templates/admin/delete_user.html
+++ b/internal/web/templates/admin/delete_user.html
@@ -2,7 +2,7 @@
{{define "content"}}
<div class="card narrow">
<h1>{{tf "Delete %s?" .Data.target.Username}}</h1>
- <p>{{t "This permanently deletes the user"}} <strong>{{.Data.target.Username}}</strong>{{t ", their blog, and every page, post and image in it."}}</p>
+ <p>{{t "This permanently deletes the user"}} <strong>{{.Data.target.Username}}</strong>{{t ", their blog, and every page, post and file in it."}}</p>
<form method="post" action="/admin/users/{{.Data.target.ID}}/delete">
<input type="hidden" name="_csrf" value="{{.CSRF}}">
<p class="actionrow"><button type="submit" class="danger">{{t "Yes, delete everything"}}</button>
diff --git a/internal/web/templates/admin/index.html b/internal/web/templates/admin/index.html
index 0e49e62..055671e 100644
--- a/internal/web/templates/admin/index.html
+++ b/internal/web/templates/admin/index.html
@@ -6,11 +6,18 @@
</div>
<div class="card">
<table>
- <tr><th>{{t "User"}}</th><th>{{t "Role"}}</th><th>{{t "Blog"}}</th><th>{{t "Since"}}</th><th>{{t "Actions"}}</th></tr>
+ <tr><th>{{t "User"}}</th><th>{{t "Role"}}</th><th>{{t "Blog"}}</th><th>{{t "Upload limit"}}</th><th>{{t "Since"}}</th><th>{{t "Actions"}}</th></tr>
{{range .Data.users}}<tr{{if .Disabled}} class="disabled"{{end}}>
<td>{{.Username}}{{if .Disabled}} <span class="tag">{{t "disabled"}}</span>{{end}}</td>
<td>{{t .Role}}</td>
<td>{{if .Subdomain}}<a href="/b/{{deref .Subdomain}}/">{{if eq (deref .Subdomain) "www"}}{{$.Data.cfg.BaseDomain}}{{else}}{{deref .Subdomain}}{{end}}</a> <a href="{{$.Data.cfg.BlogURL (deref .Subdomain)}}" target="_blank" class="muted">{{t "view"}} &#8599;</a>{{else}}<span class="muted">—</span>{{end}}</td>
+ <td class="nowrap">{{if .BlogID}}{{if .MaxUploadBytes}}{{size .MaxUploadBytes}}{{else}}{{size $.Data.defaultLimit}} <span class="muted">({{t "default"}})</span>{{end}}
+ <details class="inline"><summary class="mini">{{t "change"}}</summary>
+ <form method="post" action="/admin/blogs/{{deref64 .BlogID}}/upload-limit" class="inline">
+ <input type="hidden" name="_csrf" value="{{$.CSRF}}">
+ <input type="number" name="mb" min="1" max="1024" placeholder="MB" size="5"{{if .MaxUploadBytes}} value="{{mb .MaxUploadBytes}}"{{end}}>
+ <button class="mini">{{t "Set"}}</button> <span class="muted small">{{t "blank = default"}}</span>
+ </form></details>{{else}}<span class="muted">—</span>{{end}}</td>
<td class="nowrap">{{date .CreatedAt}}</td>
<td class="nowrap">
{{if ne .ID $.User.ID}}
diff --git a/internal/web/templates/dashboard/files.html b/internal/web/templates/dashboard/files.html
new file mode 100644
index 0000000..b42cec2
--- /dev/null
+++ b/internal/web/templates/dashboard/files.html
@@ -0,0 +1,95 @@
+{{define "title"}}{{t "Files"}} · {{.Blog.Title}}{{end}}
+{{define "content"}}
+{{$b := .Blog.Subdomain}}
+<div class="pagehead">
+ <h1>{{t "Files"}}</h1>
+ <p class="lead muted">{{t "Files are stored with your blog. Put one in a post with"}} <em>{{t "Insert file"}}</em> {{t "in the editor, or copy its line from the table."}} {{tf "%d files, %s in use." .Data.count (size .Data.bytes)}}</p>
+</div>
+<div class="card dropzone" id="dropzone">
+ <form method="post" action="/b/{{$b}}/files/upload" enctype="multipart/form-data" id="upload-form">
+ <input type="hidden" name="_csrf" value="{{.CSRF}}">
+ <label>{{t "Upload files"}} <span class="muted">({{tf "any type, up to %s each" .Data.limit}})</span><br><input type="file" name="file" id="upload-input" multiple required></label>
+ <p class="actionrow"><button type="submit">{{t "Upload"}}</button> <span class="muted small" id="upload-hint" hidden>{{t "…or drop files anywhere on this box."}}</span></p>
+ <ul class="progress" id="upload-progress"></ul>
+ </form>
+</div>
+<form method="get" class="filter files-filter">
+ <span class="kinds">
+ <a href="/b/{{$b}}/files{{if .Data.q}}?q={{.Data.q}}{{end}}"{{if not .Data.kind}} class="active"{{end}}>{{t "All"}}</a>
+ {{range .Data.kinds}}<a href="/b/{{$b}}/files?kind={{.}}{{if $.Data.q}}&amp;q={{$.Data.q}}{{end}}"{{if eq . $.Data.kind}} class="active"{{end}}>{{t (index $.Data.kindNames .)}}</a>{{end}}
+ </span>
+ {{if .Data.kind}}<input type="hidden" name="kind" value="{{.Data.kind}}">{{end}}
+ <input type="search" name="q" value="{{.Data.q}}" placeholder="{{t "Search files"}}" size="18"> <button type="submit" class="small">{{t "Search"}}</button>
+</form>
+<div class="card">
+{{if .Data.files}}
+<table class="files">
+ <tr><th></th><th>{{t "Name"}}</th><th>{{t "Kind"}}</th><th>{{t "Size"}}</th><th>{{t "Date"}}</th><th>Markdown</th><th></th></tr>
+ {{range .Data.files}}<tr>
+ <td class="icon">{{if eq .Kind "image"}}<a href="/b/{{$b}}/media/{{.ID}}" target="_blank"><img class="thumb" src="/b/{{$b}}/media/{{.ID}}" alt=""></a>{{else}}<span class="badge">{{.Badge}}</span>{{end}}</td>
+ <td class="name"><a href="/b/{{$b}}/media/{{.ID}}" target="_blank">{{.Filename}}</a>
+ <details class="inline"><summary class="mini">{{t "rename"}}</summary>
+ <form method="post" action="/b/{{$b}}/files/{{.ID}}/rename" class="inline">
+ <input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="back" value="{{$.Data.self}}">
+ <input type="text" name="filename" value="{{.Filename}}" maxlength="120" required size="18">
+ <button class="mini">{{t "Save"}}</button>
+ </form></details></td>
+ <td class="nowrap">{{t (index $.Data.kindNames .Kind)}}</td>
+ <td class="nowrap">{{size .Size}}</td>
+ <td class="nowrap">{{date .CreatedAt}}</td>
+ <td><input class="copy" readonly value="{{filemd .}}" onclick="this.select()"></td>
+ <td class="nowrap"><a href="/b/{{$b}}/media/{{.ID}}?download">{{t "download"}}</a> &middot;
+ <form method="post" action="/b/{{$b}}/files/{{.ID}}/delete" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><input type="hidden" name="back" value="{{$.Data.self}}"><button class="mini danger">{{t "delete"}}</button></form></td>
+ </tr>{{end}}
+</table>
+{{else if or .Data.q .Data.kind}}<p class="muted">{{t "No files match."}}</p>
+{{else}}<p class="muted">{{t "No files yet."}}</p>{{end}}
+</div>
+{{if gt .Data.lastPage 1}}
+<div class="pager">
+ {{if gt .Data.pageNum 1}}<a href="{{.Data.prevURL}}">&larr; {{t "Newer"}}</a>{{end}}
+ <span>{{tf "Page %d of %d" .Data.pageNum .Data.lastPage}}</span>
+ {{if lt .Data.pageNum .Data.lastPage}}<a href="{{.Data.nextURL}}">{{t "Older"}} &rarr;</a>{{end}}
+</div>
+{{end}}
+<script>
+(function () {
+ var zone = document.getElementById("dropzone"), form = document.getElementById("upload-form"), inp = document.getElementById("upload-input"), list = document.getElementById("upload-progress");
+ if (!window.fetch || !window.FormData || !zone || !inp) return; // old browser: the plain form still works
+ document.getElementById("upload-hint").hidden = false;
+ var busy = false;
+ function line(text) { var li = document.createElement("li"); li.textContent = text; list.appendChild(li); return li; }
+ function upload(files) {
+ if (busy || !files.length) return;
+ busy = true;
+ var done = 0, i = 0;
+ function next() {
+ if (i >= files.length) {
+ busy = false;
+ if (done) location.href = {{.Data.self}} + ({{.Data.self}}.indexOf("?") < 0 ? "?ok=" : "&ok=") + encodeURIComponent({{t "Uploaded %d files."}}.replace("%d", done));
+ return;
+ }
+ var f = files[i++], li = line(f.name + " — " + {{t "uploading…"}});
+ var fd = new FormData();
+ fd.append("_csrf", {{.CSRF}});
+ fd.append("file", f);
+ fetch(form.action, { method: "POST", body: fd, credentials: "same-origin", headers: { Accept: "application/json" } })
+ .then(function (r) { return r.json().then(function (j) { if (!r.ok || j.error) throw new Error(j.error || {{t "upload failed"}}); return j; }); })
+ .then(function () { li.textContent = f.name + " — " + {{t "uploaded"}}; done++; })
+ .catch(function (err) { li.textContent = f.name + " — " + err.message; })
+ .then(next);
+ }
+ next();
+ }
+ form.addEventListener("submit", function (ev) { if (inp.files && inp.files.length) { ev.preventDefault(); upload(inp.files); } });
+ inp.addEventListener("change", function () { if (inp.files && inp.files.length) { var fs = Array.prototype.slice.call(inp.files); inp.value = ""; upload(fs); } });
+ zone.addEventListener("dragover", function (ev) { ev.preventDefault(); zone.className = "card dropzone over"; });
+ zone.addEventListener("dragleave", function () { zone.className = "card dropzone"; });
+ zone.addEventListener("drop", function (ev) {
+ ev.preventDefault();
+ zone.className = "card dropzone";
+ if (ev.dataTransfer && ev.dataTransfer.files) upload(ev.dataTransfer.files);
+ });
+})();
+</script>
+{{end}}
diff --git a/internal/web/templates/dashboard/images.html b/internal/web/templates/dashboard/images.html
deleted file mode 100644
index 8b9faa3..0000000
--- a/internal/web/templates/dashboard/images.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{{define "title"}}{{t "Images"}} · {{.Blog.Title}}{{end}}
-{{define "content"}}
-<div class="pagehead">
- <h1>{{t "Images"}}</h1>
- <p class="lead muted">{{t "Images are stored with your blog. Put one in a post with"}} <em>{{t "Insert image"}}</em> {{t "in the editor, or copy its line from below."}}</p>
-</div>
-<div class="card">
- <form method="post" action="/b/{{.Blog.Subdomain}}/images/upload" enctype="multipart/form-data">
- <input type="hidden" name="_csrf" value="{{.CSRF}}">
- <label>{{t "Upload an image"}} <span class="muted">({{t "PNG, JPEG, GIF, WebP or ICO"}})</span><br><input type="file" name="file" accept="image/*" required></label>
- <p class="actionrow"><button type="submit">{{t "Upload"}}</button></p>
- </form>
-</div>
-<div class="gallery">
-{{range .Data.images}}
- <div class="card thumb">
- <a href="/b/{{$.Blog.Subdomain}}/media/{{.ID}}" target="_blank"><img src="/b/{{$.Blog.Subdomain}}/media/{{.ID}}" alt="{{.Filename}}"></a>
- <div class="meta">{{.Filename}} <span class="muted">({{kb .Size}})</span></div>
- <input class="copy" readonly value="![{{.Filename}}](/media/{{.ID}})" onclick="this.select()">
- <form method="post" action="/b/{{$.Blog.Subdomain}}/images/{{.ID}}/delete" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><button class="mini danger">{{t "delete"}}</button></form>
- </div>
-{{else}}<p class="muted">{{t "No images yet."}}</p>{{end}}
-</div>
-{{end}}
diff --git a/internal/web/templates/dashboard/overview.html b/internal/web/templates/dashboard/overview.html
index e021495..0f611cd 100644
--- a/internal/web/templates/dashboard/overview.html
+++ b/internal/web/templates/dashboard/overview.html
@@ -7,7 +7,7 @@
<div class="card">
<h2>{{t "Quick actions"}}</h2>
<p><a class="btn" href="/b/{{.Blog.Subdomain}}/posts/new">{{t "Write a new post"}}</a></p>
- <p><a href="/b/{{.Blog.Subdomain}}/pages/new">{{t "Add a page"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/announcements/new">{{t "Post an announcement"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/layout">{{t "Arrange the layout"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/design">{{t "Change the look"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/images">{{t "Upload images"}}</a></p>
+ <p><a href="/b/{{.Blog.Subdomain}}/pages/new">{{t "Add a page"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/announcements/new">{{t "Post an announcement"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/layout">{{t "Arrange the layout"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/design">{{t "Change the look"}}</a> &middot; <a href="/b/{{.Blog.Subdomain}}/files">{{t "Upload files"}}</a></p>
</div>
<div class="card">
<h2>{{t "Pages"}}</h2>
diff --git a/internal/web/templates/dashboard/page_form.html b/internal/web/templates/dashboard/page_form.html
index 08c4a43..18d1119 100644
--- a/internal/web/templates/dashboard/page_form.html
+++ b/internal/web/templates/dashboard/page_form.html
@@ -8,7 +8,7 @@
<label>{{t "Address"}} <span class="muted">({{t "leave empty to make one from the title; e.g."}} <code>about</code> → {{.BlogURL}}/about)</span><br>
<input name="slug" value="{{.Data.page.Slug}}" pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?" maxlength="80"></label>
<label for="ed-intro">{{t "Intro text"}} <span class="muted">({{t "Markdown, shown above the posts; optional"}})</span></label>
- {{template "editor" (dict "name" "intro" "value" .Data.page.IntroMD "rows" 8 "tall" false "upload" (print "/b/" .Blog.Subdomain "/images/upload") "csrf" .CSRF)}}
+ {{template "editor" (dict "name" "intro" "value" .Data.page.IntroMD "rows" 8 "tall" false "upload" (print "/b/" .Blog.Subdomain "/files/upload") "csrf" .CSRF)}}
<label class="check"><input type="checkbox" name="show_in_nav"{{if .Data.page.ShowInNav}} checked{{end}}> {{t "Show in menu"}}</label>
<p class="actionrow">
<button type="submit">{{t "Save"}}</button>
diff --git a/internal/web/templates/dashboard/post_form.html b/internal/web/templates/dashboard/post_form.html
index 8069a9a..d12d15f 100644
--- a/internal/web/templates/dashboard/post_form.html
+++ b/internal/web/templates/dashboard/post_form.html
@@ -15,7 +15,7 @@
<input type="datetime-local" name="posted_at" value="{{.Data.post.CreatedAt.Format "2006-01-02T15:04"}}" placeholder="YYYY-MM-DD HH:MM"></label>
</div>
<label for="ed-body">{{t "Content"}} <span class="muted">(Markdown)</span></label>
- {{template "editor" (dict "name" "body" "value" .Data.post.BodyMD "rows" 24 "tall" true "upload" (print "/b/" .Blog.Subdomain "/images/upload") "csrf" .CSRF)}}
+ {{template "editor" (dict "name" "body" "value" .Data.post.BodyMD "rows" 24 "tall" true "upload" (print "/b/" .Blog.Subdomain "/files/upload") "csrf" .CSRF)}}
<label class="check"><input type="checkbox" name="published"{{if .Data.post.Published}} checked{{end}}> {{t "Visible on the blog"}}</label>
<p class="actionrow">
<button type="submit">{{t "Save"}}</button>
diff --git a/internal/web/templates/dashboard/section_form.html b/internal/web/templates/dashboard/section_form.html
index bc3ba34..b401d9c 100644
--- a/internal/web/templates/dashboard/section_form.html
+++ b/internal/web/templates/dashboard/section_form.html
@@ -24,7 +24,7 @@
</div>
<p class="muted small">{{t "“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">{{t "Layout"}}</a>) {{t "the announcement moves to the main content instead."}}</p>
<label for="ed-body">{{t "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)}}
+ {{template "editor" (dict "name" "body" "value" $s.BodyMD "rows" 10 "tall" false "upload" (print "/b/" .Blog.Subdomain "/files/upload") "csrf" .CSRF)}}
<label class="check"><input type="checkbox" name="enabled"{{if $s.Enabled}} checked{{end}}> {{t "Shown on the blog"}}</label>
<p class="actionrow">
<button type="submit">{{t "Save"}}</button>
diff --git a/internal/web/templates/partials/dashnav.html b/internal/web/templates/partials/dashnav.html
index 65b0ed5..8b44e55 100644
--- a/internal/web/templates/partials/dashnav.html
+++ b/internal/web/templates/partials/dashnav.html
@@ -23,7 +23,7 @@
<a href="{{$b}}announcements"{{if hasPrefix .Path (print $b "announcements")}} class="active"{{end}}>{{t "Announcements"}}</a>
<a href="{{$b}}layout"{{if hasPrefix .Path (print $b "layout")}} class="active"{{end}}>{{t "Layout"}}</a>
<a href="{{$b}}design"{{if hasPrefix .Path (print $b "design")}} class="active"{{end}}>{{t "Design"}}</a>
- <a href="{{$b}}images"{{if hasPrefix .Path (print $b "images")}} class="active"{{end}}>{{t "Images"}}</a>
+ <a href="{{$b}}files"{{if hasPrefix .Path (print $b "files")}} class="active"{{end}}>{{t "Files"}}</a>
<a href="{{$b}}settings"{{if hasPrefix .Path (print $b "settings")}} class="active"{{end}}>{{t "Settings"}}</a>
</span>
<a class="view" href="{{.BlogURL}}" target="_blank">{{t "View blog"}} &#8599;</a>
diff --git a/internal/web/templates/partials/editor.html b/internal/web/templates/partials/editor.html
index eb2e315..4371624 100644
--- a/internal/web/templates/partials/editor.html
+++ b/internal/web/templates/partials/editor.html
@@ -1,9 +1,9 @@
{{define "editor"}}<textarea name="{{.name}}" id="ed-{{.name}}" rows="{{.rows}}"{{if .tall}} class="editor"{{end}}>{{.value}}</textarea>
<div class="editor-tools">
- <label class="upload">{{t "Insert image"}} <input type="file" name="inline_image" id="ed-{{.name}}-file" accept="image/*"></label>
+ <label class="upload">{{t "Insert file"}} <input type="file" name="inline_file" id="ed-{{.name}}-file"></label>
<span class="upload-status" id="ed-{{.name}}-status"></span>
- <span class="muted small">{{t "…or paste / drop an image into the text."}}</span>
- <noscript><span class="muted small">{{t "The image is added at the end of the text when you save."}}</span></noscript>
+ <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>
</div>
{{template "mdhelp"}}
<script>
@@ -22,23 +22,27 @@
var fd = new FormData();
fd.append("_csrf", {{.csrf}});
fd.append("file", file);
- fetch({{.upload}}, { method: "POST", body: fd, credentials: "same-origin", headers: { Accept: "application/json" } })
+ return fetch({{.upload}}, { method: "POST", body: fd, credentials: "same-origin", headers: { Accept: "application/json" } })
.then(function (r) { return r.json().then(function (j) { if (!r.ok || j.error) throw new Error(j.error || {{t "upload failed"}}); return j; }); })
.then(function (j) { insert(j.markdown); st.textContent = {{t "Inserted"}} + " " + j.filename + "."; inp.value = ""; })
.catch(function (err) { st.textContent = {{t "Could not upload:"}} + " " + err.message; });
}
+ function uploadAll(files) { // one after the other, so each lands where the caret is
+ var i = 0;
+ (function next() { if (i < files.length) upload(files[i++]).then(next); })();
+ }
inp.addEventListener("change", function () { if (inp.files && inp.files[0]) upload(inp.files[0]); });
ta.addEventListener("paste", function (ev) {
var items = ev.clipboardData && ev.clipboardData.items;
if (!items) return;
for (var i = 0; i < items.length; i++) {
- if (items[i].kind === "file" && /^image\//.test(items[i].type)) { ev.preventDefault(); upload(items[i].getAsFile()); return; }
+ if (items[i].kind === "file") { ev.preventDefault(); upload(items[i].getAsFile()); return; }
}
});
ta.addEventListener("dragover", function (ev) { ev.preventDefault(); });
ta.addEventListener("drop", function (ev) {
- var f = ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files[0];
- if (f && /^image\//.test(f.type)) { ev.preventDefault(); upload(f); }
+ var fs = ev.dataTransfer && ev.dataTransfer.files;
+ if (fs && fs.length) { ev.preventDefault(); uploadAll(Array.prototype.slice.call(fs)); }
});
})();
</script>{{end}}
diff --git a/internal/web/templates/partials/mdhelp.html b/internal/web/templates/partials/mdhelp.html
index 748452e..5489510 100644
--- a/internal/web/templates/partials/mdhelp.html
+++ b/internal/web/templates/partials/mdhelp.html
@@ -2,7 +2,7 @@
<summary>{{t "Formatting cheat-sheet"}}</summary>
<table class="cheat">
<tr><td><code># {{t "Heading"}}</code>, <code>## {{t "Smaller heading"}}</code></td><td><code>**{{t "bold"}}**</code>, <code>*{{t "italic"}}*</code></td></tr>
- <tr><td><code>[{{t "link text"}}](https://example.org)</code></td><td><code>![{{t "description"}}](/media/…)</code> — <em>{{t "Insert image"}}</em> {{t "writes this for you"}}</td></tr>
+ <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>