diff --git a/src/templates/src/layouts/base.html b/src/templates/src/layouts/base.html
index 2a6d224e..5bf931e5 100644
--- a/src/templates/src/layouts/base.html
+++ b/src/templates/src/layouts/base.html
@@ -88,7 +88,7 @@
{{- if gt $i 0 -}}
»
{{- end -}}
- {{ .Name }}
+ {{ .Name }}
{{- end }}
{{ end }}
diff --git a/src/utils/utils.go b/src/utils/utils.go
index 7920a40f..f739e0c4 100644
--- a/src/utils/utils.go
+++ b/src/utils/utils.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
+ "math"
"time"
"git.handmade.network/hmn/hmn/src/oops"
@@ -34,6 +35,10 @@ func Int64Max(a, b int64) int64 {
return b
}
+func NumPages(numThings, thingsPerPage int) int {
+ return IntMax(int(math.Ceil(float64(numThings)/float64(thingsPerPage))), 1)
+}
+
/*
Recover a panic and convert it to a returned error. Call it like so:
diff --git a/src/website/auth.go b/src/website/auth.go
index e21ba862..7f86f1eb 100644
--- a/src/website/auth.go
+++ b/src/website/auth.go
@@ -33,7 +33,7 @@ func LoginPage(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate("auth_login.html", LoginPageData{
- BaseData: getBaseData(c),
+ BaseData: getBaseDataAutocrumb(c, "Log in"),
RedirectUrl: c.Req.URL.Query().Get("redirect"),
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
}, c.Perf)
@@ -63,7 +63,7 @@ func Login(c *RequestContext) ResponseData {
showLoginWithFailure := func(c *RequestContext, redirect string) ResponseData {
var res ResponseData
- baseData := getBaseData(c)
+ baseData := getBaseDataAutocrumb(c, "Log in")
baseData.AddImmediateNotice("failure", "Incorrect username or password")
res.MustWriteTemplate("auth_login.html", LoginPageData{
BaseData: baseData,
@@ -122,7 +122,7 @@ func RegisterNewUser(c *RequestContext) ResponseData {
}
// TODO(asaf): Do something to prevent bot registration
var res ResponseData
- res.MustWriteTemplate("auth_register.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("auth_register.html", getBaseDataAutocrumb(c, "Register"), c.Perf)
return res
}
@@ -275,7 +275,7 @@ func RegisterNewUserSuccess(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate("auth_register_success.html", RegisterNewUserSuccessData{
- BaseData: getBaseData(c),
+ BaseData: getBaseDataAutocrumb(c, "Register"),
ContactUsUrl: hmnurl.BuildContactPage(),
}, c.Perf)
return res
@@ -321,7 +321,7 @@ func EmailConfirmation(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
- BaseData: getBaseData(c),
+ BaseData: getBaseDataAutocrumb(c, "Register"),
Token: token,
Username: username,
}, c.Perf)
@@ -346,11 +346,11 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, err)
} else if !success {
var res ResponseData
- baseData := getBaseData(c)
+ baseData := getBaseDataAutocrumb(c, "Register")
// NOTE(asaf): We can report that the password is incorrect, because an attacker wouldn't have a valid token to begin with.
baseData.AddImmediateNotice("failure", "Incorrect password. Please try again.")
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
- BaseData: getBaseData(c),
+ BaseData: baseData,
Token: token,
Username: username,
}, c.Perf)
@@ -424,7 +424,7 @@ func RequestPasswordReset(c *RequestContext) ResponseData {
return c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
}
var res ResponseData
- res.MustWriteTemplate("auth_password_reset.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("auth_password_reset.html", getBaseDataAutocrumb(c, "Password Reset"), c.Perf)
return res
}
@@ -548,7 +548,7 @@ func PasswordResetSent(c *RequestContext) ResponseData {
}
var res ResponseData
res.MustWriteTemplate("auth_password_reset_sent.html", PasswordResetSentData{
- BaseData: getBaseData(c),
+ BaseData: getBaseDataAutocrumb(c, "Password Reset"),
ContactUsUrl: hmnurl.BuildContactPage(),
}, c.Perf)
return res
@@ -582,7 +582,7 @@ func DoPasswordReset(c *RequestContext) ResponseData {
}
res.MustWriteTemplate("auth_do_password_reset.html", DoPasswordResetData{
- BaseData: getBaseData(c),
+ BaseData: getBaseDataAutocrumb(c, "Password Reset"),
Username: username,
Token: token,
}, c.Perf)
diff --git a/src/website/blogs.go b/src/website/blogs.go
index 6555d8ba..17f449c8 100644
--- a/src/website/blogs.go
+++ b/src/website/blogs.go
@@ -53,7 +53,7 @@ func BlogIndex(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch total number of blog posts"))
}
- numPages := NumPages(numPosts, postsPerPage)
+ numPages := utils.NumPages(numPosts, postsPerPage)
page, ok := ParsePageNumber(c, "page", numPages)
if !ok {
c.Redirect(hmnurl.BuildBlog(c.CurrentProject.Slug, page), http.StatusSeeOther)
@@ -104,8 +104,7 @@ func BlogIndex(c *RequestContext) ResponseData {
})
}
- baseData := getBaseData(c)
- baseData.Title = fmt.Sprintf("%s Blog", c.CurrentProject.Name)
+ baseData := getBaseData(c, fmt.Sprintf("%s Blog", c.CurrentProject.Name), []templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)})
canCreate := false
if c.CurrentUser != nil {
@@ -181,8 +180,7 @@ func BlogThread(c *RequestContext) ResponseData {
templatePosts = append(templatePosts, post)
}
- baseData := getBaseData(c)
- baseData.Title = thread.Title
+ baseData := getBaseData(c, thread.Title, []templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)})
var res ResponseData
res.MustWriteTemplate("blog_post.html", blogPostData{
@@ -209,9 +207,11 @@ func BlogPostRedirectToThread(c *RequestContext) ResponseData {
}
func BlogNewThread(c *RequestContext) ResponseData {
- baseData := getBaseData(c)
- baseData.Title = fmt.Sprintf("Create New Post | %s", c.CurrentProject.Name)
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Create New Post | %s", c.CurrentProject.Name),
+ []templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)},
+ )
editData := getEditorDataForNew(baseData, nil)
editData.SubmitUrl = hmnurl.BuildBlogNewThread(c.CurrentProject.Slug)
@@ -284,13 +284,17 @@ func BlogPostEdit(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
+ title := ""
if postData.Thread.FirstID == postData.Post.ID {
- baseData.Title = fmt.Sprintf("Editing \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
+ title = fmt.Sprintf("Editing \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
} else {
- baseData.Title = fmt.Sprintf("Editing Post | %s", c.CurrentProject.Name)
+ title = fmt.Sprintf("Editing Post | %s", c.CurrentProject.Name)
}
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(
+ c,
+ title,
+ BlogThreadBreadcrumbs(c.CurrentProject.Slug, &postData.Thread),
+ )
editData := getEditorDataForEdit(baseData, postData)
editData.SubmitUrl = hmnurl.BuildBlogPostEdit(c.CurrentProject.Slug, cd.ThreadID, cd.PostID)
@@ -352,9 +356,11 @@ func BlogPostReply(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
- baseData.Title = fmt.Sprintf("Replying to comment in \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Replying to comment in \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name),
+ BlogThreadBreadcrumbs(c.CurrentProject.Slug, &postData.Thread),
+ )
replyPost := templates.PostToTemplate(&postData.Post, postData.Author, c.Theme)
replyPost.AddContentVersion(postData.CurrentVersion, postData.Editor)
@@ -412,12 +418,17 @@ func BlogPostDelete(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
+ title := ""
if postData.Thread.FirstID == postData.Post.ID {
- baseData.Title = fmt.Sprintf("Deleting \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
+ title = fmt.Sprintf("Deleting \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
} else {
- baseData.Title = fmt.Sprintf("Deleting comment in \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
+ title = fmt.Sprintf("Deleting comment in \"%s\" | %s", postData.Thread.Title, c.CurrentProject.Name)
}
+ baseData := getBaseData(
+ c,
+ title,
+ BlogThreadBreadcrumbs(c.CurrentProject.Slug, &postData.Thread),
+ )
// TODO(ben): Set breadcrumbs
templatePost := templates.PostToTemplate(&postData.Post, postData.Author, c.Theme)
diff --git a/src/website/breadcrumb_helper.go b/src/website/breadcrumb_helper.go
new file mode 100644
index 00000000..0f69794a
--- /dev/null
+++ b/src/website/breadcrumb_helper.go
@@ -0,0 +1,63 @@
+package website
+
+import (
+ "git.handmade.network/hmn/hmn/src/hmnurl"
+ "git.handmade.network/hmn/hmn/src/models"
+ "git.handmade.network/hmn/hmn/src/templates"
+)
+
+func ProjectBreadcrumb(project *models.Project) templates.Breadcrumb {
+ return templates.Breadcrumb{
+ Name: project.Name,
+ Url: hmnurl.BuildProjectHomepage(project.Slug),
+ }
+}
+
+func ForumBreadcrumb(projectSlug string) templates.Breadcrumb {
+ return templates.Breadcrumb{
+ Name: "Forums",
+ Url: hmnurl.BuildForum(projectSlug, nil, 1),
+ }
+}
+
+func SubforumBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, subforumID int) []templates.Breadcrumb {
+ var result []templates.Breadcrumb
+ result = []templates.Breadcrumb{
+ ProjectBreadcrumb(project),
+ ForumBreadcrumb(project.Slug),
+ }
+ subforums := lineageBuilder.GetSubforumLineage(subforumID)
+ slugs := lineageBuilder.GetSubforumLineageSlugs(subforumID)
+ for i, subforum := range subforums {
+ result = append(result, templates.Breadcrumb{
+ Name: subforum.Name,
+ Url: hmnurl.BuildForum(project.Slug, slugs[0:i+1], 1),
+ })
+ }
+
+ return result
+}
+
+func ForumThreadBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, thread *models.Thread) []templates.Breadcrumb {
+ result := SubforumBreadcrumbs(lineageBuilder, project, *thread.SubforumID)
+ result = append(result, templates.Breadcrumb{
+ Name: thread.Title,
+ Url: hmnurl.BuildForumThread(project.Slug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1),
+ })
+ return result
+}
+
+func BlogBreadcrumb(projectSlug string) templates.Breadcrumb {
+ return templates.Breadcrumb{
+ Name: "Blog",
+ Url: hmnurl.BuildBlog(projectSlug, 1),
+ }
+}
+
+func BlogThreadBreadcrumbs(projectSlug string, thread *models.Thread) []templates.Breadcrumb {
+ result := []templates.Breadcrumb{
+ BlogBreadcrumb(projectSlug),
+ {Name: thread.Title, Url: hmnurl.BuildBlogThread(projectSlug, thread.ID, thread.Title)},
+ }
+ return result
+}
diff --git a/src/website/episode_guide.go b/src/website/episode_guide.go
index 4585b6b4..6be4069f 100644
--- a/src/website/episode_guide.go
+++ b/src/website/episode_guide.go
@@ -88,7 +88,7 @@ func EpisodeList(c *RequestContext) ResponseData {
}
var res ResponseData
- baseData := getBaseData(c)
+ baseData := getBaseDataAutocrumb(c, fmt.Sprintf("Episode Guide"))
res.MustWriteTemplate("episode_list.html", EpisodeListData{
BaseData: baseData,
Content: template.HTML(guide),
@@ -147,8 +147,11 @@ func Episode(c *RequestContext) ResponseData {
content := contentMatches[episodeContentRegex.SubexpIndex("content")]
var res ResponseData
- baseData := getBaseData(c)
- baseData.Title = title
+ baseData := getBaseData(
+ c,
+ title,
+ []templates.Breadcrumb{{Name: "Episode Guide", Url: hmnurl.BuildEpisodeList(c.CurrentProject.Slug, foundTopic)}},
+ )
res.MustWriteTemplate("episode.html", EpisodeData{
BaseData: baseData,
Content: template.HTML(content),
diff --git a/src/website/feed.go b/src/website/feed.go
index 6ec3ae05..db8831cd 100644
--- a/src/website/feed.go
+++ b/src/website/feed.go
@@ -90,7 +90,7 @@ func Feed(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed posts"))
}
- baseData := getBaseData(c)
+ baseData := getBaseDataAutocrumb(c, "Feed")
baseData.BodyClasses = append(baseData.BodyClasses, "feed")
var res ResponseData
diff --git a/src/website/forums.go b/src/website/forums.go
index aecf82af..d2dc4ce6 100644
--- a/src/website/forums.go
+++ b/src/website/forums.go
@@ -100,7 +100,7 @@ func Forum(c *RequestContext) ResponseData {
}
c.Perf.EndBlock()
- numPages := NumPages(numThreads, threadsPerPage)
+ numPages := utils.NumPages(numThreads, threadsPerPage)
page, ok := ParsePageNumber(c, "page", numPages)
if !ok {
c.Redirect(hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, page), http.StatusSeeOther)
@@ -259,27 +259,11 @@ func Forum(c *RequestContext) ResponseData {
// Template assembly
// ---------------------
- baseData := getBaseData(c)
- baseData.Title = c.CurrentProject.Name + " Forums"
- baseData.Breadcrumbs = []templates.Breadcrumb{
- {
- Name: c.CurrentProject.Name,
- Url: hmnurl.BuildProjectHomepage(c.CurrentProject.Slug),
- },
- {
- Name: "Forums",
- Url: hmnurl.BuildForum(c.CurrentProject.Slug, nil, 1),
- Current: true,
- },
- }
-
- currentSubforums := cd.LineageBuilder.GetSubforumLineage(cd.SubforumID)
- for i, subforum := range currentSubforums {
- baseData.Breadcrumbs = append(baseData.Breadcrumbs, templates.Breadcrumb{
- Name: subforum.Name,
- Url: hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs[0:i+1], 1),
- })
- }
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("%s Forums", c.CurrentProject.Name),
+ SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID),
+ )
var res ResponseData
res.MustWriteTemplate("forum.html", forumData{
@@ -511,9 +495,7 @@ func ForumThread(c *RequestContext) ResponseData {
}
}
- baseData := getBaseData(c)
- baseData.Title = thread.Title
- // TODO(asaf): Set breadcrumbs
+ baseData := getBaseData(c, thread.Title, SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID))
var res ResponseData
res.MustWriteTemplate("forum_thread.html", forumThreadData{
@@ -596,15 +578,12 @@ func ForumPostRedirect(c *RequestContext) ResponseData {
}
func ForumNewThread(c *RequestContext) ResponseData {
- baseData := getBaseData(c)
- baseData.Title = "Create New Thread"
- // TODO(ben): Set breadcrumbs
-
cd, ok := getCommonForumData(c)
if !ok {
return FourOhFour(c)
}
+ baseData := getBaseData(c, "Create New Thread", SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID))
editData := getEditorDataForNew(baseData, nil)
editData.SubmitUrl = hmnurl.BuildForumNewThread(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), true)
editData.SubmitLabel = "Post New Thread"
@@ -683,9 +662,11 @@ func ForumPostReply(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
- baseData.Title = fmt.Sprintf("Replying to post | %s", cd.SubforumTree[cd.SubforumID].Name)
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Replying to post | %s", cd.SubforumTree[cd.SubforumID].Name),
+ ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &postData.Thread),
+ )
replyPost := templates.PostToTemplate(&postData.Post, postData.Author, c.Theme)
replyPost.AddContentVersion(postData.CurrentVersion, postData.Editor)
@@ -743,13 +724,13 @@ func ForumPostEdit(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
+ title := ""
if postData.Thread.FirstID == postData.Post.ID {
- baseData.Title = fmt.Sprintf("Editing \"%s\" | %s", postData.Thread.Title, cd.SubforumTree[cd.SubforumID].Name)
+ title = fmt.Sprintf("Editing \"%s\" | %s", postData.Thread.Title, cd.SubforumTree[cd.SubforumID].Name)
} else {
- baseData.Title = fmt.Sprintf("Editing Post | %s", cd.SubforumTree[cd.SubforumID].Name)
+ title = fmt.Sprintf("Editing Post | %s", cd.SubforumTree[cd.SubforumID].Name)
}
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(c, title, ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &postData.Thread))
editData := getEditorDataForEdit(baseData, postData)
editData.SubmitUrl = hmnurl.BuildForumPostEdit(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID)
@@ -806,9 +787,11 @@ func ForumPostDelete(c *RequestContext) ResponseData {
postData := FetchPostAndStuff(c.Context(), c.Conn, cd.ThreadID, cd.PostID)
- baseData := getBaseData(c)
- baseData.Title = fmt.Sprintf("Deleting post in \"%s\" | %s", postData.Thread.Title, cd.SubforumTree[cd.SubforumID].Name)
- // TODO(ben): Set breadcrumbs
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Deleting post in \"%s\" | %s", postData.Thread.Title, cd.SubforumTree[cd.SubforumID].Name),
+ ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &postData.Thread),
+ )
templatePost := templates.PostToTemplate(&postData.Post, postData.Author, c.Theme)
templatePost.AddContentVersion(postData.CurrentVersion, postData.Editor)
diff --git a/src/website/jam.go b/src/website/jam.go
index a8998e9f..6a715c55 100644
--- a/src/website/jam.go
+++ b/src/website/jam.go
@@ -13,8 +13,7 @@ func JamIndex(c *RequestContext) ResponseData {
ogimageurl = urljoin(current_site_host(), ogimagepath)
*/
- baseData := getBaseData(c)
- baseData.Title = "Wheel Reinvention Jam"
+ baseData := getBaseDataAutocrumb(c, "Wheel Reinvention Jam")
baseData.OpenGraphItems = []templates.OpenGraphItem{
{Property: "og:site_name", Value: "Handmade.Network"},
{Property: "og:type", Value: "website"},
diff --git a/src/website/landing.go b/src/website/landing.go
index 0c02aae4..ff82df32 100644
--- a/src/website/landing.go
+++ b/src/website/landing.go
@@ -316,7 +316,7 @@ func Index(c *RequestContext) ResponseData {
showcaseJson := templates.TimelineItemsToJSON(showcaseItems)
c.Perf.EndBlock()
- baseData := getBaseData(c)
+ baseData := getBaseData(c, "", nil)
baseData.BodyClasses = append(baseData.BodyClasses, "hmdev", "landing") // TODO: Is "hmdev" necessary any more?
var res ResponseData
diff --git a/src/website/podcast.go b/src/website/podcast.go
index af280622..dc2c7fb2 100644
--- a/src/website/podcast.go
+++ b/src/website/podcast.go
@@ -44,8 +44,7 @@ func PodcastIndex(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
- baseData := getBaseData(c)
- baseData.Title = podcastResult.Podcast.Title
+ baseData := getBaseDataAutocrumb(c, podcastResult.Podcast.Title)
podcastIndexData := PodcastIndexData{
BaseData: baseData,
@@ -89,8 +88,11 @@ func PodcastEdit(c *RequestContext) ResponseData {
}
podcast := templates.PodcastToTemplate(c.CurrentProject.Slug, podcastResult.Podcast, podcastResult.ImageFile)
- baseData := getBaseData(c)
- baseData.Breadcrumbs = []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}}
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Edit %s", podcast.Title),
+ []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
+ )
podcastEditData := PodcastEditData{
BaseData: baseData,
Podcast: podcast,
@@ -229,9 +231,11 @@ func PodcastEpisode(c *RequestContext) ResponseData {
podcast := templates.PodcastToTemplate(c.CurrentProject.Slug, podcastResult.Podcast, podcastResult.ImageFile)
episode := templates.PodcastEpisodeToTemplate(c.CurrentProject.Slug, podcastResult.Episodes[0], 0, podcastResult.ImageFile)
- baseData := getBaseData(c)
- baseData.Title = podcastResult.Podcast.Title
- baseData.Breadcrumbs = []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}}
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("%s | %s", episode.Title, podcast.Title),
+ []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
+ )
podcastEpisodeData := PodcastEpisodeData{
BaseData: baseData,
@@ -280,8 +284,11 @@ func PodcastEpisodeNew(c *RequestContext) ResponseData {
podcast := templates.PodcastToTemplate(c.CurrentProject.Slug, podcastResult.Podcast, "")
var res ResponseData
- baseData := getBaseData(c)
- baseData.Breadcrumbs = []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}}
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("New episode | %s", podcast.Title),
+ []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
+ )
err = res.WriteTemplate("podcast_episode_edit.html", PodcastEpisodeEditData{
BaseData: baseData,
IsEdit: false,
@@ -321,8 +328,11 @@ func PodcastEpisodeEdit(c *RequestContext) ResponseData {
podcast := templates.PodcastToTemplate(c.CurrentProject.Slug, podcastResult.Podcast, "")
podcastEpisode := templates.PodcastEpisodeToTemplate(c.CurrentProject.Slug, episode, 0, "")
- baseData := getBaseData(c)
- baseData.Breadcrumbs = []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}, {Name: podcastEpisode.Title, Url: podcastEpisode.Url}}
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Edit episode %s | %s", podcastEpisode.Title, podcast.Title),
+ []templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}, {Name: podcastEpisode.Title, Url: podcastEpisode.Url}},
+ )
podcastEpisodeEditData := PodcastEpisodeEditData{
BaseData: baseData,
IsEdit: true,
diff --git a/src/website/post_helper.go b/src/website/post_helper.go
index ce368d26..767c9f82 100644
--- a/src/website/post_helper.go
+++ b/src/website/post_helper.go
@@ -31,30 +31,40 @@ var PostTypePrefix = map[templates.PostType]string{
templates.PostTypeForumReply: "Forum reply",
}
-func PostBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, thread *models.Thread) []templates.Breadcrumb {
+var ThreadTypeDisplayNames = map[models.ThreadType]string{
+ models.ThreadTypeProjectBlogPost: "Blog",
+ models.ThreadTypeForumPost: "Forums",
+}
+
+func GenericThreadBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, thread *models.Thread) []templates.Breadcrumb {
var result []templates.Breadcrumb
- result = append(result, templates.Breadcrumb{
- Name: project.Name,
- Url: hmnurl.BuildProjectHomepage(project.Slug),
- })
- result = append(result, templates.Breadcrumb{
- Name: ThreadTypeDisplayNames[thread.Type],
- Url: BuildProjectRootResourceUrl(project.Slug, thread.Type),
- })
- switch thread.Type {
- case models.ThreadTypeForumPost:
- subforums := lineageBuilder.GetSubforumLineage(*thread.SubforumID)
- slugs := lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID)
- for i, subforum := range subforums {
- result = append(result, templates.Breadcrumb{
- Name: subforum.Name,
- Url: hmnurl.BuildForum(project.Slug, slugs[0:i+1], 1),
- })
+ if thread.Type == models.ThreadTypeForumPost {
+ result = SubforumBreadcrumbs(lineageBuilder, project, *thread.SubforumID)
+ } else {
+ result = []templates.Breadcrumb{
+ {
+ Name: project.Name,
+ Url: hmnurl.BuildProjectHomepage(project.Slug),
+ },
+ {
+ Name: ThreadTypeDisplayNames[thread.Type],
+ Url: BuildProjectRootResourceUrl(project.Slug, thread.Type),
+ },
}
}
return result
}
+func BuildProjectRootResourceUrl(projectSlug string, kind models.ThreadType) string {
+ switch kind {
+ case models.ThreadTypeProjectBlogPost:
+ return hmnurl.BuildBlog(projectSlug, 1)
+ case models.ThreadTypeForumPost:
+ return hmnurl.BuildForum(projectSlug, nil, 1)
+ }
+ return hmnurl.BuildProjectHomepage(projectSlug)
+}
+
func MakePostListItem(
lineageBuilder *models.SubforumLineageBuilder,
project *models.Project,
@@ -87,7 +97,7 @@ func MakePostListItem(
result.PostTypePrefix = PostTypePrefix[result.PostType]
if includeBreadcrumbs {
- result.Breadcrumbs = PostBreadcrumbs(lineageBuilder, project, thread)
+ result.Breadcrumbs = GenericThreadBreadcrumbs(lineageBuilder, project, thread)
}
return result
diff --git a/src/website/projects.go b/src/website/projects.go
index 60157559..f1facd86 100644
--- a/src/website/projects.go
+++ b/src/website/projects.go
@@ -181,8 +181,7 @@ func ProjectIndex(c *RequestContext) ResponseData {
}
c.Perf.EndBlock()
- baseData := getBaseData(c)
- baseData.Title = "Project List"
+ baseData := getBaseDataAutocrumb(c, "Project List")
var res ResponseData
res.MustWriteTemplate("project_index.html", ProjectTemplateData{
BaseData: baseData,
@@ -369,7 +368,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
var projectHomepageData ProjectHomepageData
- projectHomepageData.BaseData = getBaseData(c)
+ projectHomepageData.BaseData = getBaseData(c, project.Name, nil)
if canEdit {
projectHomepageData.BaseData.Header.EditUrl = hmnurl.BuildProjectEdit(project.Slug, "")
}
diff --git a/src/website/requesthandling.go b/src/website/requesthandling.go
index daeebd8d..ec71dff5 100644
--- a/src/website/requesthandling.go
+++ b/src/website/requesthandling.go
@@ -280,7 +280,7 @@ func (c *RequestContext) ErrorResponse(status int, errs ...error) ResponseData {
StatusCode: status,
Errors: errs,
}
- res.MustWriteTemplate("error.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("error.html", getBaseData(c, "", nil), c.Perf)
return res
}
diff --git a/src/website/routes.go b/src/website/routes.go
index dfaa10e8..3d48dc9d 100644
--- a/src/website/routes.go
+++ b/src/website/routes.go
@@ -252,7 +252,13 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
return router
}
-func getBaseData(c *RequestContext) templates.BaseData {
+func getBaseDataAutocrumb(c *RequestContext, title string) templates.BaseData {
+ return getBaseData(c, title, []templates.Breadcrumb{{Name: title, Url: ""}})
+}
+
+// NOTE(asaf): If you set breadcrumbs, the breadcrumb for the current project will automatically be prepended when necessary.
+// If you pass nil, no breadcrumbs will be created.
+func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadcrumb) templates.BaseData {
var templateUser *templates.User
var templateSession *templates.Session
if c.CurrentUser != nil {
@@ -264,6 +270,17 @@ func getBaseData(c *RequestContext) templates.BaseData {
notices := getNoticesFromCookie(c)
+ if len(breadcrumbs) > 0 {
+ projectUrl := hmnurl.BuildProjectHomepage(c.CurrentProject.Slug)
+ if breadcrumbs[0].Url != projectUrl {
+ rootBreadcrumb := templates.Breadcrumb{
+ Name: c.CurrentProject.Name,
+ Url: projectUrl,
+ }
+ breadcrumbs = append([]templates.Breadcrumb{rootBreadcrumb}, breadcrumbs...)
+ }
+ }
+
episodeGuideUrl := ""
defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug]
if hasAnnotations {
@@ -271,7 +288,9 @@ func getBaseData(c *RequestContext) templates.BaseData {
}
baseData := templates.BaseData{
- Theme: c.Theme,
+ Theme: c.Theme,
+ Title: title,
+ Breadcrumbs: breadcrumbs,
CurrentUrl: c.FullUrl(),
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
@@ -365,7 +384,7 @@ func ProjectCSS(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusBadRequest, NewSafeError(nil, "You must provide a 'color' parameter.\n"))
}
- baseData := getBaseData(c)
+ baseData := getBaseData(c, "", nil)
bgColor := noire.NewHex(color)
h, s, l := bgColor.HSL()
@@ -408,7 +427,7 @@ func FourOhFour(c *RequestContext) ResponseData {
templates.BaseData
Wanted string
}{
- BaseData: getBaseData(c),
+ BaseData: getBaseData(c, "Page not found", nil),
Wanted: c.FullUrl(),
}
res.MustWriteTemplate("404.html", templateData, c.Perf)
@@ -426,7 +445,7 @@ type RejectData struct {
func RejectRequest(c *RequestContext, reason string) ResponseData {
var res ResponseData
err := res.WriteTemplate("reject.html", RejectData{
- BaseData: getBaseData(c),
+ BaseData: getBaseData(c, "Rejected", nil),
RejectReason: reason,
}, c.Perf)
if err != nil {
diff --git a/src/website/showcase.go b/src/website/showcase.go
index 3577fa15..52c328a6 100644
--- a/src/website/showcase.go
+++ b/src/website/showcase.go
@@ -53,8 +53,7 @@ func Showcase(c *RequestContext) ResponseData {
jsonItems := templates.TimelineItemsToJSON(showcaseItems)
c.Perf.EndBlock()
- baseData := getBaseData(c)
- baseData.Title = "Community Showcase"
+ baseData := getBaseDataAutocrumb(c, "Community Showcase")
var res ResponseData
res.MustWriteTemplate("showcase.html", ShowcaseData{
BaseData: baseData,
diff --git a/src/website/snippet.go b/src/website/snippet.go
index 08d5c86d..a5397238 100644
--- a/src/website/snippet.go
+++ b/src/website/snippet.go
@@ -103,7 +103,11 @@ func Snippet(c *RequestContext) ResponseData {
opengraph = append(opengraph, opengraphYoutube...)
}
- baseData := getBaseData(c)
+ baseData := getBaseData(
+ c,
+ fmt.Sprintf("Snippet by %s", snippet.OwnerName),
+ []templates.Breadcrumb{{Name: snippet.OwnerName, Url: snippet.OwnerUrl}},
+ )
baseData.OpenGraphItems = opengraph // NOTE(asaf): We're overriding the defaults on purpose.
var res ResponseData
err = res.WriteTemplate("snippet.html", SnippetData{
diff --git a/src/website/staticpages.go b/src/website/staticpages.go
index 9eb5cedf..16ee2168 100644
--- a/src/website/staticpages.go
+++ b/src/website/staticpages.go
@@ -2,42 +2,42 @@ package website
func Manifesto(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("manifesto.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("manifesto.html", getBaseDataAutocrumb(c, "Manifesto"), c.Perf)
return res
}
func About(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("about.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("about.html", getBaseDataAutocrumb(c, "About"), c.Perf)
return res
}
func CodeOfConduct(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("code_of_conduct.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("code_of_conduct.html", getBaseDataAutocrumb(c, "Code of Conduct"), c.Perf)
return res
}
func CommunicationGuidelines(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("communication_guidelines.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("communication_guidelines.html", getBaseDataAutocrumb(c, "Communication Guidelines"), c.Perf)
return res
}
func ContactPage(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("contact.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("contact.html", getBaseDataAutocrumb(c, "Contact Us"), c.Perf)
return res
}
func MonthlyUpdatePolicy(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("monthly_update_policy.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("monthly_update_policy.html", getBaseDataAutocrumb(c, "Monthly Update Policy"), c.Perf)
return res
}
func ProjectSubmissionGuidelines(c *RequestContext) ResponseData {
var res ResponseData
- res.MustWriteTemplate("project_submission_guidelines.html", getBaseData(c), c.Perf)
+ res.MustWriteTemplate("project_submission_guidelines.html", getBaseDataAutocrumb(c, "Project Submission Guidelines"), c.Perf)
return res
}
diff --git a/src/website/subforum_helper.go b/src/website/subforum_helper.go
deleted file mode 100644
index f15def2e..00000000
--- a/src/website/subforum_helper.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package website
-
-import (
- "git.handmade.network/hmn/hmn/src/models"
-)
-
-var ThreadTypeDisplayNames = map[models.ThreadType]string{
- models.ThreadTypeProjectBlogPost: "Blog",
- models.ThreadTypeForumPost: "Forums",
-}
diff --git a/src/website/timeline_helper.go b/src/website/timeline_helper.go
index 4dac16ce..3add832a 100644
--- a/src/website/timeline_helper.go
+++ b/src/website/timeline_helper.go
@@ -70,7 +70,7 @@ func PostToTimelineItem(lineageBuilder *models.SubforumLineageBuilder, post *mod
Description: "", // NOTE(asaf): No description for posts
Title: thread.Title,
- Breadcrumbs: PostBreadcrumbs(lineageBuilder, project, thread),
+ Breadcrumbs: GenericThreadBreadcrumbs(lineageBuilder, project, thread),
}
}
diff --git a/src/website/urls.go b/src/website/urls.go
deleted file mode 100644
index 69bb4b15..00000000
--- a/src/website/urls.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package website
-
-import (
- "math"
-
- "git.handmade.network/hmn/hmn/src/hmnurl"
- "git.handmade.network/hmn/hmn/src/models"
- "git.handmade.network/hmn/hmn/src/utils"
-)
-
-func NumPages(numThings, thingsPerPage int) int {
- return utils.IntMax(int(math.Ceil(float64(numThings)/float64(thingsPerPage))), 1)
-}
-
-func BuildProjectRootResourceUrl(projectSlug string, kind models.ThreadType) string {
- switch kind {
- case models.ThreadTypeProjectBlogPost:
- return hmnurl.BuildBlog(projectSlug, 1)
- case models.ThreadTypeForumPost:
- return hmnurl.BuildForum(projectSlug, nil, 1)
- }
- return hmnurl.BuildProjectHomepage(projectSlug)
-}
diff --git a/src/website/user.go b/src/website/user.go
index 2ec35d1a..099fbb03 100644
--- a/src/website/user.go
+++ b/src/website/user.go
@@ -224,8 +224,7 @@ func UserProfile(c *RequestContext) ResponseData {
templateUser := templates.UserToTemplate(profileUser, c.Theme)
- baseData := getBaseData(c)
- baseData.Title = templateUser.Name
+ baseData := getBaseDataAutocrumb(c, templateUser.Name)
var res ResponseData
res.MustWriteTemplate("user_profile.html", UserProfileTemplateData{
@@ -322,8 +321,7 @@ func UserSettings(c *RequestContext) ResponseData {
templateUser := templates.UserToTemplate(c.CurrentUser, c.Theme)
- baseData := getBaseData(c)
- baseData.Title = templateUser.Name
+ baseData := getBaseDataAutocrumb(c, templateUser.Name)
res.MustWriteTemplate("user_settings.html", UserSettingsTemplateData{
BaseData: baseData,
diff --git a/src/website/whenisit.go b/src/website/whenisit.go
index 871d6912..eadf590c 100644
--- a/src/website/whenisit.go
+++ b/src/website/whenisit.go
@@ -25,8 +25,7 @@ func WhenIsIt(c *RequestContext) ResponseData {
hasTimestamp = (err == nil)
}
- baseData := getBaseData(c)
- baseData.Title = "When is it?"
+ baseData := getBaseDataAutocrumb(c, "When is it?")
baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{
Property: "og:title",