Breadcrumbs

This commit is contained in:
Asaf Gartner 2021-09-01 21:25:09 +03:00
parent 1f39b166cb
commit d78a2e8e82
23 changed files with 233 additions and 164 deletions

View File

@ -88,7 +88,7 @@
{{- if gt $i 0 -}}
<span class="ph2">&raquo;</span>
{{- end -}}
<a class="breadcrumb {{ if .Current }}current{{ end }}" href="{{ .Url }}">{{ .Name }}</a>
<a class="breadcrumb" href="{{ .Url }}">{{ .Name }}</a>
{{- end }}
</div>
{{ end }}

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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),

View File

@ -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

View File

@ -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)

View File

@ -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"},

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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, "")
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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,

View File

@ -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{

View File

@ -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
}

View File

@ -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",
}

View File

@ -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),
}
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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",