Add Table of Contents
also refactor CSS
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{{if .Photos}}
|
||||
<div class="diary-photo-grid">
|
||||
<div class="photo-grid">
|
||||
{{range .Photos}}
|
||||
<a href="{{.URL}}" target="_blank"><img src="{{.URL}}" alt="{{.Name}}" loading="lazy"></a>
|
||||
{{end}}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
{{range .Days}}
|
||||
<div class="diary-section">
|
||||
<h2>
|
||||
{{if .URL}}<a href="{{.URL}}">{{.Heading}}</a>{{else}}{{.Heading}}{{end}}
|
||||
{{if .EditURL}}<a href="{{.EditURL}}" class="btn btn-small">edit</a>{{end}}
|
||||
</h2>
|
||||
{{if .Content}}<div class="content">{{.Content}}</div>{{end}}
|
||||
{{if .Photos}}
|
||||
<div class="diary-photo-grid">
|
||||
{{range .Photos}}
|
||||
<a href="{{.URL}}" target="_blank"><img src="{{.URL}}" alt="{{.Name}}" loading="lazy"></a>
|
||||
{{end}}
|
||||
</div>
|
||||
<h2 id="{{.ID}}">
|
||||
{{if .URL}}<a href="{{.URL}}">{{.Heading}}</a>{{else}}{{.Heading}}{{end}}
|
||||
{{if .EditURL}}<a href="{{.EditURL}}" class="btn btn-small">edit</a>{{end}}
|
||||
</h2>
|
||||
{{if .Content}}{{.Content}}{{end}}
|
||||
{{if .Photos}}
|
||||
<div class="photo-grid">
|
||||
{{range .Photos}}
|
||||
<a href="{{.URL}}" target="_blank"><img src="{{.URL}}" alt="{{.Name}}" loading="lazy"></a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<h2 id="months">Months</h2>
|
||||
{{range .Months}}
|
||||
<div class="diary-section">
|
||||
<h2 class="diary-heading">
|
||||
<a href="{{.URL}}">{{.Name}}</a>
|
||||
{{if .PhotoCount}}<span class="diary-photo-count">({{.PhotoCount}} photos)</span>{{end}}
|
||||
</h2>
|
||||
</div>
|
||||
<h3 id="{{.ID}}">
|
||||
<a href="{{.URL}}">{{.Name}}</a>
|
||||
{{if .PhotoCount}}<span class="muted">({{.PhotoCount}} photos)</span>{{end}}
|
||||
</h3>
|
||||
{{end}}
|
||||
|
||||
+2
-1
@@ -61,10 +61,11 @@
|
||||
<div class="content">{{.Content}}</div>
|
||||
{{end}}
|
||||
{{if .SpecialContent}}
|
||||
<div class="diary">{{.SpecialContent}}</div>
|
||||
<div class="content">{{.SpecialContent}}</div>
|
||||
{{end}}
|
||||
{{if or .Content .SpecialContent}}
|
||||
<script src="/_/content.js"></script>
|
||||
<script src="/_/toc.js"></script>
|
||||
{{end}}
|
||||
{{if .Content}}
|
||||
<script src="/_/sections.js"></script>
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
headings.forEach(function (h, i) {
|
||||
var a = document.createElement('a');
|
||||
a.href = '?edit§ion=' + (i + 1);
|
||||
a.className = 'btn btn-small section-edit';
|
||||
a.className = 'btn btn-small';
|
||||
a.textContent = 'edit';
|
||||
h.appendChild(a);
|
||||
});
|
||||
|
||||
+102
-33
@@ -325,59 +325,32 @@ textarea {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* === Diary views === */
|
||||
.diary-section {
|
||||
margin: 2rem 0;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px dashed var(--secondary);
|
||||
}
|
||||
|
||||
.diary-section:first-child {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.diary-section h2 {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.diary-photo-count {
|
||||
/* === Muted text === */
|
||||
.muted {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.diary-photo-grid {
|
||||
/* === Photo grid === */
|
||||
.photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.diary-photo-grid a {
|
||||
.photo-grid a {
|
||||
display: block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.diary-photo-grid img {
|
||||
.photo-grid img {
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.diary-section .content {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* === Section edit links === */
|
||||
.section-edit {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
/* === Empty state === */
|
||||
.empty {
|
||||
padding: 1rem;
|
||||
@@ -405,7 +378,99 @@ hr {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
/* === Table of contents === */
|
||||
.toc {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 14rem;
|
||||
max-height: calc(100vh - 2rem);
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--secondary);
|
||||
background: var(--bg);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.toc-header {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 1px dashed var(--secondary);
|
||||
padding-bottom: 0.25rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.toc ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toc li {
|
||||
margin: 0.15rem 0;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
color: var(--link);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.toc a:hover {
|
||||
color: var(--link-hover);
|
||||
}
|
||||
|
||||
.toc-h2 {
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
.toc-h3 {
|
||||
padding-left: 1.6rem;
|
||||
}
|
||||
|
||||
.toc-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* === Responsive === */
|
||||
@media (max-width: 1100px) {
|
||||
.toc-toggle {
|
||||
display: block;
|
||||
background: none;
|
||||
border: 1px solid var(--secondary);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.75rem;
|
||||
margin: 1rem auto;
|
||||
width: calc(100% - 2rem);
|
||||
max-width: 860px;
|
||||
}
|
||||
.toc-toggle::before {
|
||||
content: "▸ ";
|
||||
color: var(--secondary);
|
||||
}
|
||||
.toc-toggle[aria-expanded="true"]::before {
|
||||
content: "▾ ";
|
||||
}
|
||||
.toc {
|
||||
position: static;
|
||||
display: none;
|
||||
width: calc(100% - 2rem);
|
||||
max-width: 860px;
|
||||
margin: 0 auto 1rem;
|
||||
max-height: none;
|
||||
}
|
||||
.toc.is-open {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header {
|
||||
padding: 0.5rem 0.75rem;
|
||||
@@ -416,4 +481,8 @@ hr {
|
||||
textarea {
|
||||
min-height: 50vh;
|
||||
}
|
||||
.toc-toggle,
|
||||
.toc.is-open {
|
||||
width: calc(100% - 1.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
(function () {
|
||||
var content = document.querySelector("main");
|
||||
if (!content) return;
|
||||
|
||||
var headings = content.querySelectorAll("h1, h2, h3");
|
||||
if (headings.length < 2) return;
|
||||
|
||||
var nav = document.createElement("nav");
|
||||
nav.className = "toc";
|
||||
|
||||
var header = document.createElement("div");
|
||||
header.className = "toc-header";
|
||||
header.textContent = "Contents";
|
||||
nav.appendChild(header);
|
||||
|
||||
var list = document.createElement("ul");
|
||||
headings.forEach(function (h) {
|
||||
if (!h.id) return;
|
||||
var li = document.createElement("li");
|
||||
li.className = "toc-" + h.tagName.toLowerCase();
|
||||
var a = document.createElement("a");
|
||||
a.href = "#" + h.id;
|
||||
a.textContent = h.textContent;
|
||||
li.appendChild(a);
|
||||
list.appendChild(li);
|
||||
});
|
||||
nav.appendChild(list);
|
||||
|
||||
var toggle = document.createElement("button");
|
||||
toggle.type = "button";
|
||||
toggle.className = "toc-toggle";
|
||||
toggle.textContent = "Contents";
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
toggle.addEventListener("click", function () {
|
||||
var open = nav.classList.toggle("is-open");
|
||||
toggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
});
|
||||
|
||||
var main = document.querySelector("main");
|
||||
if (main) {
|
||||
main.parentNode.insertBefore(toggle, main);
|
||||
main.parentNode.insertBefore(nav, main);
|
||||
} else {
|
||||
document.body.appendChild(toggle);
|
||||
document.body.appendChild(nav);
|
||||
}
|
||||
|
||||
var pageHeader = document.querySelector("header");
|
||||
function updateTop() {
|
||||
if (!pageHeader || getComputedStyle(nav).position !== "fixed") return;
|
||||
var rect = pageHeader.getBoundingClientRect();
|
||||
nav.style.top = Math.max(8, rect.bottom + 8) + "px";
|
||||
}
|
||||
window.addEventListener("scroll", updateTop, { passive: true });
|
||||
window.addEventListener("resize", updateTop);
|
||||
updateTop();
|
||||
})();
|
||||
@@ -69,12 +69,14 @@ type diaryPhoto struct {
|
||||
}
|
||||
|
||||
type diaryMonthSummary struct {
|
||||
ID string
|
||||
Name string
|
||||
URL string
|
||||
PhotoCount int
|
||||
}
|
||||
|
||||
type diaryDaySection struct {
|
||||
ID string
|
||||
Heading string
|
||||
URL string
|
||||
EditURL string
|
||||
@@ -82,7 +84,10 @@ type diaryDaySection struct {
|
||||
Photos []diaryPhoto
|
||||
}
|
||||
|
||||
type diaryYearData struct{ Months []diaryMonthSummary }
|
||||
type diaryYearData struct {
|
||||
Months []diaryMonthSummary
|
||||
Year int
|
||||
}
|
||||
type diaryMonthData struct{ Days []diaryDaySection }
|
||||
type diaryDayData struct{ Photos []diaryPhoto }
|
||||
|
||||
@@ -191,6 +196,7 @@ func renderDiaryYear(fsPath, urlPath string) template.HTML {
|
||||
}
|
||||
monthDate := time.Date(year, time.Month(monthNum), 1, 0, 0, 0, 0, time.UTC)
|
||||
months = append(months, diaryMonthSummary{
|
||||
ID: monthDate.Format("2006-01"),
|
||||
Name: monthDate.Format("January 2006"),
|
||||
URL: path.Join(urlPath, e.Name()) + "/",
|
||||
PhotoCount: count,
|
||||
@@ -198,7 +204,7 @@ func renderDiaryYear(fsPath, urlPath string) template.HTML {
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := diaryYearTmpl.Execute(&buf, diaryYearData{Months: months}); err != nil {
|
||||
if err := diaryYearTmpl.Execute(&buf, diaryYearData{Months: months, Year: year}); err != nil {
|
||||
log.Printf("diary year template: %v", err)
|
||||
return ""
|
||||
}
|
||||
@@ -280,6 +286,7 @@ func renderDiaryMonth(fsPath, urlPath string) template.HTML {
|
||||
}
|
||||
|
||||
sections = append(sections, diaryDaySection{
|
||||
ID: date.Format("2006-01-02"),
|
||||
Heading: heading,
|
||||
URL: dayURL,
|
||||
EditURL: dayURL + "?edit",
|
||||
|
||||
Reference in New Issue
Block a user