diff --git a/public/style.css b/public/style.css index da157f3..b681eeb 100644 --- a/public/style.css +++ b/public/style.css @@ -7369,6 +7369,11 @@ article code { flex-grow: 1; flex-shrink: 1; } } +.c--inherit { + color: inherit; } + .c--inherit:hover, .c--inherit:active { + color: inherit; } + .b--theme { border-color: #666; border-color: var(--theme-color); } diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go index 6117fe3..89bb7d8 100644 --- a/src/hmnurl/urls.go +++ b/src/hmnurl/urls.go @@ -705,47 +705,8 @@ func (c *UrlContext) BuildBlogPostReply(threadId int, postId int) string { * Library */ -// Any library route. Remove after we port the library. var RegexLibraryAny = regexp.MustCompile(`^/library`) -var RegexLibrary = regexp.MustCompile(`^/library$`) - -func BuildLibrary() string { - defer CatchPanic() - return Url("/library", nil) -} - -var RegexLibraryAll = regexp.MustCompile(`^/library/all$`) - -func BuildLibraryAll() string { - defer CatchPanic() - return Url("/library/all", nil) -} - -var RegexLibraryTopic = regexp.MustCompile(`^/library/topic/(?P\d+)$`) - -func BuildLibraryTopic(topicId int) string { - defer CatchPanic() - if topicId < 1 { - panic(oops.New(nil, "Invalid library topic ID (%d), must be >= 1", topicId)) - } - - var builder strings.Builder - builder.WriteString("/library/topic/") - builder.WriteString(strconv.Itoa(topicId)) - - return Url(builder.String(), nil) -} - -var RegexLibraryResource = regexp.MustCompile(`^/library/resource/(?P\d+)$`) - -func BuildLibraryResource(resourceId int) string { - defer CatchPanic() - builder := buildLibraryResourcePath(resourceId) - - return Url(builder.String(), nil) -} - /* * Episode Guide */ diff --git a/src/rawdata/scss/_core.scss b/src/rawdata/scss/_core.scss index bb05d4c..594fa37 100644 --- a/src/rawdata/scss/_core.scss +++ b/src/rawdata/scss/_core.scss @@ -197,6 +197,14 @@ article code { } } +.c--inherit { + color: inherit; + + &:hover, &:active { + color: inherit; + } +} + .b--theme { @include usevar(border-color, theme-color); } diff --git a/src/templates/mapping.go b/src/templates/mapping.go index d964115..43db646 100644 --- a/src/templates/mapping.go +++ b/src/templates/mapping.go @@ -213,6 +213,9 @@ func UserToTemplate(u *models.User, currentTheme string) User { DiscordSaveShowcase: u.DiscordSaveShowcase, DiscordDeleteSnippetOnMessageDelete: u.DiscordDeleteSnippetOnMessageDelete, + + IsEduTester: u.CanSeeUnpublishedEducationContent(), + IsEduAuthor: u.CanAuthorEducation(), } } diff --git a/src/templates/src/editor.html b/src/templates/src/editor.html index 3519996..a03fd0b 100644 --- a/src/templates/src/editor.html +++ b/src/templates/src/editor.html @@ -82,21 +82,28 @@ {{ if .ShowEduOptions }} {{/* Hope you have a .Article field! */}} -
- - -
-
- - -
-
- -
- +
+

Education Options

+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ +
{{ end }} diff --git a/src/templates/src/education_article.html b/src/templates/src/education_article.html index 033dad6..c2e2d4e 100644 --- a/src/templates/src/education_article.html +++ b/src/templates/src/education_article.html @@ -1,5 +1,15 @@ {{ template "base.html" . }} {{ define "content" }} + {{ if .User.IsEduAuthor }} +
+
+
+ +
+ {{ end }} {{ .Article.Content }} {{ end }} diff --git a/src/templates/src/education_index.html b/src/templates/src/education_index.html index 9503ae8..3ecd2e8 100644 --- a/src/templates/src/education_index.html +++ b/src/templates/src/education_index.html @@ -1,5 +1,43 @@ {{ template "base.html" . }} {{ define "content" }} - O NO +

Learn the Handmade way.

+ +

Guides

+ + {{ if .User.IsEduAuthor }} + + {{ end }} + +
+ {{ range .Articles }} + + +
{{ .Description }}
+
+ {{ end }} +
+ +

What makes us different?

+ +
+
+

Real material.

+ + We equip you to go straight to the source. Our guides are structured around books and articles written by experts. We give you high-quality material to read, and the context to understand it. You do the rest. +
+
+

For any skill level.

+ + Each guide runs the gamut from beginner to advanced. Whether you're new to a topic or have been practicing it for years, read through our guides and you'll find something new. +
+
+

Designed for programmers.

+ + We're not here to teach you how to program. We're here to teach you a specific topic. +
+
+ {{ end }} diff --git a/src/templates/src/include/header.html b/src/templates/src/include/header.html index 75183a7..8346ec0 100644 --- a/src/templates/src/include/header.html +++ b/src/templates/src/include/header.html @@ -84,7 +84,7 @@
diff --git a/src/templates/types.go b/src/templates/types.go index 292ef61..0173c88 100644 --- a/src/templates/types.go +++ b/src/templates/types.go @@ -51,8 +51,8 @@ type Header struct { PodcastUrl string FishbowlUrl string ForumsUrl string - LibraryUrl string ConferencesUrl string + EducationUrl string Project *ProjectHeader } @@ -191,6 +191,9 @@ type User struct { DiscordSaveShowcase bool DiscordDeleteSnippetOnMessageDelete bool + + IsEduTester bool + IsEduAuthor bool } type Link struct { diff --git a/src/website/base_data.go b/src/website/base_data.go index 7926a83..a813c74 100644 --- a/src/website/base_data.go +++ b/src/website/base_data.go @@ -74,8 +74,8 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc PodcastUrl: hmnurl.BuildPodcast(), FishbowlUrl: hmnurl.BuildFishbowlIndex(), ForumsUrl: hmnurl.HMNProjectContext.BuildForum(nil, 1), - LibraryUrl: hmnurl.BuildLibrary(), ConferencesUrl: hmnurl.BuildConferences(), + EducationUrl: hmnurl.BuildEducationIndex(), }, Footer: templates.Footer{ HomepageUrl: hmnurl.BuildHomepage(), diff --git a/src/website/education.go b/src/website/education.go index 446212e..4c65421 100644 --- a/src/website/education.go +++ b/src/website/education.go @@ -20,10 +20,24 @@ import ( func EducationIndex(c *RequestContext) ResponseData { type indexData struct { templates.BaseData + Articles []templates.EduArticle + NewArticleUrl string + } + + articles, err := fetchEduArticles(c, c.Conn, models.EduArticleTypeArticle, c.CurrentUser) + if err != nil { + panic(err) + } + + var tmplArticles []templates.EduArticle + for _, article := range articles { + tmplArticles = append(tmplArticles, templates.EducationArticleToTemplate(&article)) } tmpl := indexData{ - BaseData: getBaseData(c, "Handmade Education", nil), + BaseData: getBaseData(c, "Handmade Education", nil), + Articles: tmplArticles, + NewArticleUrl: hmnurl.BuildEducationArticleNew(), } var res ResponseData @@ -48,10 +62,12 @@ func EducationGlossary(c *RequestContext) ResponseData { func EducationArticle(c *RequestContext) ResponseData { type articleData struct { templates.BaseData - Article templates.EduArticle + Article templates.EduArticle + EditUrl string + DeleteUrl string } - article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], models.EduArticleTypeArticle) + article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], models.EduArticleTypeArticle, c.CurrentUser) if errors.Is(err, db.NotFound) { return FourOhFour(c) } else if err != nil { @@ -59,9 +75,14 @@ func EducationArticle(c *RequestContext) ResponseData { } tmpl := articleData{ - BaseData: getBaseData(c, "Handmade Education", nil), - Article: templates.EducationArticleToTemplate(article), + BaseData: getBaseData(c, article.Title, nil), + Article: templates.EducationArticleToTemplate(article), + EditUrl: hmnurl.BuildEducationArticleEdit(article.Slug), + DeleteUrl: hmnurl.BuildEducationArticleDelete(article.Slug), } + tmpl.OpenGraphItems = append(tmpl.OpenGraphItems, + templates.OpenGraphItem{Property: "og:description", Value: string(article.Description)}, + ) var res ResponseData res.MustWriteTemplate("education_article.html", tmpl, c.Perf) @@ -151,7 +172,7 @@ func EducationArticleEdit(c *RequestContext) ResponseData { Article templates.EduArticle } - article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0) + article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) if errors.Is(err, db.NotFound) { return FourOhFour(c) } else if err != nil { @@ -175,7 +196,7 @@ func EducationArticleEditSubmit(c *RequestContext) ResponseData { return c.ErrorResponse(http.StatusBadRequest, err) } - _, err = fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0) + _, err = fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) if errors.Is(err, db.NotFound) { return FourOhFour(c) } else if err != nil { @@ -191,7 +212,7 @@ func EducationArticleEditSubmit(c *RequestContext) ResponseData { } func EducationArticleDelete(c *RequestContext) ResponseData { - article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0) + article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) if errors.Is(err, db.NotFound) { return FourOhFour(c) } else if err != nil { @@ -226,7 +247,55 @@ func EducationArticleDeleteSubmit(c *RequestContext) ResponseData { return res } -func fetchEduArticle(ctx context.Context, dbConn db.ConnOrTx, slug string, t models.EduArticleType) (*models.EduArticle, error) { +func fetchEduArticles( + ctx context.Context, + dbConn db.ConnOrTx, + t models.EduArticleType, + currentUser *models.User, +) ([]models.EduArticle, error) { + type eduArticleResult struct { + Article models.EduArticle `db:"a"` + CurrentVersion models.EduArticleVersion `db:"v"` + } + + var qb db.QueryBuilder + qb.Add(` + SELECT $columns + FROM + education_article AS a + JOIN education_article_version AS v ON a.current_version = v.id + WHERE + TRUE + `) + if t != 0 { + qb.Add(`AND a.type = $?`, t) + } + if currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent() { + qb.Add(`AND NOT a.published`) + } + + articles, err := db.Query[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...) + if err != nil { + return nil, err + } + + var res []models.EduArticle + for _, article := range articles { + ver := article.CurrentVersion + article.Article.CurrentVersion = &ver + res = append(res, article.Article) + } + + return res, nil +} + +func fetchEduArticle( + ctx context.Context, + dbConn db.ConnOrTx, + slug string, + t models.EduArticleType, + currentUser *models.User, +) (*models.EduArticle, error) { type eduArticleResult struct { Article models.EduArticle `db:"a"` CurrentVersion models.EduArticleVersion `db:"v"` @@ -240,12 +309,15 @@ func fetchEduArticle(ctx context.Context, dbConn db.ConnOrTx, slug string, t mod education_article AS a JOIN education_article_version AS v ON a.current_version = v.id WHERE - slug = $? + a.slug = $? `, slug, ) if t != 0 { - qb.Add(`AND type = $?`, t) + qb.Add(`AND a.type = $?`, t) + } + if currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent() { + qb.Add(`AND NOT a.published`) } res, err := db.QueryOne[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...)