Beef up index or something
This commit is contained in:
parent
c9aa3149ef
commit
c550f2cd22
|
@ -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); }
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -197,6 +197,14 @@ article code {
|
|||
}
|
||||
}
|
||||
|
||||
.c--inherit {
|
||||
color: inherit;
|
||||
|
||||
&:hover, &:active {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.b--theme {
|
||||
@include usevar(border-color, theme-color);
|
||||
}
|
||||
|
|
|
@ -213,6 +213,9 @@ func UserToTemplate(u *models.User, currentTheme string) User {
|
|||
|
||||
DiscordSaveShowcase: u.DiscordSaveShowcase,
|
||||
DiscordDeleteSnippetOnMessageDelete: u.DiscordDeleteSnippetOnMessageDelete,
|
||||
|
||||
IsEduTester: u.CanSeeUnpublishedEducationContent(),
|
||||
IsEduAuthor: u.CanAuthorEducation(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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">✎ Edit</a>
|
||||
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖ Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ .Article.Content }}
|
||||
{{ end }}
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()...)
|
||||
|
|
Loading…
Reference in New Issue