Use new UrlContext for project URLs
Wow that was a lot to change
This commit is contained in:
		
							parent
							
								
									73836c5e25
								
							
						
					
					
						commit
						cc9c3b3b60
					
				|  | @ -6,8 +6,9 @@ import ( | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.handmade.network/hmn/hmn/src/config" |  | ||||||
| 	"git.handmade.network/hmn/hmn/src/models" | 	"git.handmade.network/hmn/hmn/src/models" | ||||||
|  | 
 | ||||||
|  | 	"git.handmade.network/hmn/hmn/src/config" | ||||||
| 	"git.handmade.network/hmn/hmn/src/oops" | 	"git.handmade.network/hmn/hmn/src/oops" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -62,34 +63,25 @@ func GetBaseHost() string { | ||||||
| 	return baseUrlParsed.Host | 	return baseUrlParsed.Host | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type UrlContext struct { | ||||||
|  | 	PersonalProject bool | ||||||
|  | 	ProjectID       int | ||||||
|  | 	ProjectSlug     string | ||||||
|  | 	ProjectName     string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var HMNProjectContext = UrlContext{ | ||||||
|  | 	PersonalProject: false, | ||||||
|  | 	ProjectID:       models.HMNProjectID, | ||||||
|  | 	ProjectSlug:     models.HMNProjectSlug, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func Url(path string, query []Q) string { | func Url(path string, query []Q) string { | ||||||
| 	return ProjectUrl(path, query, "") | 	return UrlWithFragment(path, query, "") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func ProjectUrl(path string, query []Q, slug string) string { | func UrlWithFragment(path string, query []Q, fragment string) string { | ||||||
| 	return ProjectUrlWithFragment(path, query, slug, "") | 	return HMNProjectContext.UrlWithFragment(path, query, fragment) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ProjectUrlWithFragment(path string, query []Q, slug string, fragment string) string { |  | ||||||
| 	subdomain := slug |  | ||||||
| 	if slug == models.HMNProjectSlug { |  | ||||||
| 		subdomain = "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	host := baseUrlParsed.Host |  | ||||||
| 	if len(subdomain) > 0 { |  | ||||||
| 		host = slug + "." + host |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	url := url.URL{ |  | ||||||
| 		Scheme:   baseUrlParsed.Scheme, |  | ||||||
| 		Host:     host, |  | ||||||
| 		Path:     trim(path), |  | ||||||
| 		RawQuery: encodeQuery(query), |  | ||||||
| 		Fragment: fragment, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return url.String() |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func trim(path string) string { | func trim(path string) string { | ||||||
|  |  | ||||||
|  | @ -21,12 +21,11 @@ var RegexOldHome = regexp.MustCompile("^/home$") | ||||||
| var RegexHomepage = regexp.MustCompile("^/$") | var RegexHomepage = regexp.MustCompile("^/$") | ||||||
| 
 | 
 | ||||||
| func BuildHomepage() string { | func BuildHomepage() string { | ||||||
| 	return Url("/", nil) | 	return HMNProjectContext.BuildHomepage() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BuildOfficialProjectHomepage(projectSlug string) string { | func (c *UrlContext) BuildHomepage() string { | ||||||
| 	defer CatchPanic() | 	return c.Url("/", nil) | ||||||
| 	return ProjectUrl("/", nil, projectSlug) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexShowcase = regexp.MustCompile("^/showcase$") | var RegexShowcase = regexp.MustCompile("^/showcase$") | ||||||
|  | @ -196,7 +195,7 @@ func BuildUserProfile(username string) string { | ||||||
| var RegexUserSettings = regexp.MustCompile(`^/settings$`) | var RegexUserSettings = regexp.MustCompile(`^/settings$`) | ||||||
| 
 | 
 | ||||||
| func BuildUserSettings(section string) string { | func BuildUserSettings(section string) string { | ||||||
| 	return ProjectUrlWithFragment("/settings", nil, "", section) | 	return UrlWithFragment("/settings", nil, section) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -302,10 +301,10 @@ func BuildPersonalProject(id int, slug string) string { | ||||||
| 
 | 
 | ||||||
| var RegexProjectEdit = regexp.MustCompile("^/edit$") | var RegexProjectEdit = regexp.MustCompile("^/edit$") | ||||||
| 
 | 
 | ||||||
| func BuildProjectEdit(slug string, section string) string { | func (c *UrlContext) BuildProjectEdit(section string) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrlWithFragment(fmt.Sprintf("/p/%s/edit", slug), nil, "", section) | 	return c.UrlWithFragment("/edit", nil, section) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -367,7 +366,50 @@ func BuildPodcastEpisodeFile(filename string) string { | ||||||
| // Make sure to match Thread before Subforum in the router.
 | // Make sure to match Thread before Subforum in the router.
 | ||||||
| var RegexForum = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$`) | var RegexForum = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?(/(?P<page>\d+))?$`) | ||||||
| 
 | 
 | ||||||
| func BuildForum(projectSlug string, subforums []string, page int) string { | func (c *UrlContext) Url(path string, query []Q) string { | ||||||
|  | 	return c.UrlWithFragment(path, query, "") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *UrlContext) UrlWithFragment(path string, query []Q, fragment string) string { | ||||||
|  | 	if c == nil { | ||||||
|  | 		logging.Warn().Stack().Msg("URL context was nil; defaulting to the HMN URL context") | ||||||
|  | 		c = &HMNProjectContext | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if c.PersonalProject { | ||||||
|  | 		url := url.URL{ | ||||||
|  | 			Scheme:   baseUrlParsed.Scheme, | ||||||
|  | 			Host:     baseUrlParsed.Host, | ||||||
|  | 			Path:     fmt.Sprintf("p/%d/%s/%s", c.ProjectID, models.GeneratePersonalProjectSlug(c.ProjectName), trim(path)), | ||||||
|  | 			RawQuery: encodeQuery(query), | ||||||
|  | 			Fragment: fragment, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return url.String() | ||||||
|  | 	} else { | ||||||
|  | 		subdomain := c.ProjectSlug | ||||||
|  | 		if c.ProjectSlug == models.HMNProjectSlug { | ||||||
|  | 			subdomain = "" | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		host := baseUrlParsed.Host | ||||||
|  | 		if len(subdomain) > 0 { | ||||||
|  | 			host = c.ProjectSlug + "." + host | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		url := url.URL{ | ||||||
|  | 			Scheme:   baseUrlParsed.Scheme, | ||||||
|  | 			Host:     host, | ||||||
|  | 			Path:     trim(path), | ||||||
|  | 			RawQuery: encodeQuery(query), | ||||||
|  | 			Fragment: fragment, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return url.String() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *UrlContext) BuildForum(subforums []string, page int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	if page < 1 { | 	if page < 1 { | ||||||
| 		panic(oops.New(nil, "Invalid forum thread page (%d), must be >= 1", page)) | 		panic(oops.New(nil, "Invalid forum thread page (%d), must be >= 1", page)) | ||||||
|  | @ -380,13 +422,13 @@ func BuildForum(projectSlug string, subforums []string, page int) string { | ||||||
| 		builder.WriteString(strconv.Itoa(page)) | 		builder.WriteString(strconv.Itoa(page)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumNewThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new$`) | var RegexForumNewThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new$`) | ||||||
| var RegexForumNewThreadSubmit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new/submit$`) | var RegexForumNewThreadSubmit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/new/submit$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumNewThread(projectSlug string, subforums []string, submit bool) string { | func (c *UrlContext) BuildForumNewThread(subforums []string, submit bool) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildSubforumPath(subforums) | 	builder := buildSubforumPath(subforums) | ||||||
| 	builder.WriteString("/t/new") | 	builder.WriteString("/t/new") | ||||||
|  | @ -394,59 +436,59 @@ func BuildForumNewThread(projectSlug string, subforums []string, submit bool) st | ||||||
| 		builder.WriteString("/submit") | 		builder.WriteString("/submit") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)(-([^/]+))?(/(?P<page>\d+))?$`) | var RegexForumThread = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)(-([^/]+))?(/(?P<page>\d+))?$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumThread(projectSlug string, subforums []string, threadId int, title string, page int) string { | func (c *UrlContext) BuildForumThread(subforums []string, threadId int, title string, page int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumThreadPath(subforums, threadId, title, page) | 	builder := buildForumThreadPath(subforums, threadId, title, page) | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BuildForumThreadWithPostHash(projectSlug string, subforums []string, threadId int, title string, page int, postId int) string { | func (c *UrlContext) BuildForumThreadWithPostHash(subforums []string, threadId int, title string, page int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumThreadPath(subforums, threadId, title, page) | 	builder := buildForumThreadPath(subforums, threadId, title, page) | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrlWithFragment(builder.String(), nil, projectSlug, strconv.Itoa(postId)) | 	return UrlWithFragment(builder.String(), nil, strconv.Itoa(postId)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumPost = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)$`) | var RegexForumPost = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumPost(projectSlug string, subforums []string, threadId int, postId int) string { | func (c *UrlContext) BuildForumPost(subforums []string, threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumPostPath(subforums, threadId, postId) | 	builder := buildForumPostPath(subforums, threadId, postId) | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumPostDelete = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/delete$`) | var RegexForumPostDelete = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/delete$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumPostDelete(projectSlug string, subforums []string, threadId int, postId int) string { | func (c *UrlContext) BuildForumPostDelete(subforums []string, threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumPostPath(subforums, threadId, postId) | 	builder := buildForumPostPath(subforums, threadId, postId) | ||||||
| 	builder.WriteString("/delete") | 	builder.WriteString("/delete") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumPostEdit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/edit$`) | var RegexForumPostEdit = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/edit$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumPostEdit(projectSlug string, subforums []string, threadId int, postId int) string { | func (c *UrlContext) BuildForumPostEdit(subforums []string, threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumPostPath(subforums, threadId, postId) | 	builder := buildForumPostPath(subforums, threadId, postId) | ||||||
| 	builder.WriteString("/edit") | 	builder.WriteString("/edit") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexForumPostReply = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/reply$`) | var RegexForumPostReply = regexp.MustCompile(`^/forums(/(?P<subforums>[^\d/]+(/[^\d]+)*))?/t/(?P<threadid>\d+)/p/(?P<postid>\d+)/reply$`) | ||||||
| 
 | 
 | ||||||
| func BuildForumPostReply(projectSlug string, subforums []string, threadId int, postId int) string { | func (c *UrlContext) BuildForumPostReply(subforums []string, threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildForumPostPath(subforums, threadId, postId) | 	builder := buildForumPostPath(subforums, threadId, postId) | ||||||
| 	builder.WriteString("/reply") | 	builder.WriteString("/reply") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexWikiArticle = regexp.MustCompile(`^/wiki/(?P<threadid>\d+)(-([^/]+))?$`) | var RegexWikiArticle = regexp.MustCompile(`^/wiki/(?P<threadid>\d+)(-([^/]+))?$`) | ||||||
|  | @ -459,7 +501,7 @@ var RegexBlogsRedirect = regexp.MustCompile(`^/blogs(?P<remainder>.*)`) | ||||||
| 
 | 
 | ||||||
| var RegexBlog = regexp.MustCompile(`^/blog(/(?P<page>\d+))?$`) | var RegexBlog = regexp.MustCompile(`^/blog(/(?P<page>\d+))?$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlog(projectSlug string, page int) string { | func (c *UrlContext) BuildBlog(page int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	if page < 1 { | 	if page < 1 { | ||||||
| 		panic(oops.New(nil, "Invalid blog page (%d), must be >= 1", page)) | 		panic(oops.New(nil, "Invalid blog page (%d), must be >= 1", page)) | ||||||
|  | @ -470,63 +512,63 @@ func BuildBlog(projectSlug string, page int) string { | ||||||
| 		path += "/" + strconv.Itoa(page) | 		path += "/" + strconv.Itoa(page) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(path, nil, projectSlug) | 	return c.Url(path, nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogThread = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)(-([^/]+))?$`) | var RegexBlogThread = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)(-([^/]+))?$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogThread(projectSlug string, threadId int, title string) string { | func (c *UrlContext) BuildBlogThread(threadId int, title string) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogThreadPath(threadId, title) | 	builder := buildBlogThreadPath(threadId, title) | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BuildBlogThreadWithPostHash(projectSlug string, threadId int, title string, postId int) string { | func (c *UrlContext) BuildBlogThreadWithPostHash(threadId int, title string, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogThreadPath(threadId, title) | 	builder := buildBlogThreadPath(threadId, title) | ||||||
| 	return ProjectUrlWithFragment(builder.String(), nil, projectSlug, strconv.Itoa(postId)) | 	return c.UrlWithFragment(builder.String(), nil, strconv.Itoa(postId)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogNewThread = regexp.MustCompile(`^/blog/new$`) | var RegexBlogNewThread = regexp.MustCompile(`^/blog/new$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogNewThread(projectSlug string) string { | func (c *UrlContext) BuildBlogNewThread() string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	return ProjectUrl("/blog/new", nil, projectSlug) | 	return c.Url("/blog/new", nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogPost = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)$`) | var RegexBlogPost = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogPost(projectSlug string, threadId int, postId int) string { | func (c *UrlContext) BuildBlogPost(threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogPostPath(threadId, postId) | 	builder := buildBlogPostPath(threadId, postId) | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogPostDelete = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/delete$`) | var RegexBlogPostDelete = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/delete$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogPostDelete(projectSlug string, threadId int, postId int) string { | func (c *UrlContext) BuildBlogPostDelete(threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogPostPath(threadId, postId) | 	builder := buildBlogPostPath(threadId, postId) | ||||||
| 	builder.WriteString("/delete") | 	builder.WriteString("/delete") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogPostEdit = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/edit$`) | var RegexBlogPostEdit = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/edit$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogPostEdit(projectSlug string, threadId int, postId int) string { | func (c *UrlContext) BuildBlogPostEdit(threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogPostPath(threadId, postId) | 	builder := buildBlogPostPath(threadId, postId) | ||||||
| 	builder.WriteString("/edit") | 	builder.WriteString("/edit") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexBlogPostReply = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/reply$`) | var RegexBlogPostReply = regexp.MustCompile(`^/blog/p/(?P<threadid>\d+)/e/(?P<postid>\d+)/reply$`) | ||||||
| 
 | 
 | ||||||
| func BuildBlogPostReply(projectSlug string, threadId int, postId int) string { | func (c *UrlContext) BuildBlogPostReply(threadId int, postId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	builder := buildBlogPostPath(threadId, postId) | 	builder := buildBlogPostPath(threadId, postId) | ||||||
| 	builder.WriteString("/reply") | 	builder.WriteString("/reply") | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -580,7 +622,7 @@ func BuildLibraryResource(resourceId int) string { | ||||||
| 
 | 
 | ||||||
| var RegexEpisodeList = regexp.MustCompile(`^/episode(/(?P<topic>[^/]+))?$`) | var RegexEpisodeList = regexp.MustCompile(`^/episode(/(?P<topic>[^/]+))?$`) | ||||||
| 
 | 
 | ||||||
| func BuildEpisodeList(projectSlug string, topic string) string { | func (c *UrlContext) BuildEpisodeList(topic string) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 
 | 
 | ||||||
| 	var builder strings.Builder | 	var builder strings.Builder | ||||||
|  | @ -589,21 +631,21 @@ func BuildEpisodeList(projectSlug string, topic string) string { | ||||||
| 		builder.WriteString("/") | 		builder.WriteString("/") | ||||||
| 		builder.WriteString(topic) | 		builder.WriteString(topic) | ||||||
| 	} | 	} | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexEpisode = regexp.MustCompile(`^/episode/(?P<topic>[^/]+)/(?P<episode>[^/]+)$`) | var RegexEpisode = regexp.MustCompile(`^/episode/(?P<topic>[^/]+)/(?P<episode>[^/]+)$`) | ||||||
| 
 | 
 | ||||||
| func BuildEpisode(projectSlug string, topic string, episode string) string { | func (c *UrlContext) BuildEpisode(topic string, episode string) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	return ProjectUrl(fmt.Sprintf("/episode/%s/%s", topic, episode), nil, projectSlug) | 	return c.Url(fmt.Sprintf("/episode/%s/%s", topic, episode), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexCineraIndex = regexp.MustCompile(`^/(?P<topic>[^/]+).index$`) | var RegexCineraIndex = regexp.MustCompile(`^/(?P<topic>[^/]+).index$`) | ||||||
| 
 | 
 | ||||||
| func BuildCineraIndex(projectSlug string, topic string) string { | func (c *UrlContext) BuildCineraIndex(topic string) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	return ProjectUrl(fmt.Sprintf("/%s.index", topic), nil, projectSlug) | 	return c.Url(fmt.Sprintf("/%s.index", topic), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -635,8 +677,8 @@ func BuildDiscordShowcaseBacklog() string { | ||||||
| var RegexAssetUpload = regexp.MustCompile("^/upload_asset$") | var RegexAssetUpload = regexp.MustCompile("^/upload_asset$") | ||||||
| 
 | 
 | ||||||
| // NOTE(asaf): Providing the projectSlug avoids any CORS problems.
 | // NOTE(asaf): Providing the projectSlug avoids any CORS problems.
 | ||||||
| func BuildAssetUpload(projectSlug string) string { | func (c *UrlContext) BuildAssetUpload() string { | ||||||
| 	return ProjectUrl("/upload_asset", nil, projectSlug) | 	return c.Url("/upload_asset", nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -715,7 +757,7 @@ func BuildUserFile(filepath string) string { | ||||||
| var RegexForumMarkRead = regexp.MustCompile(`^/markread/(?P<sfid>\d+)$`) | var RegexForumMarkRead = regexp.MustCompile(`^/markread/(?P<sfid>\d+)$`) | ||||||
| 
 | 
 | ||||||
| // NOTE(asaf): subforumId == 0 means ALL SUBFORUMS
 | // NOTE(asaf): subforumId == 0 means ALL SUBFORUMS
 | ||||||
| func BuildForumMarkRead(projectSlug string, subforumId int) string { | func (c *UrlContext) BuildForumMarkRead(subforumId int) string { | ||||||
| 	defer CatchPanic() | 	defer CatchPanic() | ||||||
| 	if subforumId < 0 { | 	if subforumId < 0 { | ||||||
| 		panic(oops.New(nil, "Invalid subforum ID (%d), must be >= 0", subforumId)) | 		panic(oops.New(nil, "Invalid subforum ID (%d), must be >= 0", subforumId)) | ||||||
|  | @ -725,7 +767,7 @@ func BuildForumMarkRead(projectSlug string, subforumId int) string { | ||||||
| 	builder.WriteString("/markread/") | 	builder.WriteString("/markread/") | ||||||
| 	builder.WriteString(strconv.Itoa(subforumId)) | 	builder.WriteString(strconv.Itoa(subforumId)) | ||||||
| 
 | 
 | ||||||
| 	return ProjectUrl(builder.String(), nil, projectSlug) | 	return c.Url(builder.String(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegexCatchAll = regexp.MustCompile("^") | var RegexCatchAll = regexp.MustCompile("^") | ||||||
|  |  | ||||||
|  | @ -59,22 +59,11 @@ var LifecycleBadgeStrings = map[models.ProjectLifecycle]string{ | ||||||
| 	models.ProjectLifecycleLTS:              "Complete", | 	models.ProjectLifecycleLTS:              "Complete", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func ProjectUrl(p *models.Project) string { | func ProjectToTemplate(p *models.Project, url string, theme string) Project { | ||||||
| 	var url string |  | ||||||
| 	if p.Personal { |  | ||||||
| 		url = hmnurl.BuildPersonalProject(p.ID, models.GeneratePersonalProjectSlug(p.Name)) |  | ||||||
| 	} else { |  | ||||||
| 		url = hmnurl.BuildOfficialProjectHomepage(p.Slug) |  | ||||||
| 	} |  | ||||||
| 	return url |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ProjectToTemplate(p *models.Project, theme string) Project { |  | ||||||
| 	logo := p.LogoLight | 	logo := p.LogoLight | ||||||
| 	if theme == "dark" { | 	if theme == "dark" { | ||||||
| 		logo = p.LogoDark | 		logo = p.LogoDark | ||||||
| 	} | 	} | ||||||
| 	url := ProjectUrl(p) |  | ||||||
| 	return Project{ | 	return Project{ | ||||||
| 		Name:              p.Name, | 		Name:              p.Name, | ||||||
| 		Subdomain:         p.Subdomain(), | 		Subdomain:         p.Subdomain(), | ||||||
|  | @ -93,7 +82,6 @@ func ProjectToTemplate(p *models.Project, theme string) Project { | ||||||
| 
 | 
 | ||||||
| 		HasBlog:  p.BlogEnabled, | 		HasBlog:  p.BlogEnabled, | ||||||
| 		HasForum: p.ForumEnabled, | 		HasForum: p.ForumEnabled, | ||||||
| 		HasLibrary: false, // TODO: port the library lol
 |  | ||||||
| 
 | 
 | ||||||
| 		DateApproved: p.DateApproved, | 		DateApproved: p.DateApproved, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -128,7 +128,6 @@ type Project struct { | ||||||
| 
 | 
 | ||||||
| 	HasBlog  bool | 	HasBlog  bool | ||||||
| 	HasForum bool | 	HasForum bool | ||||||
| 	HasLibrary bool |  | ||||||
| 
 | 
 | ||||||
| 	UUID         string | 	UUID         string | ||||||
| 	DateApproved time.Time | 	DateApproved time.Time | ||||||
|  |  | ||||||
|  | @ -137,7 +137,7 @@ func AdminApprovalQueue(c *RequestContext) ResponseData { | ||||||
| 	for _, p := range posts { | 	for _, p := range posts { | ||||||
| 		post := templates.PostToTemplate(&p.Post, &p.Author, c.Theme) | 		post := templates.PostToTemplate(&p.Post, &p.Author, c.Theme) | ||||||
| 		post.AddContentVersion(p.CurrentVersion, &p.Author) // NOTE(asaf): Don't care about editors here
 | 		post.AddContentVersion(p.CurrentVersion, &p.Author) // NOTE(asaf): Don't care about editors here
 | ||||||
| 		post.Url = UrlForGenericPost(&p.Thread, &p.Post, lineageBuilder, p.Project.Slug) | 		post.Url = UrlForGenericPost(UrlContextForProject(&p.Project), &p.Thread, &p.Post, lineageBuilder) | ||||||
| 		data.Posts = append(data.Posts, postWithTitle{ | 		data.Posts = append(data.Posts, postWithTitle{ | ||||||
| 			Post:  post, | 			Post:  post, | ||||||
| 			Title: p.Thread.Title, | 			Title: p.Thread.Title, | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc | ||||||
| 	notices := getNoticesFromCookie(c) | 	notices := getNoticesFromCookie(c) | ||||||
| 
 | 
 | ||||||
| 	if len(breadcrumbs) > 0 { | 	if len(breadcrumbs) > 0 { | ||||||
| 		projectUrl := UrlForProject(c.CurrentProject) | 		projectUrl := c.UrlContext.BuildHomepage() | ||||||
| 		if breadcrumbs[0].Url != projectUrl { | 		if breadcrumbs[0].Url != projectUrl { | ||||||
| 			rootBreadcrumb := templates.Breadcrumb{ | 			rootBreadcrumb := templates.Breadcrumb{ | ||||||
| 				Name: c.CurrentProject.Name, | 				Name: c.CurrentProject.Name, | ||||||
|  | @ -42,11 +42,11 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc | ||||||
| 		Breadcrumbs: breadcrumbs, | 		Breadcrumbs: breadcrumbs, | ||||||
| 
 | 
 | ||||||
| 		CurrentUrl:        c.FullUrl(), | 		CurrentUrl:        c.FullUrl(), | ||||||
| 		CurrentProjectUrl: UrlForProject(c.CurrentProject), | 		CurrentProjectUrl: c.UrlContext.BuildHomepage(), | ||||||
| 		LoginPageUrl:      hmnurl.BuildLoginPage(c.FullUrl()), | 		LoginPageUrl:      hmnurl.BuildLoginPage(c.FullUrl()), | ||||||
| 		ProjectCSSUrl:     hmnurl.BuildProjectCSS(c.CurrentProject.Color1), | 		ProjectCSSUrl:     hmnurl.BuildProjectCSS(c.CurrentProject.Color1), | ||||||
| 
 | 
 | ||||||
| 		Project: templates.ProjectToTemplate(c.CurrentProject, c.Theme), | 		Project: templates.ProjectToTemplate(c.CurrentProject, c.UrlContext.BuildHomepage(), c.Theme), | ||||||
| 		User:    templateUser, | 		User:    templateUser, | ||||||
| 		Session: templateSession, | 		Session: templateSession, | ||||||
| 		Notices: notices, | 		Notices: notices, | ||||||
|  | @ -67,7 +67,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc | ||||||
| 			HMNHomepageUrl:  hmnurl.BuildHomepage(), | 			HMNHomepageUrl:  hmnurl.BuildHomepage(), | ||||||
| 			ProjectIndexUrl: hmnurl.BuildProjectIndex(1), | 			ProjectIndexUrl: hmnurl.BuildProjectIndex(1), | ||||||
| 			PodcastUrl:      hmnurl.BuildPodcast(), | 			PodcastUrl:      hmnurl.BuildPodcast(), | ||||||
| 			ForumsUrl:       hmnurl.BuildForum(models.HMNProjectSlug, nil, 1), | 			ForumsUrl:       hmnurl.HMNProjectContext.BuildForum(nil, 1), | ||||||
| 			LibraryUrl:      hmnurl.BuildLibrary(), | 			LibraryUrl:      hmnurl.BuildLibrary(), | ||||||
| 		}, | 		}, | ||||||
| 		Footer: templates.Footer{ | 		Footer: templates.Footer{ | ||||||
|  | @ -77,7 +77,7 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc | ||||||
| 			CodeOfConductUrl:           hmnurl.BuildCodeOfConduct(), | 			CodeOfConductUrl:           hmnurl.BuildCodeOfConduct(), | ||||||
| 			CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(), | 			CommunicationGuidelinesUrl: hmnurl.BuildCommunicationGuidelines(), | ||||||
| 			ProjectIndexUrl:            hmnurl.BuildProjectIndex(1), | 			ProjectIndexUrl:            hmnurl.BuildProjectIndex(1), | ||||||
| 			ForumsUrl:                  hmnurl.BuildForum(models.HMNProjectSlug, nil, 1), | 			ForumsUrl:                  hmnurl.HMNProjectContext.BuildForum(nil, 1), | ||||||
| 			ContactUrl:                 hmnurl.BuildContactPage(), | 			ContactUrl:                 hmnurl.BuildContactPage(), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | @ -90,15 +90,15 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc | ||||||
| 		episodeGuideUrl := "" | 		episodeGuideUrl := "" | ||||||
| 		defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug] | 		defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug] | ||||||
| 		if hasAnnotations { | 		if hasAnnotations { | ||||||
| 			episodeGuideUrl = hmnurl.BuildEpisodeList(c.CurrentProject.Slug, defaultTopic) | 			episodeGuideUrl = c.UrlContext.BuildEpisodeList(defaultTopic) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		baseData.Header.Project = &templates.ProjectHeader{ | 		baseData.Header.Project = &templates.ProjectHeader{ | ||||||
| 			HasForums:       c.CurrentProject.ForumEnabled, | 			HasForums:       c.CurrentProject.ForumEnabled, | ||||||
| 			HasBlog:         c.CurrentProject.BlogEnabled, | 			HasBlog:         c.CurrentProject.BlogEnabled, | ||||||
| 			HasEpisodeGuide: hasAnnotations, | 			HasEpisodeGuide: hasAnnotations, | ||||||
| 			ForumsUrl:       hmnurl.BuildForum(c.CurrentProject.Slug, nil, 1), | 			ForumsUrl:       c.UrlContext.BuildForum(nil, 1), | ||||||
| 			BlogUrl:         hmnurl.BuildBlog(c.CurrentProject.Slug, 1), | 			BlogUrl:         c.UrlContext.BuildBlog(1), | ||||||
| 			EpisodeGuideUrl: episodeGuideUrl, | 			EpisodeGuideUrl: episodeGuideUrl, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ func BlogIndex(c *RequestContext) ResponseData { | ||||||
| 	numPages := utils.NumPages(numPosts, postsPerPage) | 	numPages := utils.NumPages(numPosts, postsPerPage) | ||||||
| 	page, ok := ParsePageNumber(c, "page", numPages) | 	page, ok := ParsePageNumber(c, "page", numPages) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		c.Redirect(hmnurl.BuildBlog(c.CurrentProject.Slug, page), http.StatusSeeOther) | 		c.Redirect(c.UrlContext.BuildBlog(page), http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	threads, err := FetchThreads(c.Context(), c.Conn, c.CurrentUser, ThreadsQuery{ | 	threads, err := FetchThreads(c.Context(), c.Conn, c.CurrentUser, ThreadsQuery{ | ||||||
|  | @ -63,14 +63,14 @@ func BlogIndex(c *RequestContext) ResponseData { | ||||||
| 	for _, thread := range threads { | 	for _, thread := range threads { | ||||||
| 		entries = append(entries, blogIndexEntry{ | 		entries = append(entries, blogIndexEntry{ | ||||||
| 			Title:   thread.Thread.Title, | 			Title:   thread.Thread.Title, | ||||||
| 			Url:     hmnurl.BuildBlogThread(c.CurrentProject.Slug, thread.Thread.ID, thread.Thread.Title), | 			Url:     c.UrlContext.BuildBlogThread(thread.Thread.ID, thread.Thread.Title), | ||||||
| 			Author:  templates.UserToTemplate(thread.FirstPostAuthor, c.Theme), | 			Author:  templates.UserToTemplate(thread.FirstPostAuthor, c.Theme), | ||||||
| 			Date:    thread.FirstPost.PostDate, | 			Date:    thread.FirstPost.PostDate, | ||||||
| 			Content: template.HTML(thread.FirstPostCurrentVersion.TextParsed), | 			Content: template.HTML(thread.FirstPostCurrentVersion.TextParsed), | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	baseData := getBaseData(c, fmt.Sprintf("%s Blog", c.CurrentProject.Name), []templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)}) | 	baseData := getBaseData(c, fmt.Sprintf("%s Blog", c.CurrentProject.Name), []templates.Breadcrumb{BlogBreadcrumb(c.UrlContext)}) | ||||||
| 
 | 
 | ||||||
| 	canCreate := false | 	canCreate := false | ||||||
| 	if c.CurrentUser != nil { | 	if c.CurrentUser != nil { | ||||||
|  | @ -97,14 +97,14 @@ func BlogIndex(c *RequestContext) ResponseData { | ||||||
| 			Current: page, | 			Current: page, | ||||||
| 			Total:   numPages, | 			Total:   numPages, | ||||||
| 
 | 
 | ||||||
| 			FirstUrl:    hmnurl.BuildBlog(c.CurrentProject.Slug, 1), | 			FirstUrl:    c.UrlContext.BuildBlog(1), | ||||||
| 			LastUrl:     hmnurl.BuildBlog(c.CurrentProject.Slug, numPages), | 			LastUrl:     c.UrlContext.BuildBlog(numPages), | ||||||
| 			PreviousUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page-1, numPages)), | 			PreviousUrl: c.UrlContext.BuildBlog(utils.IntClamp(1, page-1, numPages)), | ||||||
| 			NextUrl:     hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page+1, numPages)), | 			NextUrl:     c.UrlContext.BuildBlog(utils.IntClamp(1, page+1, numPages)), | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		CanCreatePost: canCreate, | 		CanCreatePost: canCreate, | ||||||
| 		NewPostUrl:    hmnurl.BuildBlogNewThread(c.CurrentProject.Slug), | 		NewPostUrl:    c.UrlContext.BuildBlogNewThread(), | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
| } | } | ||||||
|  | @ -138,11 +138,11 @@ func BlogThread(c *RequestContext) ResponseData { | ||||||
| 	for _, p := range posts { | 	for _, p := range posts { | ||||||
| 		post := templates.PostToTemplate(&p.Post, p.Author, c.Theme) | 		post := templates.PostToTemplate(&p.Post, p.Author, c.Theme) | ||||||
| 		post.AddContentVersion(p.CurrentVersion, p.Editor) | 		post.AddContentVersion(p.CurrentVersion, p.Editor) | ||||||
| 		addBlogUrlsToPost(&post, c.CurrentProject.Slug, &p.Thread, p.Post.ID) | 		addBlogUrlsToPost(c.UrlContext, &post, &p.Thread, p.Post.ID) | ||||||
| 
 | 
 | ||||||
| 		if p.ReplyPost != nil { | 		if p.ReplyPost != nil { | ||||||
| 			reply := templates.PostToTemplate(p.ReplyPost, p.ReplyAuthor, c.Theme) | 			reply := templates.PostToTemplate(p.ReplyPost, p.ReplyAuthor, c.Theme) | ||||||
| 			addBlogUrlsToPost(&reply, c.CurrentProject.Slug, &p.Thread, p.Post.ID) | 			addBlogUrlsToPost(c.UrlContext, &reply, &p.Thread, p.Post.ID) | ||||||
| 			post.ReplyPost = &reply | 			post.ReplyPost = &reply | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -168,7 +168,7 @@ func BlogThread(c *RequestContext) ResponseData { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	baseData := getBaseData(c, thread.Title, []templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)}) | 	baseData := getBaseData(c, thread.Title, []templates.Breadcrumb{BlogBreadcrumb(c.UrlContext)}) | ||||||
| 	baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{ | 	baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{ | ||||||
| 		Property: "og:description", | 		Property: "og:description", | ||||||
| 		Value:    posts[0].Post.Preview, | 		Value:    posts[0].Post.Preview, | ||||||
|  | @ -180,7 +180,7 @@ func BlogThread(c *RequestContext) ResponseData { | ||||||
| 		Thread:    templates.ThreadToTemplate(&thread), | 		Thread:    templates.ThreadToTemplate(&thread), | ||||||
| 		MainPost:  templatePosts[0], | 		MainPost:  templatePosts[0], | ||||||
| 		Comments:  templatePosts[1:], | 		Comments:  templatePosts[1:], | ||||||
| 		ReplyLink: hmnurl.BuildBlogPostReply(c.CurrentProject.Slug, cd.ThreadID, posts[0].Post.ID), | 		ReplyLink: c.UrlContext.BuildBlogPostReply(cd.ThreadID, posts[0].Post.ID), | ||||||
| 		LoginLink: hmnurl.BuildLoginPage(c.FullUrl()), | 		LoginLink: hmnurl.BuildLoginPage(c.FullUrl()), | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
|  | @ -202,7 +202,7 @@ func BlogPostRedirectToThread(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch thread for blog redirect")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch thread for blog redirect")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	threadUrl := hmnurl.BuildBlogThreadWithPostHash(c.CurrentProject.Slug, cd.ThreadID, thread.Thread.Title, cd.PostID) | 	threadUrl := c.UrlContext.BuildBlogThreadWithPostHash(cd.ThreadID, thread.Thread.Title, cd.PostID) | ||||||
| 	return c.Redirect(threadUrl, http.StatusFound) | 	return c.Redirect(threadUrl, http.StatusFound) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -210,11 +210,11 @@ func BlogNewThread(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		fmt.Sprintf("Create New Post | %s", c.CurrentProject.Name), | 		fmt.Sprintf("Create New Post | %s", c.CurrentProject.Name), | ||||||
| 		[]templates.Breadcrumb{BlogBreadcrumb(c.CurrentProject.Slug)}, | 		[]templates.Breadcrumb{BlogBreadcrumb(c.UrlContext)}, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	editData := getEditorDataForNew(c.CurrentUser, baseData, nil) | 	editData := getEditorDataForNew(c.UrlContext, c.CurrentUser, baseData, nil) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildBlogNewThread(c.CurrentProject.Slug) | 	editData.SubmitUrl = c.UrlContext.BuildBlogNewThread() | ||||||
| 	editData.SubmitLabel = "Create Post" | 	editData.SubmitLabel = "Create Post" | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
|  | @ -268,7 +268,7 @@ func BlogNewThreadSubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new blog post")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new blog post")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newThreadUrl := hmnurl.BuildBlogThread(c.CurrentProject.Slug, threadId, title) | 	newThreadUrl := c.UrlContext.BuildBlogThread(threadId, title) | ||||||
| 	return c.Redirect(newThreadUrl, http.StatusSeeOther) | 	return c.Redirect(newThreadUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -301,11 +301,11 @@ func BlogPostEdit(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		title, | 		title, | ||||||
| 		BlogThreadBreadcrumbs(c.CurrentProject.Slug, &post.Thread), | 		BlogThreadBreadcrumbs(c.UrlContext, &post.Thread), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	editData := getEditorDataForEdit(c.CurrentUser, baseData, post) | 	editData := getEditorDataForEdit(c.UrlContext, c.CurrentUser, baseData, post) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildBlogPostEdit(c.CurrentProject.Slug, cd.ThreadID, cd.PostID) | 	editData.SubmitUrl = c.UrlContext.BuildBlogPostEdit(cd.ThreadID, cd.PostID) | ||||||
| 	editData.SubmitLabel = "Submit Edited Post" | 	editData.SubmitLabel = "Submit Edited Post" | ||||||
| 	if post.Thread.FirstID != post.Post.ID { | 	if post.Thread.FirstID != post.Post.ID { | ||||||
| 		editData.SubmitLabel = "Submit Edited Comment" | 		editData.SubmitLabel = "Submit Edited Comment" | ||||||
|  | @ -373,7 +373,7 @@ func BlogPostEditSubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to edit blog post")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to edit blog post")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	postUrl := hmnurl.BuildBlogThreadWithPostHash(c.CurrentProject.Slug, cd.ThreadID, post.Thread.Title, cd.PostID) | 	postUrl := c.UrlContext.BuildBlogThreadWithPostHash(cd.ThreadID, post.Thread.Title, cd.PostID) | ||||||
| 	return c.Redirect(postUrl, http.StatusSeeOther) | 	return c.Redirect(postUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -396,14 +396,14 @@ func BlogPostReply(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		fmt.Sprintf("Replying to comment in \"%s\" | %s", post.Thread.Title, c.CurrentProject.Name), | 		fmt.Sprintf("Replying to comment in \"%s\" | %s", post.Thread.Title, c.CurrentProject.Name), | ||||||
| 		BlogThreadBreadcrumbs(c.CurrentProject.Slug, &post.Thread), | 		BlogThreadBreadcrumbs(c.UrlContext, &post.Thread), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	replyPost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | 	replyPost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | ||||||
| 	replyPost.AddContentVersion(post.CurrentVersion, post.Editor) | 	replyPost.AddContentVersion(post.CurrentVersion, post.Editor) | ||||||
| 
 | 
 | ||||||
| 	editData := getEditorDataForNew(c.CurrentUser, baseData, &replyPost) | 	editData := getEditorDataForNew(c.UrlContext, c.CurrentUser, baseData, &replyPost) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildBlogPostReply(c.CurrentProject.Slug, cd.ThreadID, cd.PostID) | 	editData.SubmitUrl = c.UrlContext.BuildBlogPostReply(cd.ThreadID, cd.PostID) | ||||||
| 	editData.SubmitLabel = "Submit Reply" | 	editData.SubmitLabel = "Submit Reply" | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
|  | @ -439,7 +439,7 @@ func BlogPostReplySubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to reply to blog post")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to reply to blog post")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newPostUrl := hmnurl.BuildBlogPost(c.CurrentProject.Slug, cd.ThreadID, newPostId) | 	newPostUrl := c.UrlContext.BuildBlogPost(cd.ThreadID, newPostId) | ||||||
| 	return c.Redirect(newPostUrl, http.StatusSeeOther) | 	return c.Redirect(newPostUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -472,7 +472,7 @@ func BlogPostDelete(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		title, | 		title, | ||||||
| 		BlogThreadBreadcrumbs(c.CurrentProject.Slug, &post.Thread), | 		BlogThreadBreadcrumbs(c.UrlContext, &post.Thread), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	templatePost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | 	templatePost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | ||||||
|  | @ -487,7 +487,7 @@ func BlogPostDelete(c *RequestContext) ResponseData { | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
| 	res.MustWriteTemplate("blog_post_delete.html", blogPostDeleteData{ | 	res.MustWriteTemplate("blog_post_delete.html", blogPostDeleteData{ | ||||||
| 		BaseData:  baseData, | 		BaseData:  baseData, | ||||||
| 		SubmitUrl: hmnurl.BuildBlogPostDelete(c.CurrentProject.Slug, cd.ThreadID, cd.PostID), | 		SubmitUrl: c.UrlContext.BuildBlogPostDelete(cd.ThreadID, cd.PostID), | ||||||
| 		Post:      templatePost, | 		Post:      templatePost, | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
|  | @ -517,8 +517,7 @@ func BlogPostDeleteSubmit(c *RequestContext) ResponseData { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if threadDeleted { | 	if threadDeleted { | ||||||
| 		projectUrl := UrlForProject(c.CurrentProject) | 		return c.Redirect(c.UrlContext.BuildHomepage(), http.StatusSeeOther) | ||||||
| 		return c.Redirect(projectUrl, http.StatusSeeOther) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		thread, err := FetchThread(c.Context(), c.Conn, c.CurrentUser, cd.ThreadID, ThreadsQuery{ | 		thread, err := FetchThread(c.Context(), c.Conn, c.CurrentUser, cd.ThreadID, ThreadsQuery{ | ||||||
| 			ProjectIDs:  []int{c.CurrentProject.ID}, | 			ProjectIDs:  []int{c.CurrentProject.ID}, | ||||||
|  | @ -529,7 +528,7 @@ func BlogPostDeleteSubmit(c *RequestContext) ResponseData { | ||||||
| 		} else if err != nil { | 		} else if err != nil { | ||||||
| 			return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch thread after blog post delete")) | 			return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch thread after blog post delete")) | ||||||
| 		} | 		} | ||||||
| 		threadUrl := hmnurl.BuildBlogThread(c.CurrentProject.Slug, thread.Thread.ID, thread.Thread.Title) | 		threadUrl := c.UrlContext.BuildBlogThread(thread.Thread.ID, thread.Thread.Title) | ||||||
| 		return c.Redirect(threadUrl, http.StatusSeeOther) | 		return c.Redirect(threadUrl, http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -608,9 +607,9 @@ func getCommonBlogData(c *RequestContext) (commonBlogData, bool) { | ||||||
| 	return res, true | 	return res, true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func addBlogUrlsToPost(p *templates.Post, projectSlug string, thread *models.Thread, postId int) { | func addBlogUrlsToPost(urlContext *hmnurl.UrlContext, p *templates.Post, thread *models.Thread, postId int) { | ||||||
| 	p.Url = hmnurl.BuildBlogThreadWithPostHash(projectSlug, thread.ID, thread.Title, postId) | 	p.Url = urlContext.BuildBlogThreadWithPostHash(thread.ID, thread.Title, postId) | ||||||
| 	p.DeleteUrl = hmnurl.BuildBlogPostDelete(projectSlug, thread.ID, postId) | 	p.DeleteUrl = urlContext.BuildBlogPostDelete(thread.ID, postId) | ||||||
| 	p.EditUrl = hmnurl.BuildBlogPostEdit(projectSlug, thread.ID, postId) | 	p.EditUrl = urlContext.BuildBlogPostEdit(thread.ID, postId) | ||||||
| 	p.ReplyUrl = hmnurl.BuildBlogPostReply(projectSlug, thread.ID, postId) | 	p.ReplyUrl = urlContext.BuildBlogPostReply(thread.ID, postId) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,58 +6,58 @@ import ( | ||||||
| 	"git.handmade.network/hmn/hmn/src/templates" | 	"git.handmade.network/hmn/hmn/src/templates" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func ProjectBreadcrumb(project *models.Project) templates.Breadcrumb { | func ProjectBreadcrumb(projectUrlContext *hmnurl.UrlContext) templates.Breadcrumb { | ||||||
| 	return templates.Breadcrumb{ | 	return templates.Breadcrumb{ | ||||||
| 		Name: project.Name, | 		Name: projectUrlContext.ProjectName, | ||||||
| 		Url:  UrlForProject(project), | 		Url:  projectUrlContext.BuildHomepage(), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func ForumBreadcrumb(projectSlug string) templates.Breadcrumb { | func ForumBreadcrumb(projectUrlContext *hmnurl.UrlContext) templates.Breadcrumb { | ||||||
| 	return templates.Breadcrumb{ | 	return templates.Breadcrumb{ | ||||||
| 		Name: "Forums", | 		Name: "Forums", | ||||||
| 		Url:  hmnurl.BuildForum(projectSlug, nil, 1), | 		Url:  projectUrlContext.BuildForum(nil, 1), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func SubforumBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, subforumID int) []templates.Breadcrumb { | func SubforumBreadcrumbs(projectUrlContext *hmnurl.UrlContext, lineageBuilder *models.SubforumLineageBuilder, subforumID int) []templates.Breadcrumb { | ||||||
| 	var result []templates.Breadcrumb | 	var result []templates.Breadcrumb | ||||||
| 	result = []templates.Breadcrumb{ | 	result = []templates.Breadcrumb{ | ||||||
| 		ProjectBreadcrumb(project), | 		ProjectBreadcrumb(projectUrlContext), | ||||||
| 		ForumBreadcrumb(project.Slug), | 		ForumBreadcrumb(projectUrlContext), | ||||||
| 	} | 	} | ||||||
| 	subforums := lineageBuilder.GetSubforumLineage(subforumID) | 	subforums := lineageBuilder.GetSubforumLineage(subforumID) | ||||||
| 	slugs := lineageBuilder.GetSubforumLineageSlugs(subforumID) | 	slugs := lineageBuilder.GetSubforumLineageSlugs(subforumID) | ||||||
| 	for i, subforum := range subforums { | 	for i, subforum := range subforums { | ||||||
| 		result = append(result, templates.Breadcrumb{ | 		result = append(result, templates.Breadcrumb{ | ||||||
| 			Name: subforum.Name, | 			Name: subforum.Name, | ||||||
| 			Url:  hmnurl.BuildForum(project.Slug, slugs[0:i+1], 1), | 			Url:  projectUrlContext.BuildForum(slugs[0:i+1], 1), | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func ForumThreadBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, thread *models.Thread) []templates.Breadcrumb { | func ForumThreadBreadcrumbs(projectUrlContext *hmnurl.UrlContext, lineageBuilder *models.SubforumLineageBuilder, thread *models.Thread) []templates.Breadcrumb { | ||||||
| 	result := SubforumBreadcrumbs(lineageBuilder, project, *thread.SubforumID) | 	result := SubforumBreadcrumbs(projectUrlContext, lineageBuilder, *thread.SubforumID) | ||||||
| 	result = append(result, templates.Breadcrumb{ | 	result = append(result, templates.Breadcrumb{ | ||||||
| 		Name: thread.Title, | 		Name: thread.Title, | ||||||
| 		Url:  hmnurl.BuildForumThread(project.Slug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1), | 		Url:  projectUrlContext.BuildForumThread(lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1), | ||||||
| 	}) | 	}) | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BlogBreadcrumb(projectSlug string) templates.Breadcrumb { | func BlogBreadcrumb(projectUrlContext *hmnurl.UrlContext) templates.Breadcrumb { | ||||||
| 	return templates.Breadcrumb{ | 	return templates.Breadcrumb{ | ||||||
| 		Name: "Blog", | 		Name: "Blog", | ||||||
| 		Url:  hmnurl.BuildBlog(projectSlug, 1), | 		Url:  projectUrlContext.BuildBlog(1), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BlogThreadBreadcrumbs(projectSlug string, thread *models.Thread) []templates.Breadcrumb { | func BlogThreadBreadcrumbs(projectUrlContext *hmnurl.UrlContext, thread *models.Thread) []templates.Breadcrumb { | ||||||
| 	result := []templates.Breadcrumb{ | 	result := []templates.Breadcrumb{ | ||||||
| 		BlogBreadcrumb(projectSlug), | 		BlogBreadcrumb(projectUrlContext), | ||||||
| 		{Name: thread.Title, Url: hmnurl.BuildBlogThread(projectSlug, thread.ID, thread.Title)}, | 		{Name: thread.Title, Url: projectUrlContext.BuildBlogThread(thread.ID, thread.Title)}, | ||||||
| 	} | 	} | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"git.handmade.network/hmn/hmn/src/config" | 	"git.handmade.network/hmn/hmn/src/config" | ||||||
| 	"git.handmade.network/hmn/hmn/src/hmnurl" |  | ||||||
| 	"git.handmade.network/hmn/hmn/src/templates" | 	"git.handmade.network/hmn/hmn/src/templates" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -53,11 +52,11 @@ func EpisodeList(c *RequestContext) ResponseData { | ||||||
| 	defaultTopic, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug] | 	defaultTopic, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug] | ||||||
| 
 | 
 | ||||||
| 	if !hasEpisodeGuide { | 	if !hasEpisodeGuide { | ||||||
| 		return c.Redirect(UrlForProject(c.CurrentProject), http.StatusSeeOther) | 		return c.Redirect(c.UrlContext.BuildHomepage(), http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if topic == "" { | 	if topic == "" { | ||||||
| 		return c.Redirect(hmnurl.BuildEpisodeList(slug, defaultTopic), http.StatusSeeOther) | 		return c.Redirect(c.UrlContext.BuildEpisodeList(defaultTopic), http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	allTopics, foundTopic := topicsForProject(slug, topic) | 	allTopics, foundTopic := topicsForProject(slug, topic) | ||||||
|  | @ -82,7 +81,7 @@ func EpisodeList(c *RequestContext) ResponseData { | ||||||
| 	for _, t := range allTopics { | 	for _, t := range allTopics { | ||||||
| 		url := "" | 		url := "" | ||||||
| 		if t != foundTopic { | 		if t != foundTopic { | ||||||
| 			url = hmnurl.BuildEpisodeList(slug, t) | 			url = c.UrlContext.BuildEpisodeList(t) | ||||||
| 		} | 		} | ||||||
| 		topicLinks = append(topicLinks, templates.Link{LinkText: t, Url: url}) | 		topicLinks = append(topicLinks, templates.Link{LinkText: t, Url: url}) | ||||||
| 	} | 	} | ||||||
|  | @ -114,7 +113,7 @@ func Episode(c *RequestContext) ResponseData { | ||||||
| 	_, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug] | 	_, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug] | ||||||
| 
 | 
 | ||||||
| 	if !hasEpisodeGuide { | 	if !hasEpisodeGuide { | ||||||
| 		return c.Redirect(UrlForProject(c.CurrentProject), http.StatusSeeOther) | 		return c.Redirect(c.UrlContext.BuildHomepage(), http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, foundTopic := topicsForProject(slug, topic) | 	_, foundTopic := topicsForProject(slug, topic) | ||||||
|  | @ -150,7 +149,7 @@ func Episode(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		title, | 		title, | ||||||
| 		[]templates.Breadcrumb{{Name: "Episode Guide", Url: hmnurl.BuildEpisodeList(c.CurrentProject.Slug, foundTopic)}}, | 		[]templates.Breadcrumb{{Name: "Episode Guide", Url: c.UrlContext.BuildEpisodeList(foundTopic)}}, | ||||||
| 	) | 	) | ||||||
| 	res.MustWriteTemplate("episode.html", EpisodeData{ | 	res.MustWriteTemplate("episode.html", EpisodeData{ | ||||||
| 		BaseData: baseData, | 		BaseData: baseData, | ||||||
|  |  | ||||||
|  | @ -71,7 +71,7 @@ func Feed(c *RequestContext) ResponseData { | ||||||
| 		BaseData: baseData, | 		BaseData: baseData, | ||||||
| 
 | 
 | ||||||
| 		AtomFeedUrl:    hmnurl.BuildAtomFeed(), | 		AtomFeedUrl:    hmnurl.BuildAtomFeed(), | ||||||
| 		MarkAllReadUrl: hmnurl.BuildForumMarkRead(c.CurrentProject.Slug, 0), | 		MarkAllReadUrl: c.UrlContext.BuildForumMarkRead(0), | ||||||
| 		Posts:          posts, | 		Posts:          posts, | ||||||
| 		Pagination:     pagination, | 		Pagination:     pagination, | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
|  | @ -181,7 +181,7 @@ func AtomFeed(c *RequestContext) ResponseData { | ||||||
| 			projectMap := make(map[int]int) // map[project id]index in slice
 | 			projectMap := make(map[int]int) // map[project id]index in slice
 | ||||||
| 			for _, p := range projects.ToSlice() { | 			for _, p := range projects.ToSlice() { | ||||||
| 				project := p.(*projectResult).Project | 				project := p.(*projectResult).Project | ||||||
| 				templateProject := templates.ProjectToTemplate(&project, c.Theme) | 				templateProject := templates.ProjectToTemplate(&project, UrlContextForProject(&project).BuildHomepage(), c.Theme) | ||||||
| 				templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN() | 				templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN() | ||||||
| 
 | 
 | ||||||
| 				projectIds = append(projectIds, project.ID) | 				projectIds = append(projectIds, project.ID) | ||||||
|  |  | ||||||
|  | @ -35,8 +35,6 @@ type forumSubforumData struct { | ||||||
| 	TotalThreads int | 	TotalThreads int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type editActionType string |  | ||||||
| 
 |  | ||||||
| type editorData struct { | type editorData struct { | ||||||
| 	templates.BaseData | 	templates.BaseData | ||||||
| 	SubmitUrl   string | 	SubmitUrl   string | ||||||
|  | @ -54,13 +52,13 @@ type editorData struct { | ||||||
| 	UploadUrl   string | 	UploadUrl   string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getEditorDataForNew(currentUser *models.User, baseData templates.BaseData, replyPost *templates.Post) editorData { | func getEditorDataForNew(urlContext *hmnurl.UrlContext, currentUser *models.User, baseData templates.BaseData, replyPost *templates.Post) editorData { | ||||||
| 	result := editorData{ | 	result := editorData{ | ||||||
| 		BaseData:       baseData, | 		BaseData:       baseData, | ||||||
| 		CanEditTitle:   replyPost == nil, | 		CanEditTitle:   replyPost == nil, | ||||||
| 		PostReplyingTo: replyPost, | 		PostReplyingTo: replyPost, | ||||||
| 		MaxFileSize:    AssetMaxSize(currentUser), | 		MaxFileSize:    AssetMaxSize(currentUser), | ||||||
| 		UploadUrl:      hmnurl.BuildAssetUpload(baseData.Project.Subdomain), | 		UploadUrl:      urlContext.BuildAssetUpload(), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if replyPost != nil { | 	if replyPost != nil { | ||||||
|  | @ -70,7 +68,7 @@ func getEditorDataForNew(currentUser *models.User, baseData templates.BaseData, | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getEditorDataForEdit(currentUser *models.User, baseData templates.BaseData, p PostAndStuff) editorData { | func getEditorDataForEdit(urlContext *hmnurl.UrlContext, currentUser *models.User, baseData templates.BaseData, p PostAndStuff) editorData { | ||||||
| 	return editorData{ | 	return editorData{ | ||||||
| 		BaseData:            baseData, | 		BaseData:            baseData, | ||||||
| 		Title:               p.Thread.Title, | 		Title:               p.Thread.Title, | ||||||
|  | @ -78,7 +76,7 @@ func getEditorDataForEdit(currentUser *models.User, baseData templates.BaseData, | ||||||
| 		IsEditing:           true, | 		IsEditing:           true, | ||||||
| 		EditInitialContents: p.CurrentVersion.TextRaw, | 		EditInitialContents: p.CurrentVersion.TextRaw, | ||||||
| 		MaxFileSize:         AssetMaxSize(currentUser), | 		MaxFileSize:         AssetMaxSize(currentUser), | ||||||
| 		UploadUrl:           hmnurl.BuildAssetUpload(baseData.Project.Subdomain), | 		UploadUrl:           urlContext.BuildAssetUpload(), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -104,7 +102,7 @@ func Forum(c *RequestContext) ResponseData { | ||||||
| 	numPages := utils.NumPages(numThreads, threadsPerPage) | 	numPages := utils.NumPages(numThreads, threadsPerPage) | ||||||
| 	page, ok := ParsePageNumber(c, "page", numPages) | 	page, ok := ParsePageNumber(c, "page", numPages) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		c.Redirect(hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, page), http.StatusSeeOther) | 		c.Redirect(c.UrlContext.BuildForum(currentSubforumSlugs, page), http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 	howManyThreadsToSkip := (page - 1) * threadsPerPage | 	howManyThreadsToSkip := (page - 1) * threadsPerPage | ||||||
| 
 | 
 | ||||||
|  | @ -119,7 +117,7 @@ func Forum(c *RequestContext) ResponseData { | ||||||
| 	makeThreadListItem := func(row ThreadAndStuff) templates.ThreadListItem { | 	makeThreadListItem := func(row ThreadAndStuff) templates.ThreadListItem { | ||||||
| 		return templates.ThreadListItem{ | 		return templates.ThreadListItem{ | ||||||
| 			Title:     row.Thread.Title, | 			Title:     row.Thread.Title, | ||||||
| 			Url:       hmnurl.BuildForumThread(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(*row.Thread.SubforumID), row.Thread.ID, row.Thread.Title, 1), | 			Url:       c.UrlContext.BuildForumThread(cd.LineageBuilder.GetSubforumLineageSlugs(*row.Thread.SubforumID), row.Thread.ID, row.Thread.Title, 1), | ||||||
| 			FirstUser: templates.UserToTemplate(row.FirstPostAuthor, c.Theme), | 			FirstUser: templates.UserToTemplate(row.FirstPostAuthor, c.Theme), | ||||||
| 			FirstDate: row.FirstPost.PostDate, | 			FirstDate: row.FirstPost.PostDate, | ||||||
| 			LastUser:  templates.UserToTemplate(row.LastPostAuthor, c.Theme), | 			LastUser:  templates.UserToTemplate(row.LastPostAuthor, c.Theme), | ||||||
|  | @ -165,7 +163,7 @@ func Forum(c *RequestContext) ResponseData { | ||||||
| 
 | 
 | ||||||
| 			subforums = append(subforums, forumSubforumData{ | 			subforums = append(subforums, forumSubforumData{ | ||||||
| 				Name:         sfNode.Name, | 				Name:         sfNode.Name, | ||||||
| 				Url:          hmnurl.BuildForum(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(sfNode.ID), 1), | 				Url:          c.UrlContext.BuildForum(cd.LineageBuilder.GetSubforumLineageSlugs(sfNode.ID), 1), | ||||||
| 				Threads:      threads, | 				Threads:      threads, | ||||||
| 				TotalThreads: numThreads, | 				TotalThreads: numThreads, | ||||||
| 			}) | 			}) | ||||||
|  | @ -179,23 +177,23 @@ func Forum(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		fmt.Sprintf("%s Forums", c.CurrentProject.Name), | 		fmt.Sprintf("%s Forums", c.CurrentProject.Name), | ||||||
| 		SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID), | 		SubforumBreadcrumbs(c.UrlContext, cd.LineageBuilder, cd.SubforumID), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
| 	res.MustWriteTemplate("forum.html", forumData{ | 	res.MustWriteTemplate("forum.html", forumData{ | ||||||
| 		BaseData:     baseData, | 		BaseData:     baseData, | ||||||
| 		NewThreadUrl: hmnurl.BuildForumNewThread(c.CurrentProject.Slug, currentSubforumSlugs, false), | 		NewThreadUrl: c.UrlContext.BuildForumNewThread(currentSubforumSlugs, false), | ||||||
| 		MarkReadUrl:  hmnurl.BuildForumMarkRead(c.CurrentProject.Slug, cd.SubforumID), | 		MarkReadUrl:  c.UrlContext.BuildForumMarkRead(cd.SubforumID), | ||||||
| 		Threads:      threads, | 		Threads:      threads, | ||||||
| 		Pagination: templates.Pagination{ | 		Pagination: templates.Pagination{ | ||||||
| 			Current: page, | 			Current: page, | ||||||
| 			Total:   numPages, | 			Total:   numPages, | ||||||
| 
 | 
 | ||||||
| 			FirstUrl:    hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, 1), | 			FirstUrl:    c.UrlContext.BuildForum(currentSubforumSlugs, 1), | ||||||
| 			LastUrl:     hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, numPages), | 			LastUrl:     c.UrlContext.BuildForum(currentSubforumSlugs, numPages), | ||||||
| 			NextUrl:     hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, utils.IntClamp(1, page+1, numPages)), | 			NextUrl:     c.UrlContext.BuildForum(currentSubforumSlugs, utils.IntClamp(1, page+1, numPages)), | ||||||
| 			PreviousUrl: hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, utils.IntClamp(1, page-1, numPages)), | 			PreviousUrl: c.UrlContext.BuildForum(currentSubforumSlugs, utils.IntClamp(1, page-1, numPages)), | ||||||
| 		}, | 		}, | ||||||
| 		Subforums: subforums, | 		Subforums: subforums, | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
|  | @ -308,7 +306,7 @@ func ForumMarkRead(c *RequestContext) ResponseData { | ||||||
| 	if sfId == 0 { | 	if sfId == 0 { | ||||||
| 		redirUrl = hmnurl.BuildFeed() | 		redirUrl = hmnurl.BuildFeed() | ||||||
| 	} else { | 	} else { | ||||||
| 		redirUrl = hmnurl.BuildForum(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(sfId), 1) | 		redirUrl = c.UrlContext.BuildForum(lineageBuilder.GetSubforumLineageSlugs(sfId), 1) | ||||||
| 	} | 	} | ||||||
| 	return c.Redirect(redirUrl, http.StatusSeeOther) | 	return c.Redirect(redirUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
|  | @ -358,17 +356,17 @@ func ForumThread(c *RequestContext) ResponseData { | ||||||
| 	} | 	} | ||||||
| 	page, numPages, ok := getPageInfo(c.PathParams["page"], numPosts, threadPostsPerPage) | 	page, numPages, ok := getPageInfo(c.PathParams["page"], numPosts, threadPostsPerPage) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		urlNoPage := hmnurl.BuildForumThread(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.Title, 1) | 		urlNoPage := c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, 1) | ||||||
| 		return c.Redirect(urlNoPage, http.StatusSeeOther) | 		return c.Redirect(urlNoPage, http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| 	pagination := templates.Pagination{ | 	pagination := templates.Pagination{ | ||||||
| 		Current: page, | 		Current: page, | ||||||
| 		Total:   numPages, | 		Total:   numPages, | ||||||
| 
 | 
 | ||||||
| 		FirstUrl:    hmnurl.BuildForumThread(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.Title, 1), | 		FirstUrl:    c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, 1), | ||||||
| 		LastUrl:     hmnurl.BuildForumThread(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.Title, numPages), | 		LastUrl:     c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, numPages), | ||||||
| 		NextUrl:     hmnurl.BuildForumThread(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page+1, numPages)), | 		NextUrl:     c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page+1, numPages)), | ||||||
| 		PreviousUrl: hmnurl.BuildForumThread(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page-1, numPages)), | 		PreviousUrl: c.UrlContext.BuildForumThread(currentSubforumSlugs, thread.ID, thread.Title, utils.IntClamp(1, page-1, numPages)), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	postsAndStuff, err := FetchPosts(c.Context(), c.Conn, c.CurrentUser, PostsQuery{ | 	postsAndStuff, err := FetchPosts(c.Context(), c.Conn, c.CurrentUser, PostsQuery{ | ||||||
|  | @ -385,11 +383,11 @@ func ForumThread(c *RequestContext) ResponseData { | ||||||
| 	for _, p := range postsAndStuff { | 	for _, p := range postsAndStuff { | ||||||
| 		post := templates.PostToTemplate(&p.Post, p.Author, c.Theme) | 		post := templates.PostToTemplate(&p.Post, p.Author, c.Theme) | ||||||
| 		post.AddContentVersion(p.CurrentVersion, p.Editor) | 		post.AddContentVersion(p.CurrentVersion, p.Editor) | ||||||
| 		addForumUrlsToPost(&post, c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, post.ID) | 		addForumUrlsToPost(c.UrlContext, &post, currentSubforumSlugs, thread.ID, post.ID) | ||||||
| 
 | 
 | ||||||
| 		if p.ReplyPost != nil { | 		if p.ReplyPost != nil { | ||||||
| 			reply := templates.PostToTemplate(p.ReplyPost, p.ReplyAuthor, c.Theme) | 			reply := templates.PostToTemplate(p.ReplyPost, p.ReplyAuthor, c.Theme) | ||||||
| 			addForumUrlsToPost(&reply, c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, reply.ID) | 			addForumUrlsToPost(c.UrlContext, &reply, currentSubforumSlugs, thread.ID, reply.ID) | ||||||
| 			post.ReplyPost = &reply | 			post.ReplyPost = &reply | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -418,7 +416,7 @@ func ForumThread(c *RequestContext) ResponseData { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	baseData := getBaseData(c, thread.Title, SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID)) | 	baseData := getBaseData(c, thread.Title, SubforumBreadcrumbs(c.UrlContext, cd.LineageBuilder, cd.SubforumID)) | ||||||
| 	baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{ | 	baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{ | ||||||
| 		Property: "og:description", | 		Property: "og:description", | ||||||
| 		Value:    threadResult.FirstPost.Preview, | 		Value:    threadResult.FirstPost.Preview, | ||||||
|  | @ -429,8 +427,8 @@ func ForumThread(c *RequestContext) ResponseData { | ||||||
| 		BaseData:    baseData, | 		BaseData:    baseData, | ||||||
| 		Thread:      templates.ThreadToTemplate(&thread), | 		Thread:      templates.ThreadToTemplate(&thread), | ||||||
| 		Posts:       posts, | 		Posts:       posts, | ||||||
| 		SubforumUrl: hmnurl.BuildForum(c.CurrentProject.Slug, currentSubforumSlugs, 1), | 		SubforumUrl: c.UrlContext.BuildForum(currentSubforumSlugs, 1), | ||||||
| 		ReplyUrl:    hmnurl.BuildForumPostReply(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, thread.FirstID), | 		ReplyUrl:    c.UrlContext.BuildForumPostReply(currentSubforumSlugs, thread.ID, thread.FirstID), | ||||||
| 		Pagination:  pagination, | 		Pagination:  pagination, | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
|  | @ -466,8 +464,7 @@ func ForumPostRedirect(c *RequestContext) ResponseData { | ||||||
| 
 | 
 | ||||||
| 	page := (postIdx / threadPostsPerPage) + 1 | 	page := (postIdx / threadPostsPerPage) + 1 | ||||||
| 
 | 
 | ||||||
| 	return c.Redirect(hmnurl.BuildForumThreadWithPostHash( | 	return c.Redirect(c.UrlContext.BuildForumThreadWithPostHash( | ||||||
| 		c.CurrentProject.Slug, |  | ||||||
| 		cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), | 		cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), | ||||||
| 		cd.ThreadID, | 		cd.ThreadID, | ||||||
| 		post.Thread.Title, | 		post.Thread.Title, | ||||||
|  | @ -482,9 +479,9 @@ func ForumNewThread(c *RequestContext) ResponseData { | ||||||
| 		return FourOhFour(c) | 		return FourOhFour(c) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	baseData := getBaseData(c, "Create New Thread", SubforumBreadcrumbs(cd.LineageBuilder, c.CurrentProject, cd.SubforumID)) | 	baseData := getBaseData(c, "Create New Thread", SubforumBreadcrumbs(c.UrlContext, cd.LineageBuilder, cd.SubforumID)) | ||||||
| 	editData := getEditorDataForNew(c.CurrentUser, baseData, nil) | 	editData := getEditorDataForNew(c.UrlContext, c.CurrentUser, baseData, nil) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildForumNewThread(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), true) | 	editData.SubmitUrl = c.UrlContext.BuildForumNewThread(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), true) | ||||||
| 	editData.SubmitLabel = "Post New Thread" | 	editData.SubmitLabel = "Post New Thread" | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
|  | @ -549,7 +546,7 @@ func ForumNewThreadSubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new forum thread")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new forum thread")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newThreadUrl := hmnurl.BuildForumThread(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), threadId, title, 1) | 	newThreadUrl := c.UrlContext.BuildForumThread(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), threadId, title, 1) | ||||||
| 	return c.Redirect(newThreadUrl, http.StatusSeeOther) | 	return c.Redirect(newThreadUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -572,14 +569,14 @@ func ForumPostReply(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		fmt.Sprintf("Replying to post | %s", cd.SubforumTree[cd.SubforumID].Name), | 		fmt.Sprintf("Replying to post | %s", cd.SubforumTree[cd.SubforumID].Name), | ||||||
| 		ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &post.Thread), | 		ForumThreadBreadcrumbs(c.UrlContext, cd.LineageBuilder, &post.Thread), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	replyPost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | 	replyPost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | ||||||
| 	replyPost.AddContentVersion(post.CurrentVersion, post.Editor) | 	replyPost.AddContentVersion(post.CurrentVersion, post.Editor) | ||||||
| 
 | 
 | ||||||
| 	editData := getEditorDataForNew(c.CurrentUser, baseData, &replyPost) | 	editData := getEditorDataForNew(c.UrlContext, c.CurrentUser, baseData, &replyPost) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildForumPostReply(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | 	editData.SubmitUrl = c.UrlContext.BuildForumPostReply(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | ||||||
| 	editData.SubmitLabel = "Submit Reply" | 	editData.SubmitLabel = "Submit Reply" | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
|  | @ -629,7 +626,7 @@ func ForumPostReplySubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to reply to forum post")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to reply to forum post")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newPostUrl := hmnurl.BuildForumPost(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, newPostId) | 	newPostUrl := c.UrlContext.BuildForumPost(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, newPostId) | ||||||
| 	return c.Redirect(newPostUrl, http.StatusSeeOther) | 	return c.Redirect(newPostUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -659,10 +656,10 @@ func ForumPostEdit(c *RequestContext) ResponseData { | ||||||
| 	} else { | 	} else { | ||||||
| 		title = fmt.Sprintf("Editing Post | %s", cd.SubforumTree[cd.SubforumID].Name) | 		title = fmt.Sprintf("Editing Post | %s", cd.SubforumTree[cd.SubforumID].Name) | ||||||
| 	} | 	} | ||||||
| 	baseData := getBaseData(c, title, ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &post.Thread)) | 	baseData := getBaseData(c, title, ForumThreadBreadcrumbs(c.UrlContext, cd.LineageBuilder, &post.Thread)) | ||||||
| 
 | 
 | ||||||
| 	editData := getEditorDataForEdit(c.CurrentUser, baseData, post) | 	editData := getEditorDataForEdit(c.UrlContext, c.CurrentUser, baseData, post) | ||||||
| 	editData.SubmitUrl = hmnurl.BuildForumPostEdit(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | 	editData.SubmitUrl = c.UrlContext.BuildForumPostEdit(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | ||||||
| 	editData.SubmitLabel = "Submit Edited Post" | 	editData.SubmitLabel = "Submit Edited Post" | ||||||
| 
 | 
 | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
|  | @ -727,7 +724,7 @@ func ForumPostEditSubmit(c *RequestContext) ResponseData { | ||||||
| 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to edit forum post")) | 		return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to edit forum post")) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	postUrl := hmnurl.BuildForumPost(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | 	postUrl := c.UrlContext.BuildForumPost(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID) | ||||||
| 	return c.Redirect(postUrl, http.StatusSeeOther) | 	return c.Redirect(postUrl, http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -754,7 +751,7 @@ func ForumPostDelete(c *RequestContext) ResponseData { | ||||||
| 	baseData := getBaseData( | 	baseData := getBaseData( | ||||||
| 		c, | 		c, | ||||||
| 		fmt.Sprintf("Deleting post in \"%s\" | %s", post.Thread.Title, cd.SubforumTree[cd.SubforumID].Name), | 		fmt.Sprintf("Deleting post in \"%s\" | %s", post.Thread.Title, cd.SubforumTree[cd.SubforumID].Name), | ||||||
| 		ForumThreadBreadcrumbs(cd.LineageBuilder, c.CurrentProject, &post.Thread), | 		ForumThreadBreadcrumbs(c.UrlContext, cd.LineageBuilder, &post.Thread), | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	templatePost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | 	templatePost := templates.PostToTemplate(&post.Post, post.Author, c.Theme) | ||||||
|  | @ -769,7 +766,7 @@ func ForumPostDelete(c *RequestContext) ResponseData { | ||||||
| 	var res ResponseData | 	var res ResponseData | ||||||
| 	res.MustWriteTemplate("forum_post_delete.html", forumPostDeleteData{ | 	res.MustWriteTemplate("forum_post_delete.html", forumPostDeleteData{ | ||||||
| 		BaseData:  baseData, | 		BaseData:  baseData, | ||||||
| 		SubmitUrl: hmnurl.BuildForumPostDelete(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID), | 		SubmitUrl: c.UrlContext.BuildForumPostDelete(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, cd.PostID), | ||||||
| 		Post:      templatePost, | 		Post:      templatePost, | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
|  | @ -799,10 +796,10 @@ func ForumPostDeleteSubmit(c *RequestContext) ResponseData { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if threadDeleted { | 	if threadDeleted { | ||||||
| 		forumUrl := hmnurl.BuildForum(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), 1) | 		forumUrl := c.UrlContext.BuildForum(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), 1) | ||||||
| 		return c.Redirect(forumUrl, http.StatusSeeOther) | 		return c.Redirect(forumUrl, http.StatusSeeOther) | ||||||
| 	} else { | 	} else { | ||||||
| 		threadUrl := hmnurl.BuildForumThread(c.CurrentProject.Slug, cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, "", 1) // TODO: Go to the last page of the thread? Or the post before the post we just deleted?
 | 		threadUrl := c.UrlContext.BuildForumThread(cd.LineageBuilder.GetSubforumLineageSlugs(cd.SubforumID), cd.ThreadID, "", 1) // TODO: Go to the last page of the thread? Or the post before the post we just deleted?
 | ||||||
| 		return c.Redirect(threadUrl, http.StatusSeeOther) | 		return c.Redirect(threadUrl, http.StatusSeeOther) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -829,7 +826,7 @@ func WikiArticleRedirect(c *RequestContext) ResponseData { | ||||||
| 	lineageBuilder := models.MakeSubforumLineageBuilder(subforumTree) | 	lineageBuilder := models.MakeSubforumLineageBuilder(subforumTree) | ||||||
| 	c.Perf.EndBlock() | 	c.Perf.EndBlock() | ||||||
| 
 | 
 | ||||||
| 	dest := UrlForGenericThread(&thread.Thread, lineageBuilder, c.CurrentProject.Slug) | 	dest := UrlForGenericThread(c.UrlContext, &thread.Thread, lineageBuilder) | ||||||
| 	return c.Redirect(dest, http.StatusFound) | 	return c.Redirect(dest, http.StatusFound) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -928,11 +925,11 @@ func validateSubforums(lineageBuilder *models.SubforumLineageBuilder, project *m | ||||||
| 	return subforumId, valid | 	return subforumId, valid | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func addForumUrlsToPost(p *templates.Post, projectSlug string, subforums []string, threadId int, postId int) { | func addForumUrlsToPost(urlContext *hmnurl.UrlContext, p *templates.Post, subforums []string, threadId int, postId int) { | ||||||
| 	p.Url = hmnurl.BuildForumPost(projectSlug, subforums, threadId, postId) | 	p.Url = urlContext.BuildForumPost(subforums, threadId, postId) | ||||||
| 	p.DeleteUrl = hmnurl.BuildForumPostDelete(projectSlug, subforums, threadId, postId) | 	p.DeleteUrl = urlContext.BuildForumPostDelete(subforums, threadId, postId) | ||||||
| 	p.EditUrl = hmnurl.BuildForumPostEdit(projectSlug, subforums, threadId, postId) | 	p.EditUrl = urlContext.BuildForumPostEdit(subforums, threadId, postId) | ||||||
| 	p.ReplyUrl = hmnurl.BuildForumPostReply(projectSlug, subforums, threadId, postId) | 	p.ReplyUrl = urlContext.BuildForumPostReply(subforums, threadId, postId) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Takes a template post and adds information about how many posts the user has made
 | // Takes a template post and adds information about how many posts the user has made
 | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ func Index(c *RequestContext) ResponseData { | ||||||
| 		c.Logger.Warn().Err(err).Msg("failed to fetch latest posts") | 		c.Logger.Warn().Err(err).Msg("failed to fetch latest posts") | ||||||
| 	} | 	} | ||||||
| 	for _, p := range posts { | 	for _, p := range posts { | ||||||
| 		item := PostToTimelineItem(lineageBuilder, &p.Post, &p.Thread, &p.Project, p.Author, c.Theme) | 		item := PostToTimelineItem(UrlContextForProject(&p.Project), lineageBuilder, &p.Post, &p.Thread, p.Author, c.Theme) | ||||||
| 		if p.Thread.Type == models.ThreadTypeProjectBlogPost && p.Post.ID == p.Thread.FirstID { | 		if p.Thread.Type == models.ThreadTypeProjectBlogPost && p.Post.ID == p.Thread.FirstID { | ||||||
| 			// blog post
 | 			// blog post
 | ||||||
| 			item.Description = template.HTML(p.CurrentVersion.TextParsed) | 			item.Description = template.HTML(p.CurrentVersion.TextParsed) | ||||||
|  | @ -95,7 +95,7 @@ func Index(c *RequestContext) ResponseData { | ||||||
| 	var newsPostItem *templates.TimelineItem | 	var newsPostItem *templates.TimelineItem | ||||||
| 	if len(newsThreads) > 0 { | 	if len(newsThreads) > 0 { | ||||||
| 		t := newsThreads[0] | 		t := newsThreads[0] | ||||||
| 		item := PostToTimelineItem(lineageBuilder, &t.FirstPost, &t.Thread, &t.Project, t.FirstPostAuthor, c.Theme) | 		item := PostToTimelineItem(UrlContextForProject(&t.Project), lineageBuilder, &t.FirstPost, &t.Thread, t.FirstPostAuthor, c.Theme) | ||||||
| 		item.OwnerAvatarUrl = "" | 		item.OwnerAvatarUrl = "" | ||||||
| 		item.Breadcrumbs = nil | 		item.Breadcrumbs = nil | ||||||
| 		item.TypeTitle = "" | 		item.TypeTitle = "" | ||||||
|  | @ -167,7 +167,7 @@ func Index(c *RequestContext) ResponseData { | ||||||
| 		StreamsUrl:     hmnurl.BuildStreams(), | 		StreamsUrl:     hmnurl.BuildStreams(), | ||||||
| 		ShowcaseUrl:    hmnurl.BuildShowcase(), | 		ShowcaseUrl:    hmnurl.BuildShowcase(), | ||||||
| 		AtomFeedUrl:    hmnurl.BuildAtomFeed(), | 		AtomFeedUrl:    hmnurl.BuildAtomFeed(), | ||||||
| 		MarkAllReadUrl: hmnurl.BuildForumMarkRead(models.HMNProjectSlug, 0), | 		MarkAllReadUrl: hmnurl.HMNProjectContext.BuildForumMarkRead(0), | ||||||
| 
 | 
 | ||||||
| 		WheelJamUrl: hmnurl.BuildJamIndex(), | 		WheelJamUrl: hmnurl.BuildJamIndex(), | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
|  |  | ||||||
|  | @ -7,26 +7,26 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NOTE(asaf): Please don't use these if you already know the kind of the thread beforehand. Just call the appropriate build function.
 | // NOTE(asaf): Please don't use these if you already know the kind of the thread beforehand. Just call the appropriate build function.
 | ||||||
| func UrlForGenericThread(thread *models.Thread, lineageBuilder *models.SubforumLineageBuilder, projectSlug string) string { | func UrlForGenericThread(urlContext *hmnurl.UrlContext, thread *models.Thread, lineageBuilder *models.SubforumLineageBuilder) string { | ||||||
| 	switch thread.Type { | 	switch thread.Type { | ||||||
| 	case models.ThreadTypeProjectBlogPost: | 	case models.ThreadTypeProjectBlogPost: | ||||||
| 		return hmnurl.BuildBlogThread(projectSlug, thread.ID, thread.Title) | 		return urlContext.BuildBlogThread(thread.ID, thread.Title) | ||||||
| 	case models.ThreadTypeForumPost: | 	case models.ThreadTypeForumPost: | ||||||
| 		return hmnurl.BuildForumThread(projectSlug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1) | 		return urlContext.BuildForumThread(lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), thread.ID, thread.Title, 1) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
 | 	return urlContext.BuildHomepage() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func UrlForGenericPost(thread *models.Thread, post *models.Post, lineageBuilder *models.SubforumLineageBuilder, projectSlug string) string { | func UrlForGenericPost(urlContext *hmnurl.UrlContext, thread *models.Thread, post *models.Post, lineageBuilder *models.SubforumLineageBuilder) string { | ||||||
| 	switch post.ThreadType { | 	switch post.ThreadType { | ||||||
| 	case models.ThreadTypeProjectBlogPost: | 	case models.ThreadTypeProjectBlogPost: | ||||||
| 		return hmnurl.BuildBlogThreadWithPostHash(projectSlug, post.ThreadID, thread.Title, post.ID) | 		return urlContext.BuildBlogThreadWithPostHash(post.ThreadID, thread.Title, post.ID) | ||||||
| 	case models.ThreadTypeForumPost: | 	case models.ThreadTypeForumPost: | ||||||
| 		return hmnurl.BuildForumPost(projectSlug, lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), post.ThreadID, post.ID) | 		return urlContext.BuildForumPost(lineageBuilder.GetSubforumLineageSlugs(*thread.SubforumID), post.ThreadID, post.ID) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
 | 	return urlContext.BuildHomepage() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var PostTypeMap = map[models.ThreadType][]templates.PostType{ | var PostTypeMap = map[models.ThreadType][]templates.PostType{ | ||||||
|  | @ -47,33 +47,33 @@ var ThreadTypeDisplayNames = map[models.ThreadType]string{ | ||||||
| 	models.ThreadTypeForumPost:       "Forums", | 	models.ThreadTypeForumPost:       "Forums", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func GenericThreadBreadcrumbs(lineageBuilder *models.SubforumLineageBuilder, project *models.Project, thread *models.Thread) []templates.Breadcrumb { | func GenericThreadBreadcrumbs(urlContext *hmnurl.UrlContext, lineageBuilder *models.SubforumLineageBuilder, thread *models.Thread) []templates.Breadcrumb { | ||||||
| 	var result []templates.Breadcrumb | 	var result []templates.Breadcrumb | ||||||
| 	if thread.Type == models.ThreadTypeForumPost { | 	if thread.Type == models.ThreadTypeForumPost { | ||||||
| 		result = SubforumBreadcrumbs(lineageBuilder, project, *thread.SubforumID) | 		result = SubforumBreadcrumbs(urlContext, lineageBuilder, *thread.SubforumID) | ||||||
| 	} else { | 	} else { | ||||||
| 		result = []templates.Breadcrumb{ | 		result = []templates.Breadcrumb{ | ||||||
| 			{ | 			{ | ||||||
| 				Name: project.Name, | 				Name: urlContext.ProjectName, | ||||||
| 				Url:  UrlForProject(project), | 				Url:  urlContext.BuildHomepage(), | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				Name: ThreadTypeDisplayNames[thread.Type], | 				Name: ThreadTypeDisplayNames[thread.Type], | ||||||
| 				Url:  BuildProjectRootResourceUrl(project.Slug, thread.Type), | 				Url:  BuildProjectRootResourceUrl(urlContext, thread.Type), | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BuildProjectRootResourceUrl(projectSlug string, kind models.ThreadType) string { | func BuildProjectRootResourceUrl(urlContext *hmnurl.UrlContext, kind models.ThreadType) string { | ||||||
| 	switch kind { | 	switch kind { | ||||||
| 	case models.ThreadTypeProjectBlogPost: | 	case models.ThreadTypeProjectBlogPost: | ||||||
| 		return hmnurl.BuildBlog(projectSlug, 1) | 		return urlContext.BuildBlog(1) | ||||||
| 	case models.ThreadTypeForumPost: | 	case models.ThreadTypeForumPost: | ||||||
| 		return hmnurl.BuildForum(projectSlug, nil, 1) | 		return urlContext.BuildForum(nil, 1) | ||||||
| 	} | 	} | ||||||
| 	return hmnurl.BuildOfficialProjectHomepage(projectSlug) // TODO: both official and personal projects
 | 	return urlContext.BuildHomepage() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func MakePostListItem( | func MakePostListItem( | ||||||
|  | @ -88,11 +88,13 @@ func MakePostListItem( | ||||||
| ) templates.PostListItem { | ) templates.PostListItem { | ||||||
| 	var result templates.PostListItem | 	var result templates.PostListItem | ||||||
| 
 | 
 | ||||||
|  | 	urlContext := UrlContextForProject(project) | ||||||
|  | 
 | ||||||
| 	result.Title = thread.Title | 	result.Title = thread.Title | ||||||
| 	result.User = templates.UserToTemplate(user, currentTheme) | 	result.User = templates.UserToTemplate(user, currentTheme) | ||||||
| 	result.Date = post.PostDate | 	result.Date = post.PostDate | ||||||
| 	result.Unread = unread | 	result.Unread = unread | ||||||
| 	result.Url = UrlForGenericPost(thread, post, lineageBuilder, project.Slug) | 	result.Url = UrlForGenericPost(urlContext, thread, post, lineageBuilder) | ||||||
| 	result.Preview = post.Preview | 	result.Preview = post.Preview | ||||||
| 
 | 
 | ||||||
| 	postType := templates.PostTypeUnknown | 	postType := templates.PostTypeUnknown | ||||||
|  | @ -108,7 +110,7 @@ func MakePostListItem( | ||||||
| 	result.PostTypePrefix = PostTypePrefix[result.PostType] | 	result.PostTypePrefix = PostTypePrefix[result.PostType] | ||||||
| 
 | 
 | ||||||
| 	if includeBreadcrumbs { | 	if includeBreadcrumbs { | ||||||
| 		result.Breadcrumbs = GenericThreadBreadcrumbs(lineageBuilder, project, thread) | 		result.Breadcrumbs = GenericThreadBreadcrumbs(urlContext, lineageBuilder, thread) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return result | 	return result | ||||||
|  |  | ||||||
|  | @ -3,8 +3,9 @@ package website | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"git.handmade.network/hmn/hmn/src/db" |  | ||||||
| 	"git.handmade.network/hmn/hmn/src/hmnurl" | 	"git.handmade.network/hmn/hmn/src/hmnurl" | ||||||
|  | 
 | ||||||
|  | 	"git.handmade.network/hmn/hmn/src/db" | ||||||
| 	"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" | ||||||
| ) | ) | ||||||
|  | @ -388,10 +389,11 @@ func FetchProjectOwners( | ||||||
| 	return projectOwners[0].Owners, nil | 	return projectOwners[0].Owners, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func UrlForProject(p *models.Project) string { | func UrlContextForProject(p *models.Project) *hmnurl.UrlContext { | ||||||
| 	if p.Personal { | 	return &hmnurl.UrlContext{ | ||||||
| 		return hmnurl.BuildPersonalProject(p.ID, models.GeneratePersonalProjectSlug(p.Name)) | 		PersonalProject: p.Personal, | ||||||
| 	} else { | 		ProjectID:       p.ID, | ||||||
| 		return hmnurl.BuildOfficialProjectHomepage(p.Slug) | 		ProjectSlug:     p.Slug, | ||||||
|  | 		ProjectName:     p.Name, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ func ProjectIndex(c *RequestContext) ResponseData { | ||||||
| 	var restProjects []templates.Project | 	var restProjects []templates.Project | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 	for _, p := range officialProjects { | 	for _, p := range officialProjects { | ||||||
| 		templateProject := templates.ProjectToTemplate(&p.Project, c.Theme) | 		templateProject := templates.ProjectToTemplate(&p.Project, UrlContextForProject(&p.Project).BuildHomepage(), c.Theme) | ||||||
| 		if p.Project.Slug == "hero" { | 		if p.Project.Slug == "hero" { | ||||||
| 			// NOTE(asaf): Handmade Hero gets special treatment. Must always be first in the list.
 | 			// NOTE(asaf): Handmade Hero gets special treatment. Must always be first in the list.
 | ||||||
| 			handmadeHero = &templateProject | 			handmadeHero = &templateProject | ||||||
|  | @ -121,7 +121,11 @@ func ProjectIndex(c *RequestContext) ResponseData { | ||||||
| 			if i >= maxPersonalProjects { | 			if i >= maxPersonalProjects { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 			personalProjects = append(personalProjects, templates.ProjectToTemplate(&p.Project, c.Theme)) | 			personalProjects = append(personalProjects, templates.ProjectToTemplate( | ||||||
|  | 				&p.Project, | ||||||
|  | 				UrlContextForProject(&p.Project).BuildHomepage(), | ||||||
|  | 				c.Theme, | ||||||
|  | 			)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -136,7 +140,7 @@ func ProjectIndex(c *RequestContext) ResponseData { | ||||||
| 		PersonalProjects: personalProjects, | 		PersonalProjects: personalProjects, | ||||||
| 
 | 
 | ||||||
| 		ProjectAtomFeedUrl: hmnurl.BuildAtomFeedForProjects(), | 		ProjectAtomFeedUrl: hmnurl.BuildAtomFeedForProjects(), | ||||||
| 		WIPForumUrl:        hmnurl.BuildForum(models.HMNProjectSlug, []string{"wip"}, 1), | 		WIPForumUrl:        hmnurl.HMNProjectContext.BuildForum([]string{"wip"}, 1), | ||||||
| 	}, c.Perf) | 	}, c.Perf) | ||||||
| 	return res | 	return res | ||||||
| } | } | ||||||
|  | @ -249,7 +253,7 @@ func ProjectHomepage(c *RequestContext) ResponseData { | ||||||
| 		Value:    c.CurrentProject.Blurb, | 		Value:    c.CurrentProject.Blurb, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	projectHomepageData.Project = templates.ProjectToTemplate(c.CurrentProject, c.Theme) | 	projectHomepageData.Project = templates.ProjectToTemplate(c.CurrentProject, c.UrlContext.BuildHomepage(), c.Theme) | ||||||
| 	for _, owner := range owners { | 	for _, owner := range owners { | ||||||
| 		projectHomepageData.Owners = append(projectHomepageData.Owners, templates.UserToTemplate(owner, c.Theme)) | 		projectHomepageData.Owners = append(projectHomepageData.Owners, templates.UserToTemplate(owner, c.Theme)) | ||||||
| 	} | 	} | ||||||
|  | @ -268,7 +272,7 @@ func ProjectHomepage(c *RequestContext) ResponseData { | ||||||
| 				"unapproved", | 				"unapproved", | ||||||
| 				fmt.Sprintf( | 				fmt.Sprintf( | ||||||
| 					"NOTICE: This project has not yet been submitted for approval. It is only visible to owners. Please <a href=\"%s\">submit it for approval</a> when the project content is ready for review.", | 					"NOTICE: This project has not yet been submitted for approval. It is only visible to owners. Please <a href=\"%s\">submit it for approval</a> when the project content is ready for review.", | ||||||
| 					hmnurl.BuildProjectEdit(c.CurrentProject.Slug, "submit"), | 					c.UrlContext.BuildProjectEdit("submit"), | ||||||
| 				), | 				), | ||||||
| 			) | 			) | ||||||
| 		case models.ProjectLifecycleApprovalRequired: | 		case models.ProjectLifecycleApprovalRequired: | ||||||
|  | @ -309,10 +313,10 @@ func ProjectHomepage(c *RequestContext) ResponseData { | ||||||
| 
 | 
 | ||||||
| 	for _, post := range postQueryResult.ToSlice() { | 	for _, post := range postQueryResult.ToSlice() { | ||||||
| 		projectHomepageData.RecentActivity = append(projectHomepageData.RecentActivity, PostToTimelineItem( | 		projectHomepageData.RecentActivity = append(projectHomepageData.RecentActivity, PostToTimelineItem( | ||||||
|  | 			c.UrlContext, | ||||||
| 			lineageBuilder, | 			lineageBuilder, | ||||||
| 			&post.(*postQuery).Post, | 			&post.(*postQuery).Post, | ||||||
| 			&post.(*postQuery).Thread, | 			&post.(*postQuery).Thread, | ||||||
| 			c.CurrentProject, |  | ||||||
| 			&post.(*postQuery).Author, | 			&post.(*postQuery).Author, | ||||||
| 			c.Theme, | 			c.Theme, | ||||||
| 		)) | 		)) | ||||||
|  |  | ||||||
|  | @ -156,6 +156,7 @@ type RequestContext struct { | ||||||
| 	CurrentUser    *models.User | 	CurrentUser    *models.User | ||||||
| 	CurrentSession *models.Session | 	CurrentSession *models.Session | ||||||
| 	Theme          string | 	Theme          string | ||||||
|  | 	UrlContext     *hmnurl.UrlContext | ||||||
| 
 | 
 | ||||||
| 	Perf *perf.RequestPerf | 	Perf *perf.RequestPerf | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -251,9 +251,8 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe | ||||||
| 		rb.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete)) | 		rb.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete)) | ||||||
| 		rb.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit))) | 		rb.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit))) | ||||||
| 		rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData { | 		rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData { | ||||||
| 			return c.Redirect(hmnurl.ProjectUrl( | 			return c.Redirect(c.UrlContext.Url( | ||||||
| 				fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil, | 				fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil, | ||||||
| 				c.CurrentProject.Slug, |  | ||||||
| 			), http.StatusMovedPermanently) | 			), http.StatusMovedPermanently) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  | @ -281,7 +280,7 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe | ||||||
| 
 | 
 | ||||||
| 				if !p.Project.Personal { | 				if !p.Project.Personal { | ||||||
| 					// TODO: Redirect to the same page on the other prefix
 | 					// TODO: Redirect to the same page on the other prefix
 | ||||||
| 					return c.Redirect(hmnurl.BuildOfficialProjectHomepage(p.Project.Slug), http.StatusSeeOther) | 					return c.Redirect(UrlContextForProject(&p.Project).BuildHomepage(), http.StatusSeeOther) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if c.PathParams["slug"] != models.GeneratePersonalProjectSlug(p.Project.Name) { | 				if c.PathParams["slug"] != models.GeneratePersonalProjectSlug(p.Project.Name) { | ||||||
|  | @ -290,6 +289,7 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				c.CurrentProject = &p.Project | 				c.CurrentProject = &p.Project | ||||||
|  | 				c.UrlContext = UrlContextForProject(c.CurrentProject) | ||||||
| 
 | 
 | ||||||
| 				return h(c) | 				return h(c) | ||||||
| 			}) | 			}) | ||||||
|  | @ -458,15 +458,15 @@ func LoadCommonWebsiteData(c *RequestContext) (bool, ResponseData) { | ||||||
| 		if c.CurrentProject == nil { | 		if c.CurrentProject == nil { | ||||||
| 			panic("failed to load project data") | 			panic("failed to load project data") | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		c.UrlContext = UrlContextForProject(c.CurrentProject) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	theme := "light" | 	c.Theme = "light" | ||||||
| 	if c.CurrentUser != nil && c.CurrentUser.DarkTheme { | 	if c.CurrentUser != nil && c.CurrentUser.DarkTheme { | ||||||
| 		theme = "dark" | 		c.Theme = "dark" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.Theme = theme |  | ||||||
| 
 |  | ||||||
| 	return true, ResponseData{} | 	return true, ResponseData{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,18 +24,18 @@ var TimelineTypeTitleMap = map[models.ThreadType]TimelineTypeTitles{ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func PostToTimelineItem( | func PostToTimelineItem( | ||||||
|  | 	urlContext *hmnurl.UrlContext, | ||||||
| 	lineageBuilder *models.SubforumLineageBuilder, | 	lineageBuilder *models.SubforumLineageBuilder, | ||||||
| 	post *models.Post, | 	post *models.Post, | ||||||
| 	thread *models.Thread, | 	thread *models.Thread, | ||||||
| 	project *models.Project, |  | ||||||
| 	owner *models.User, | 	owner *models.User, | ||||||
| 	currentTheme string, | 	currentTheme string, | ||||||
| ) templates.TimelineItem { | ) templates.TimelineItem { | ||||||
| 	item := templates.TimelineItem{ | 	item := templates.TimelineItem{ | ||||||
| 		Date:        post.PostDate, | 		Date:        post.PostDate, | ||||||
| 		Title:       thread.Title, | 		Title:       thread.Title, | ||||||
| 		Breadcrumbs: GenericThreadBreadcrumbs(lineageBuilder, project, thread), | 		Breadcrumbs: GenericThreadBreadcrumbs(urlContext, lineageBuilder, thread), | ||||||
| 		Url:         UrlForGenericPost(thread, post, lineageBuilder, project.Slug), | 		Url:         UrlForGenericPost(urlContext, thread, post, lineageBuilder), | ||||||
| 
 | 
 | ||||||
| 		OwnerAvatarUrl: templates.UserAvatarUrl(owner, currentTheme), | 		OwnerAvatarUrl: templates.UserAvatarUrl(owner, currentTheme), | ||||||
| 		OwnerName:      owner.BestName(), | 		OwnerName:      owner.BestName(), | ||||||
|  |  | ||||||
|  | @ -121,7 +121,11 @@ func UserProfile(c *RequestContext) ResponseData { | ||||||
| 	templateProjects := make([]templates.Project, 0, len(projectQuerySlice)) | 	templateProjects := make([]templates.Project, 0, len(projectQuerySlice)) | ||||||
| 	for _, projectRow := range projectQuerySlice { | 	for _, projectRow := range projectQuerySlice { | ||||||
| 		projectData := projectRow.(*projectQuery) | 		projectData := projectRow.(*projectQuery) | ||||||
| 		templateProjects = append(templateProjects, templates.ProjectToTemplate(&projectData.Project, c.Theme)) | 		templateProjects = append(templateProjects, templates.ProjectToTemplate( | ||||||
|  | 			&projectData.Project, | ||||||
|  | 			UrlContextForProject(&projectData.Project).BuildHomepage(), | ||||||
|  | 			c.Theme, | ||||||
|  | 		)) | ||||||
| 	} | 	} | ||||||
| 	c.Perf.EndBlock() | 	c.Perf.EndBlock() | ||||||
| 
 | 
 | ||||||
|  | @ -166,10 +170,10 @@ func UserProfile(c *RequestContext) ResponseData { | ||||||
| 
 | 
 | ||||||
| 	for _, post := range posts { | 	for _, post := range posts { | ||||||
| 		timelineItems = append(timelineItems, PostToTimelineItem( | 		timelineItems = append(timelineItems, PostToTimelineItem( | ||||||
|  | 			UrlContextForProject(&post.Project), | ||||||
| 			lineageBuilder, | 			lineageBuilder, | ||||||
| 			&post.Post, | 			&post.Post, | ||||||
| 			&post.Thread, | 			&post.Thread, | ||||||
| 			&post.Project, |  | ||||||
| 			profileUser, | 			profileUser, | ||||||
| 			c.Theme, | 			c.Theme, | ||||||
| 		)) | 		)) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue