Beef up index or something

This commit is contained in:
Ben Visness 2022-07-23 12:10:28 -05:00
parent c9aa3149ef
commit c550f2cd22
11 changed files with 176 additions and 69 deletions

View File

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

View File

@ -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<topicid>\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<resourceid>\d+)$`)
func BuildLibraryResource(resourceId int) string {
defer CatchPanic()
builder := buildLibraryResourcePath(resourceId)
return Url(builder.String(), nil)
}
/*
* Episode Guide
*/

View File

@ -197,6 +197,14 @@ article code {
}
}
.c--inherit {
color: inherit;
&:hover, &:active {
color: inherit;
}
}
.b--theme {
@include usevar(border-color, theme-color);
}

View File

@ -213,6 +213,9 @@ func UserToTemplate(u *models.User, currentTheme string) User {
DiscordSaveShowcase: u.DiscordSaveShowcase,
DiscordDeleteSnippetOnMessageDelete: u.DiscordDeleteSnippetOnMessageDelete,
IsEduTester: u.CanSeeUnpublishedEducationContent(),
IsEduAuthor: u.CanAuthorEducation(),
}
}

View File

@ -82,21 +82,28 @@
{{ if .ShowEduOptions }}
{{/* Hope you have a .Article field! */}}
<div class="mb2">
<label for="slug">Slug:</label>
<input name="slug" maxlength="255" type="text" id="slug" required value="{{ .Article.Slug }}" />
</div>
<div class="mb2">
<label for="type">Type:</label>
<select name="type" id="type">
<option value="article" {{ if eq .Article.Type "article" }}selected{{ end }}>Article</option>
<option value="glossary" {{ if eq .Article.Type "glossary" }}selected{{ end }}>Glossary Term</option>
</select>
</div>
<div class="mb2">
<label for="description">Description:</label>
<div>
<textarea name="description" id="slug" required>{{ .Article.Description }}</textarea>
<div class="bg--dim br3 pa3 mt3">
<h4>Education Options</h4>
<div class="mb2">
<label for="slug">Slug:</label>
<input name="slug" maxlength="255" type="text" id="slug" required value="{{ .Article.Slug }}" />
</div>
<div class="mb2">
<label for="type">Type:</label>
<select name="type" id="type">
<option value="article" {{ if eq .Article.Type "article" }}selected{{ end }}>Article</option>
<option value="glossary" {{ if eq .Article.Type "glossary" }}selected{{ end }}>Glossary Term</option>
</select>
</div>
<div class="mb2">
<label for="description">Description:</label>
<div>
<textarea name="description" id="slug" required>{{ .Article.Description }}</textarea>
</div>
</div>
<div class="mb2">
<label for="published">Published:</label>
<input name="published" id="published" type="checkbox" {{ if .Article.Published }}checked{{ end }}>
</div>
</div>
{{ end }}

View File

@ -1,5 +1,15 @@
{{ template "base.html" . }}
{{ define "content" }}
{{ if .User.IsEduAuthor }}
<div class="optionbar">
<div class="options">
</div>
<div class="options">
<a class="edit action button" href="{{ .EditUrl }}" title="Edit">&#9998; Edit</a>
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">&#10006; Delete</a>
</div>
</div>
{{ end }}
{{ .Article.Content }}
{{ end }}

View File

@ -1,5 +1,43 @@
{{ template "base.html" . }}
{{ define "content" }}
O NO
<h1>Learn the Handmade way.</h1>
<h2>Guides</h2>
{{ if .User.IsEduAuthor }}
<div class="mb2">
<a href="{{ .NewArticleUrl }}"><span class="big pr1">+</span> New Article</a>
</div>
{{ end }}
<div class="flex flex-column g3 mb3">
{{ range .Articles }}
<a class="c--inherit flex flex-column pa3 bg--dim br2" href="{{ .Url }}" >
<h3 class="mb1 link">{{ .Title }}</h3>
<div>{{ .Description }}</div>
</a>
{{ end }}
</div>
<h2>What makes us different?</h2>
<div class="flex flex-column flex-row-ns g3">
<div class="flex-fair bg--dim pa3 br2">
<h3>Real material.</h3>
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.
</div>
<div class="flex-fair bg--dim pa3 br3">
<h3>For any skill level.</h3>
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.
</div>
<div class="flex-fair bg--dim pa3 br3">
<h3>Designed for programmers.</h3>
We're not here to teach you how to program. We're here to teach you a specific topic.
</div>
</div>
{{ end }}

View File

@ -84,7 +84,7 @@
<div class="root-item">
<a>Resources <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.LibraryUrl }}">Library</a>
<a href="{{ .Header.EducationUrl }}">Education</a>
</div>
</div>
</div>

View File

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

View File

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

View File

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