Get forum threads mostly done
Still need to do breadcrumbs, but that applies to forum categories too actually.
This commit is contained in:
		
							parent
							
								
									c8231750aa
								
							
						
					
					
						commit
						d6481ab421
					
				| 
						 | 
					@ -8,67 +8,67 @@ import (
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/oops"
 | 
						"git.handmade.network/hmn/hmn/src/oops"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexHomepage *regexp.Regexp = regexp.MustCompile("^/$")
 | 
					var RegexHomepage = regexp.MustCompile("^/$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildHomepage() string {
 | 
					func BuildHomepage() string {
 | 
				
			||||||
	return Url("/", nil)
 | 
						return Url("/", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexLogin *regexp.Regexp = regexp.MustCompile("^/login$")
 | 
					var RegexLogin = regexp.MustCompile("^/login$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildLogin() string {
 | 
					func BuildLogin() string {
 | 
				
			||||||
	return Url("/login", nil)
 | 
						return Url("/login", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexLogout *regexp.Regexp = regexp.MustCompile("^/logout$")
 | 
					var RegexLogout = regexp.MustCompile("^/logout$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildLogout() string {
 | 
					func BuildLogout() string {
 | 
				
			||||||
	return Url("/logout", nil)
 | 
						return Url("/logout", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexManifesto *regexp.Regexp = regexp.MustCompile("^/manifesto$")
 | 
					var RegexManifesto = regexp.MustCompile("^/manifesto$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildManifesto() string {
 | 
					func BuildManifesto() string {
 | 
				
			||||||
	return Url("/manifesto", nil)
 | 
						return Url("/manifesto", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexAbout *regexp.Regexp = regexp.MustCompile("^/about$")
 | 
					var RegexAbout = regexp.MustCompile("^/about$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildAbout() string {
 | 
					func BuildAbout() string {
 | 
				
			||||||
	return Url("/about", nil)
 | 
						return Url("/about", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexCodeOfConduct *regexp.Regexp = regexp.MustCompile("^/code-of-conduct$")
 | 
					var RegexCodeOfConduct = regexp.MustCompile("^/code-of-conduct$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildCodeOfConduct() string {
 | 
					func BuildCodeOfConduct() string {
 | 
				
			||||||
	return Url("/code-of-conduct", nil)
 | 
						return Url("/code-of-conduct", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexCommunicationGuidelines *regexp.Regexp = regexp.MustCompile("^/communication-guidelines$")
 | 
					var RegexCommunicationGuidelines = regexp.MustCompile("^/communication-guidelines$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildCommunicationGuidelines() string {
 | 
					func BuildCommunicationGuidelines() string {
 | 
				
			||||||
	return Url("/communication-guidelines", nil)
 | 
						return Url("/communication-guidelines", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexContactPage *regexp.Regexp = regexp.MustCompile("^/contact$")
 | 
					var RegexContactPage = regexp.MustCompile("^/contact$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildContactPage() string {
 | 
					func BuildContactPage() string {
 | 
				
			||||||
	return Url("/contact", nil)
 | 
						return Url("/contact", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexMonthlyUpdatePolicy *regexp.Regexp = regexp.MustCompile("^/monthly-update-policy$")
 | 
					var RegexMonthlyUpdatePolicy = regexp.MustCompile("^/monthly-update-policy$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildMonthlyUpdatePolicy() string {
 | 
					func BuildMonthlyUpdatePolicy() string {
 | 
				
			||||||
	return Url("/monthly-update-policy", nil)
 | 
						return Url("/monthly-update-policy", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexProjectSubmissionGuidelines *regexp.Regexp = regexp.MustCompile("^/project-guidelines$")
 | 
					var RegexProjectSubmissionGuidelines = regexp.MustCompile("^/project-guidelines$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildProjectSubmissionGuidelines() string {
 | 
					func BuildProjectSubmissionGuidelines() string {
 | 
				
			||||||
	return Url("/project-guidelines", nil)
 | 
						return Url("/project-guidelines", nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexFeed *regexp.Regexp = regexp.MustCompile(`^/feed(/(?P<page>.+)?)?$`)
 | 
					var RegexFeed = regexp.MustCompile(`^/feed(/(?P<page>.+)?)?$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildFeed() string {
 | 
					func BuildFeed() string {
 | 
				
			||||||
	return Url("/feed", nil)
 | 
						return Url("/feed", nil)
 | 
				
			||||||
| 
						 | 
					@ -84,7 +84,7 @@ func BuildFeedWithPage(page int) string {
 | 
				
			||||||
	return Url("/feed/"+strconv.Itoa(page), nil)
 | 
						return Url("/feed/"+strconv.Itoa(page), nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexForumThread *regexp.Regexp = regexp.MustCompile(`^/(?P<cats>forums(/[^\d]+?)*)/t/(?P<threadid>\d+)(/(?P<page>\d+))?$`)
 | 
					var RegexForumThread = regexp.MustCompile(`^/(?P<cats>forums(/[^\d]+?)*)/t/(?P<threadid>\d+)(/(?P<page>\d+))?$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildForumThread(projectSlug string, subforums []string, threadId int, page int) string {
 | 
					func BuildForumThread(projectSlug string, subforums []string, threadId int, page int) string {
 | 
				
			||||||
	if page < 1 {
 | 
						if page < 1 {
 | 
				
			||||||
| 
						 | 
					@ -114,7 +114,7 @@ func BuildForumThread(projectSlug string, subforums []string, threadId int, page
 | 
				
			||||||
	return ProjectUrl(builder.String(), nil, projectSlug)
 | 
						return ProjectUrl(builder.String(), nil, projectSlug)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexForumCategory *regexp.Regexp = regexp.MustCompile(`^/(?P<cats>forums(/[^\d]+?)*)(/(?P<page>\d+))?$`)
 | 
					var RegexForumCategory = regexp.MustCompile(`^/(?P<cats>forums(/[^\d]+?)*)(/(?P<page>\d+))?$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildForumCategory(projectSlug string, subforums []string, page int) string {
 | 
					func BuildForumCategory(projectSlug string, subforums []string, page int) string {
 | 
				
			||||||
	if page < 1 {
 | 
						if page < 1 {
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,7 @@ func BuildForumCategory(projectSlug string, subforums []string, page int) string
 | 
				
			||||||
	return ProjectUrl(builder.String(), nil, projectSlug)
 | 
						return ProjectUrl(builder.String(), nil, projectSlug)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexForumPost *regexp.Regexp = regexp.MustCompile(``) // TODO(asaf): Complete this and test it
 | 
					var RegexForumPost = regexp.MustCompile(``) // TODO(asaf): Complete this and test it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildForumPost(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
					func BuildForumPost(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
				
			||||||
	var builder strings.Builder
 | 
						var builder strings.Builder
 | 
				
			||||||
| 
						 | 
					@ -166,13 +166,38 @@ func BuildForumPost(projectSlug string, subforums []string, threadId int, postId
 | 
				
			||||||
	return ProjectUrl(builder.String(), nil, projectSlug)
 | 
						return ProjectUrl(builder.String(), nil, projectSlug)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexProjectCSS *regexp.Regexp = regexp.MustCompile("^/assets/project.css$")
 | 
					var RegexForumPostDelete = regexp.MustCompile(``) // TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BuildForumPostDelete(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
				
			||||||
 | 
						return BuildForumPost(projectSlug, subforums, threadId, postId) + "/delete"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var RegexForumPostEdit = regexp.MustCompile(``) // TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BuildForumPostEdit(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
				
			||||||
 | 
						return BuildForumPost(projectSlug, subforums, threadId, postId) + "/edit"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var RegexForumPostReply = regexp.MustCompile(``) // TODO(asaf): Ha ha! I, Ben, have played a trick on you, and forced you to do this regex as well!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: It's kinda weird that we have "replies" to a specific post. That's not how the data works. I guess this just affects what you see as the "post you're replying to" on the post composer page?
 | 
				
			||||||
 | 
					func BuildForumPostReply(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
				
			||||||
 | 
						return BuildForumPost(projectSlug, subforums, threadId, postId) + "/reply"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var RegexForumPostQuote = regexp.MustCompile(``) // TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BuildForumPostQuote(projectSlug string, subforums []string, threadId int, postId int) string {
 | 
				
			||||||
 | 
						return BuildForumPost(projectSlug, subforums, threadId, postId) + "/quote"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var RegexProjectCSS = regexp.MustCompile("^/assets/project.css$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildProjectCSS(color string) string {
 | 
					func BuildProjectCSS(color string) string {
 | 
				
			||||||
	return Url("/assets/project.css", []Q{{"color", color}})
 | 
						return Url("/assets/project.css", []Q{{"color", color}})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexPublic *regexp.Regexp = regexp.MustCompile("^/public/.+$")
 | 
					var RegexPublic = regexp.MustCompile("^/public/.+$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildPublic(filepath string) string {
 | 
					func BuildPublic(filepath string) string {
 | 
				
			||||||
	filepath = strings.Trim(filepath, "/")
 | 
						filepath = strings.Trim(filepath, "/")
 | 
				
			||||||
| 
						 | 
					@ -193,4 +218,4 @@ func BuildPublic(filepath string) string {
 | 
				
			||||||
	return Url(builder.String(), nil)
 | 
						return Url(builder.String(), nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var RegexCatchAll *regexp.Regexp = regexp.MustCompile("")
 | 
					var RegexCatchAll = regexp.MustCompile("")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/hmnurl"
 | 
						"git.handmade.network/hmn/hmn/src/hmnurl"
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/models"
 | 
						"git.handmade.network/hmn/hmn/src/models"
 | 
				
			||||||
| 
						 | 
					@ -16,24 +17,38 @@ func PostToTemplate(p *models.Post, author *models.User) Post {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Post{
 | 
						return Post{
 | 
				
			||||||
		ID: p.ID,
 | 
							ID: p.ID,
 | 
				
			||||||
		Url: "nope", // TODO
 | 
					
 | 
				
			||||||
 | 
							// Urls not set here. See AddUrls.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Preview:  p.Preview,
 | 
							Preview:  p.Preview,
 | 
				
			||||||
		ReadOnly: p.ReadOnly,
 | 
							ReadOnly: p.ReadOnly,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Author: authorUser,
 | 
							Author: authorUser,
 | 
				
			||||||
		// No content. Do it yourself if you care.
 | 
							// No content. A lot of the time we don't have this handy and don't need it. See AddContentVersion.
 | 
				
			||||||
		PostDate: p.PostDate,
 | 
							PostDate: p.PostDate,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		IP: p.IP.String(),
 | 
							IP: p.IP.String(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func PostToTemplateWithContent(p *models.Post, author *models.User, content string) Post {
 | 
					func (p *Post) AddContentVersion(ver models.PostVersion, editor *models.User) {
 | 
				
			||||||
	post := PostToTemplate(p, author)
 | 
						p.Content = template.HTML(ver.TextParsed)
 | 
				
			||||||
	post.Content = template.HTML(content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return post
 | 
						if editor != nil {
 | 
				
			||||||
 | 
							editorTmpl := UserToTemplate(editor)
 | 
				
			||||||
 | 
							p.Editor = &editorTmpl
 | 
				
			||||||
 | 
							p.EditDate = ver.EditDate
 | 
				
			||||||
 | 
							p.EditIP = maybeIp(ver.EditIP)
 | 
				
			||||||
 | 
							p.EditReason = ver.EditReason
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Post) AddUrls(projectSlug string, subforums []string, threadId int, postId int) {
 | 
				
			||||||
 | 
						p.Url = hmnurl.BuildForumPost(projectSlug, subforums, threadId, postId)
 | 
				
			||||||
 | 
						p.DeleteUrl = hmnurl.BuildForumPostDelete(projectSlug, subforums, threadId, postId)
 | 
				
			||||||
 | 
						p.EditUrl = hmnurl.BuildForumPostEdit(projectSlug, subforums, threadId, postId)
 | 
				
			||||||
 | 
						p.ReplyUrl = hmnurl.BuildForumPostReply(projectSlug, subforums, threadId, postId)
 | 
				
			||||||
 | 
						p.QuoteUrl = hmnurl.BuildForumPostQuote(projectSlug, subforums, threadId, postId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ProjectToTemplate(p *models.Project) Project {
 | 
					func ProjectToTemplate(p *models.Project) Project {
 | 
				
			||||||
| 
						 | 
					@ -103,3 +118,11 @@ func maybeString(s *string) string {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return *s
 | 
						return *s
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func maybeIp(ip *net.IPNet) string {
 | 
				
			||||||
 | 
						if ip == nil {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ip.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{ define "content" }}
 | 
					{{ define "content" }}
 | 
				
			||||||
<div class="content-block">
 | 
					<div class="content-block">
 | 
				
			||||||
 | 
					    <div class="optionbar">
 | 
				
			||||||
 | 
					        <a class="button" href="{{ .CategoryUrl }}">← Back to index</a>
 | 
				
			||||||
 | 
					        {{ template "pagination.html" .Pagination }}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
    {{ range .Posts }}
 | 
					    {{ range .Posts }}
 | 
				
			||||||
        <div class="post background-even pa3 bbcode"> {{/* TODO: Dynamically switch between bbcode and markdown */}}
 | 
					        <div class="post background-even pa3 bbcode"> {{/* TODO: Dynamically switch between bbcode and markdown */}}
 | 
				
			||||||
            <div class="fl w-100 w-25-l pt3 pa3-l flex tc-l">
 | 
					            <div class="fl w-100 w-25-l pt3 pa3-l flex tc-l">
 | 
				
			||||||
| 
						 | 
					@ -62,28 +66,28 @@
 | 
				
			||||||
                        {{ if $.User }}
 | 
					                        {{ if $.User }}
 | 
				
			||||||
                            <div class="flex pr3">
 | 
					                            <div class="flex pr3">
 | 
				
			||||||
                                {{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
 | 
					                                {{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
 | 
				
			||||||
                                    <a class="delete action button" href="{{ .Url }}/delete" title="Delete">✖</a> 
 | 
					                                    <a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a> 
 | 
				
			||||||
                                    <a class="edit action button" href="{{ .Url }}/edit" title="Edit">✎</a> 
 | 
					                                    <a class="edit action button" href="{{ .EditUrl }}" title="Edit">✎</a> 
 | 
				
			||||||
                                {{ end }}
 | 
					                                {{ end }}
 | 
				
			||||||
                                {{ if or (not $.Thread.Locked) $.User.IsStaff }}
 | 
					                                {{ if or (not $.Thread.Locked) $.User.IsStaff }}
 | 
				
			||||||
                                    {{ if $.Thread.Locked }}
 | 
					                                    {{ if $.Thread.Locked }}
 | 
				
			||||||
                                        WARNING: locked thread - use power responsibly!
 | 
					                                        WARNING: locked thread - use power responsibly!
 | 
				
			||||||
                                    {{ end }}
 | 
					                                    {{ end }}
 | 
				
			||||||
                                    <a class="reply action button" href="{{ .Url }}/reply" title="Reply">↪</a> 
 | 
					                                    <a class="reply action button" href="{{ .ReplyUrl }}" title="Reply">↪</a> 
 | 
				
			||||||
                                    <a class="quote action button" href="{{ .Url }}/quote" title="Quote">❝</a>
 | 
					                                    <a class="quote action button" href="{{ .QuoteUrl }}" title="Quote">❝</a>
 | 
				
			||||||
                                {{ end }}
 | 
					                                {{ end }}
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        {{ end }}
 | 
					                        {{ end }}
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="w-100 pb3">
 | 
					                    <div class="w-100 pb3">
 | 
				
			||||||
                        <div class="b" role="heading" aria-level="2">{{ $.Thread.Title }}</div>
 | 
					                        <div class="b" role="heading" aria-level="2">{{ $.Thread.Title }}</div>
 | 
				
			||||||
                        <span>{{ relativedate .PostDate }} ago</span>
 | 
					                        {{ timehtml (relativedate .PostDate) .PostDate }}
 | 
				
			||||||
                        {{ if .Editor }}
 | 
					                        {{ if .Editor }}
 | 
				
			||||||
                            <span class="pl3">
 | 
					                            <span class="pl3">
 | 
				
			||||||
                                Edited by
 | 
					                                Edited by
 | 
				
			||||||
                                <a class="name" href="{{ .Editor.ProfileUrl }}" target="_blank">{{ coalesce .Editor.Name .Editor.Username }}</a>
 | 
					                                <a class="name" href="{{ .Editor.ProfileUrl }}" target="_blank">{{ coalesce .Editor.Name .Editor.Username }}</a>
 | 
				
			||||||
                                {{ if and $.User.IsStaff .EditIP }}<span class="ip">[{{ .EditIP }}]</span>{{ end }}
 | 
					                                {{ if and $.User.IsStaff .EditIP }}<span class="ip">[{{ .EditIP }}]</span>{{ end }}
 | 
				
			||||||
                                on <span class="editdate">{{ .EditDate }}</span>
 | 
					                                on {{ timehtml (absolutedate .EditDate) .EditDate }}
 | 
				
			||||||
                                {{ with .EditReason }}
 | 
					                                {{ with .EditReason }}
 | 
				
			||||||
                                    Reason: {{ . }}
 | 
					                                    Reason: {{ . }}
 | 
				
			||||||
                                {{ end }}
 | 
					                                {{ end }}
 | 
				
			||||||
| 
						 | 
					@ -108,5 +112,20 @@
 | 
				
			||||||
            <div class="cb"></div>
 | 
					            <div class="cb"></div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    {{ end }}
 | 
					    {{ end }}
 | 
				
			||||||
 | 
					    <div class="optionbar bottom">
 | 
				
			||||||
 | 
					        <div class="options order-1">
 | 
				
			||||||
 | 
					            <a class="button" href="{{ .CategoryUrl }}">← Back to index</a>
 | 
				
			||||||
 | 
					            {{ if .Thread.Locked }}
 | 
				
			||||||
 | 
					                <span>Thread is locked.</span>
 | 
				
			||||||
 | 
					            {{ else if .User }}
 | 
				
			||||||
 | 
					                <a class="button" href="{{ .ReplyUrl }}">⤷ Reply to Thread</a>
 | 
				
			||||||
 | 
					            {{ else }}
 | 
				
			||||||
 | 
					                <span><a href="{% url 'member_login' subdomain=None %}">Log in</a> to reply</span>
 | 
				
			||||||
 | 
					            {{ end }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="options order-0 order-last-ns">
 | 
				
			||||||
 | 
					            {{ template "pagination.html" .Pagination }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
{{ end }}
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,5 +87,10 @@
 | 
				
			||||||
                loginPopup.classList.toggle("open");
 | 
					                loginPopup.classList.toggle("open");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const time of document.querySelectorAll('time')) {
 | 
				
			||||||
 | 
					            const d = new Date(Date.parse(time.dateTime));
 | 
				
			||||||
 | 
					            time.title = d.toLocaleString();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ It should be called with PostListItem.
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
					        <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
				
			||||||
        <div class="details">
 | 
					        <div class="details">
 | 
				
			||||||
            <a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — <span class="datetime">{{ relativedate .Date }}</span>
 | 
					            <a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — {{ timehtml (relativedate .Date) .Date }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        {{ with .Content }}
 | 
					        {{ with .Content }}
 | 
				
			||||||
            <div class="mt2">
 | 
					            <div class="mt2">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ It should be called with ThreadListItem.
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
					        <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
				
			||||||
        <div class="details">
 | 
					        <div class="details">
 | 
				
			||||||
            <a class="user" href="{{ .FirstUser.ProfileUrl }}">{{ .FirstUser.Name }}</a> — <span class="datetime">{{ relativedate .FirstDate }}</span>
 | 
					            <a class="user" href="{{ .FirstUser.ProfileUrl }}">{{ .FirstUser.Name }}</a> — {{ timehtml (relativedate .FirstDate) .FirstDate }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        {{ with .Content }}
 | 
					        {{ with .Content }}
 | 
				
			||||||
            <div class="mt2">
 | 
					            <div class="mt2">
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ It should be called with ThreadListItem.
 | 
				
			||||||
    <div class="latestpost dn flex-ns flex-shrink-0 items-center ml2">
 | 
					    <div class="latestpost dn flex-ns flex-shrink-0 items-center ml2">
 | 
				
			||||||
        <img class="avatar-icon mr2" src="{{ .LastUser.AvatarUrl }}">
 | 
					        <img class="avatar-icon mr2" src="{{ .LastUser.AvatarUrl }}">
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
            <div>Last post <span class="datetime">{{ relativedate .LastDate }}</span></div>
 | 
					            <div>Last post {{ timehtml (relativedate .LastDate) .LastDate }}</div>
 | 
				
			||||||
            <a class="user" href="{{ .LastUser.ProfileUrl }}">{{ .LastUser.Name }}</a>
 | 
					            <a class="user" href="{{ .LastUser.ProfileUrl }}">{{ .LastUser.Name }}</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,7 +271,7 @@
 | 
				
			||||||
    <div class="flex-grow-1">
 | 
					    <div class="flex-grow-1">
 | 
				
			||||||
        <div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
					        <div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
 | 
				
			||||||
        <div class="details">
 | 
					        <div class="details">
 | 
				
			||||||
            <a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — <span class="datetime">{{ relativedate .Date }}</span>
 | 
					            <a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — {{ timehtml (relativedate .Date) .Date }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="overflow-hidden mh-5 mt2 relative">
 | 
					        <div class="overflow-hidden mh-5 mt2 relative">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
        {{ end }}
 | 
					        {{ end }}
 | 
				
			||||||
    {{ end }}
 | 
					    {{ end }}
 | 
				
			||||||
    {{ if .Title }}
 | 
					    {{ if .Title }}
 | 
				
			||||||
        <title>{{ .Title }} | Handmade Network</title>
 | 
					        <title>{{ .Title }} | Handmade Network</title> {{/* TODO: Some parts of the site replace "Handmade Network" with other things like "4coder Forums". */}}
 | 
				
			||||||
    {{ else }}
 | 
					    {{ else }}
 | 
				
			||||||
        <title>Handmade Network</title>
 | 
					        <title>Handmade Network</title>
 | 
				
			||||||
    {{ end }}
 | 
					    {{ end }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,9 @@ func names(ts []*template.Template) []string {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var HMNTemplateFuncs = template.FuncMap{
 | 
					var HMNTemplateFuncs = template.FuncMap{
 | 
				
			||||||
 | 
						"absolutedate": func(t time.Time) string {
 | 
				
			||||||
 | 
							return t.Format("January 2, 2006, 3:04pm")
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"alpha": func(alpha float64, color noire.Color) noire.Color {
 | 
						"alpha": func(alpha float64, color noire.Color) noire.Color {
 | 
				
			||||||
		color.Alpha = alpha
 | 
							color.Alpha = alpha
 | 
				
			||||||
		return color
 | 
							return color
 | 
				
			||||||
| 
						 | 
					@ -157,6 +160,10 @@ var HMNTemplateFuncs = template.FuncMap{
 | 
				
			||||||
	"staticthemenobust": func(theme string, filepath string) string {
 | 
						"staticthemenobust": func(theme string, filepath string) string {
 | 
				
			||||||
		return hmnurl.StaticThemeUrl(filepath, theme, nil)
 | 
							return hmnurl.StaticThemeUrl(filepath, theme, nil)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"timehtml": func(formatted string, t time.Time) template.HTML {
 | 
				
			||||||
 | 
							iso := t.Format(time.RFC3339)
 | 
				
			||||||
 | 
							return template.HTML(fmt.Sprintf(`<time datetime="%s">%s</time>`, iso, formatted))
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"url": func(url string) string {
 | 
						"url": func(url string) string {
 | 
				
			||||||
		return hmnurl.Url(url, nil)
 | 
							return hmnurl.Url(url, nil)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,12 @@ type Thread struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Post struct {
 | 
					type Post struct {
 | 
				
			||||||
	ID int
 | 
						ID int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Url       string
 | 
						Url       string
 | 
				
			||||||
 | 
						DeleteUrl string
 | 
				
			||||||
 | 
						EditUrl   string
 | 
				
			||||||
 | 
						ReplyUrl  string
 | 
				
			||||||
 | 
						QuoteUrl  string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Preview  string
 | 
						Preview  string
 | 
				
			||||||
	ReadOnly bool
 | 
						ReadOnly bool
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import (
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/models"
 | 
						"git.handmade.network/hmn/hmn/src/models"
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/oops"
 | 
						"git.handmade.network/hmn/hmn/src/oops"
 | 
				
			||||||
	"git.handmade.network/hmn/hmn/src/templates"
 | 
						"git.handmade.network/hmn/hmn/src/templates"
 | 
				
			||||||
 | 
						"git.handmade.network/hmn/hmn/src/utils"
 | 
				
			||||||
	"github.com/jackc/pgx/v4/pgxpool"
 | 
						"github.com/jackc/pgx/v4/pgxpool"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -262,7 +263,7 @@ func ForumCategory(c *RequestContext) ResponseData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	baseData := getBaseData(c)
 | 
						baseData := getBaseData(c)
 | 
				
			||||||
	baseData.Title = c.CurrentProject.Name + " Forums"
 | 
						baseData.Title = c.CurrentProject.Name + " Forums"
 | 
				
			||||||
	baseData.Breadcrumbs = []templates.Breadcrumb{
 | 
						baseData.Breadcrumbs = []templates.Breadcrumb{ // TODO(ben): This is wrong; it needs to account for subcategories.
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			Name: c.CurrentProject.Name,
 | 
								Name: c.CurrentProject.Name,
 | 
				
			||||||
			Url:  hmnurl.ProjectUrl("/", nil, c.CurrentProject.Slug),
 | 
								Url:  hmnurl.ProjectUrl("/", nil, c.CurrentProject.Slug),
 | 
				
			||||||
| 
						 | 
					@ -299,13 +300,17 @@ func ForumCategory(c *RequestContext) ResponseData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type forumThreadData struct {
 | 
					type forumThreadData struct {
 | 
				
			||||||
	templates.BaseData
 | 
						templates.BaseData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Thread templates.Thread
 | 
						Thread templates.Thread
 | 
				
			||||||
	Posts  []templates.Post
 | 
						Posts  []templates.Post
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CategoryUrl string
 | 
				
			||||||
 | 
						ReplyUrl    string
 | 
				
			||||||
 | 
						Pagination  templates.Pagination
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ForumThread(c *RequestContext) ResponseData {
 | 
					func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
	const postsPerPage = 15
 | 
						const postsPerPage = 15
 | 
				
			||||||
	// TODO(asaf): Verify that the requested thread is not deleted, and only fetch non-deleted posts.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	threadId, err := strconv.Atoi(c.PathParams["threadid"])
 | 
						threadId, err := strconv.Atoi(c.PathParams["threadid"])
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -319,10 +324,16 @@ func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
	irow, err := db.QueryOne(c.Context(), c.Conn, threadQueryResult{},
 | 
						irow, err := db.QueryOne(c.Context(), c.Conn, threadQueryResult{},
 | 
				
			||||||
		`
 | 
							`
 | 
				
			||||||
		SELECT $columns
 | 
							SELECT $columns
 | 
				
			||||||
		FROM handmade_thread AS thread
 | 
							FROM
 | 
				
			||||||
		WHERE thread.id = $1
 | 
								handmade_thread AS thread
 | 
				
			||||||
 | 
								JOIN handmade_category AS cat ON cat.id = thread.category_id
 | 
				
			||||||
 | 
							WHERE
 | 
				
			||||||
 | 
								thread.id = $1
 | 
				
			||||||
 | 
								AND NOT thread.deleted
 | 
				
			||||||
 | 
								AND cat.project_id = $2
 | 
				
			||||||
		`,
 | 
							`,
 | 
				
			||||||
		threadId,
 | 
							threadId,
 | 
				
			||||||
 | 
							c.CurrentProject.ID,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	c.Perf.EndBlock()
 | 
						c.Perf.EndBlock()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -336,18 +347,46 @@ func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID)
 | 
						categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	page, numPages, ok := getPageInfo(c.PathParams["page"], 100, postsPerPage) // TODO: Not 100
 | 
						c.Perf.StartBlock("SQL", "Fetch category tree")
 | 
				
			||||||
 | 
						categoryTree := models.GetFullCategoryTree(c.Context(), c.Conn)
 | 
				
			||||||
 | 
						lineageBuilder := models.MakeCategoryLineageBuilder(categoryTree)
 | 
				
			||||||
 | 
						subforums := lineageBuilder.GetLineageSlugs(thread.CategoryID)[1:]
 | 
				
			||||||
 | 
						c.Perf.EndBlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						numPosts, err := db.QueryInt(c.Context(), c.Conn,
 | 
				
			||||||
 | 
							`
 | 
				
			||||||
 | 
							SELECT COUNT(*)
 | 
				
			||||||
 | 
							FROM handmade_post
 | 
				
			||||||
 | 
							WHERE
 | 
				
			||||||
 | 
								thread_id = $1
 | 
				
			||||||
 | 
								AND NOT deleted
 | 
				
			||||||
 | 
							`,
 | 
				
			||||||
 | 
							thread.ID,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(oops.New(err, "failed to get count of posts for thread"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						page, numPages, ok := getPageInfo(c.PathParams["page"], numPosts, postsPerPage)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		urlNoPage := ThreadUrl(thread, models.CatKindForum, categoryUrls[thread.CategoryID])
 | 
							urlNoPage := ThreadUrl(thread, models.CatKindForum, categoryUrls[thread.CategoryID])
 | 
				
			||||||
		return c.Redirect(urlNoPage, http.StatusSeeOther)
 | 
							return c.Redirect(urlNoPage, http.StatusSeeOther)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_ = numPages // TODO
 | 
						pagination := templates.Pagination{
 | 
				
			||||||
 | 
							Current: page,
 | 
				
			||||||
 | 
							Total:   numPages,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FirstUrl:    hmnurl.BuildForumThread(c.CurrentProject.Slug, subforums, thread.ID, 1),
 | 
				
			||||||
 | 
							LastUrl:     hmnurl.BuildForumThread(c.CurrentProject.Slug, subforums, thread.ID, numPages),
 | 
				
			||||||
 | 
							NextUrl:     hmnurl.BuildForumThread(c.CurrentProject.Slug, subforums, thread.ID, utils.IntClamp(1, page+1, numPages)),
 | 
				
			||||||
 | 
							PreviousUrl: hmnurl.BuildForumThread(c.CurrentProject.Slug, subforums, thread.ID, utils.IntClamp(1, page-1, numPages)),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Perf.StartBlock("SQL", "Fetch posts")
 | 
						c.Perf.StartBlock("SQL", "Fetch posts")
 | 
				
			||||||
	type postsQueryResult struct {
 | 
						type postsQueryResult struct {
 | 
				
			||||||
		Post   models.Post        `db:"post"`
 | 
							Post   models.Post        `db:"post"`
 | 
				
			||||||
		Content string       `db:"ver.text_parsed"`
 | 
							Ver    models.PostVersion `db:"ver"`
 | 
				
			||||||
		Author *models.User       `db:"author"`
 | 
							Author *models.User       `db:"author"`
 | 
				
			||||||
 | 
							Editor *models.User       `db:"editor"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	itPosts, err := db.Query(c.Context(), c.Conn, postsQueryResult{},
 | 
						itPosts, err := db.Query(c.Context(), c.Conn, postsQueryResult{},
 | 
				
			||||||
		`
 | 
							`
 | 
				
			||||||
| 
						 | 
					@ -356,8 +395,10 @@ func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
			handmade_post AS post
 | 
								handmade_post AS post
 | 
				
			||||||
			JOIN handmade_postversion AS ver ON post.current_id = ver.id
 | 
								JOIN handmade_postversion AS ver ON post.current_id = ver.id
 | 
				
			||||||
			LEFT JOIN auth_user AS author ON post.author_id = author.id
 | 
								LEFT JOIN auth_user AS author ON post.author_id = author.id
 | 
				
			||||||
 | 
								LEFT JOIN auth_user AS editor ON ver.editor_id = editor.id
 | 
				
			||||||
		WHERE
 | 
							WHERE
 | 
				
			||||||
			post.thread_id = $1
 | 
								post.thread_id = $1
 | 
				
			||||||
 | 
								AND NOT post.deleted
 | 
				
			||||||
		ORDER BY postdate
 | 
							ORDER BY postdate
 | 
				
			||||||
		LIMIT $2 OFFSET $3
 | 
							LIMIT $2 OFFSET $3
 | 
				
			||||||
		`,
 | 
							`,
 | 
				
			||||||
| 
						 | 
					@ -374,11 +415,16 @@ func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
	var posts []templates.Post
 | 
						var posts []templates.Post
 | 
				
			||||||
	for _, irow := range itPosts.ToSlice() {
 | 
						for _, irow := range itPosts.ToSlice() {
 | 
				
			||||||
		row := irow.(*postsQueryResult)
 | 
							row := irow.(*postsQueryResult)
 | 
				
			||||||
		posts = append(posts, templates.PostToTemplateWithContent(&row.Post, row.Author, row.Content))
 | 
					
 | 
				
			||||||
 | 
							post := templates.PostToTemplate(&row.Post, row.Author)
 | 
				
			||||||
 | 
							post.AddContentVersion(row.Ver, row.Editor)
 | 
				
			||||||
 | 
							post.AddUrls(c.CurrentProject.Slug, subforums, thread.ID, post.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							posts = append(posts, post)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	baseData := getBaseData(c)
 | 
						baseData := getBaseData(c)
 | 
				
			||||||
	// TODO(asaf): Replace page title with thread title
 | 
						baseData.Title = thread.Title
 | 
				
			||||||
	// TODO(asaf): Set breadcrumbs
 | 
						// TODO(asaf): Set breadcrumbs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var res ResponseData
 | 
						var res ResponseData
 | 
				
			||||||
| 
						 | 
					@ -386,6 +432,9 @@ func ForumThread(c *RequestContext) ResponseData {
 | 
				
			||||||
		BaseData:    baseData,
 | 
							BaseData:    baseData,
 | 
				
			||||||
		Thread:      templates.ThreadToTemplate(&thread),
 | 
							Thread:      templates.ThreadToTemplate(&thread),
 | 
				
			||||||
		Posts:       posts,
 | 
							Posts:       posts,
 | 
				
			||||||
 | 
							CategoryUrl: categoryUrls[thread.CategoryID],
 | 
				
			||||||
 | 
							ReplyUrl:    hmnurl.BuildForumPostReply(c.CurrentProject.Slug, subforums, thread.ID, *thread.FirstID),
 | 
				
			||||||
 | 
							Pagination:  pagination,
 | 
				
			||||||
	}, c.Perf)
 | 
						}, c.Perf)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,9 +44,10 @@ func GetProjectCategoryUrls(ctx context.Context, conn *pgxpool.Pool, projectId .
 | 
				
			||||||
			JOIN handmade_project AS project ON project.id = cat.project_id
 | 
								JOIN handmade_project AS project ON project.id = cat.project_id
 | 
				
			||||||
		WHERE
 | 
							WHERE
 | 
				
			||||||
			project.id = ANY ($1)
 | 
								project.id = ANY ($1)
 | 
				
			||||||
			AND cat.kind != 6
 | 
								AND cat.kind != $2
 | 
				
			||||||
		`, // TODO(asaf): Clean up the db and remove the cat.kind != 6 check
 | 
							`, // TODO(asaf): Clean up the db and remove the cat.kind != library resource check
 | 
				
			||||||
		projectId,
 | 
							projectId,
 | 
				
			||||||
 | 
							models.CatKindLibraryResource,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
| 
						 | 
					@ -94,13 +95,13 @@ func makeCategoryUrls(rows []interface{}) map[int]string {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CategoryUrl(projectSlug string, cats ...*models.Category) string {
 | 
					func CategoryUrl(projectSlug string, cats ...*models.Category) string {
 | 
				
			||||||
	catNames := make([]string, 0, len(cats))
 | 
						catSlugs := make([]string, 0, len(cats))
 | 
				
			||||||
	for _, cat := range cats {
 | 
						for _, cat := range cats {
 | 
				
			||||||
		catNames = append(catNames, *cat.Name)
 | 
							catSlugs = append(catSlugs, *cat.Slug)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	switch cats[0].Kind {
 | 
						switch cats[0].Kind {
 | 
				
			||||||
	case models.CatKindForum:
 | 
						case models.CatKindForum:
 | 
				
			||||||
		return hmnurl.BuildForumCategory(projectSlug, catNames[1:], 1)
 | 
							return hmnurl.BuildForumCategory(projectSlug, catSlugs[1:], 1)
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue