A few education improvements

This commit is contained in:
Ben Visness 2022-09-17 16:21:58 -05:00
parent c489d0ffa9
commit b27ddd1e7f
7 changed files with 134 additions and 5 deletions

Binary file not shown.

View File

@ -1,3 +1,5 @@
//go:build !js
package config
import (

7
src/config/jsconfig.go Normal file
View File

@ -0,0 +1,7 @@
//go:build js
package config
var Config = HMNConfig{
BaseUrl: "https://handmade.network",
}

View File

@ -14,6 +14,7 @@ import (
// Used for rendering real-time previews of post content.
var ForumPreviewMarkdown = makeGoldmark(
false,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: true,
Embeds: true,
@ -22,6 +23,7 @@ var ForumPreviewMarkdown = makeGoldmark(
// Used for generating the final HTML for a post.
var ForumRealMarkdown = makeGoldmark(
false,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: false,
Embeds: true,
@ -30,6 +32,7 @@ var ForumRealMarkdown = makeGoldmark(
// Used for generating plain-text previews of posts.
var PlaintextMarkdown = makeGoldmark(
false,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: false,
Embeds: true,
@ -39,6 +42,7 @@ var PlaintextMarkdown = makeGoldmark(
// Used for processing Discord messages
var DiscordMarkdown = makeGoldmark(
false,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: false,
Embeds: false,
@ -48,20 +52,24 @@ var DiscordMarkdown = makeGoldmark(
// Used for rendering real-time previews of post content.
var EducationPreviewMarkdown = makeGoldmark(
true,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: true,
Embeds: true,
Education: true,
})...),
goldmark.WithRendererOptions(html.WithUnsafe()),
)
// Used for generating the final HTML for a post.
var EducationRealMarkdown = makeGoldmark(
true,
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
Previews: false,
Embeds: true,
Education: true,
})...),
goldmark.WithRendererOptions(html.WithUnsafe()),
)
func ParseMarkdown(source string, md goldmark.Markdown) string {
@ -79,8 +87,9 @@ type MarkdownOptions struct {
Education bool
}
func makeGoldmark(opts ...goldmark.Option) goldmark.Markdown {
func makeGoldmark(rawHTML bool, opts ...goldmark.Option) goldmark.Markdown {
// We need to re-create Goldmark's default parsers to disable HTML parsing.
// Or enable it again. yay
// See parser.DefaultBlockParsers
blockParsers := []util.PrioritizedValue{
@ -105,6 +114,11 @@ func makeGoldmark(opts ...goldmark.Option) goldmark.Markdown {
util.Prioritized(parser.NewEmphasisParser(), 500),
}
if rawHTML {
blockParsers = append(blockParsers, util.Prioritized(parser.NewHTMLBlockParser(), 900))
inlineParsers = append(inlineParsers, util.Prioritized(parser.NewRawHTMLParser(), 400))
}
opts = append(opts, goldmark.WithParser(parser.NewParser(
parser.WithBlockParsers(blockParsers...),
parser.WithInlineParsers(inlineParsers...),

View File

@ -16,6 +16,6 @@ func main() {
return parsing.ParseMarkdown(args[0].String(), parsing.EducationPreviewMarkdown)
}))
var done chan bool
var done chan struct{}
<-done // block forever
}

View File

@ -5,6 +5,38 @@
.hide-notes .note {
display: none;
}
.toc {
position: relative;
overflow: hidden;
transition: all 40ms ease-in-out;
}
.toc::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-left: 0 solid var(--link-color);
transition: all 40ms ease-in-out;
}
.toc.active {
background-color: var(--dim-background);
}
.toc.active::after {
border-left-width: 0.25rem;
}
.toc-1 {}
.toc-2 { margin-left: 1rem; }
.toc-3 { margin-left: 2rem; }
.toc-4 { margin-left: 3rem; }
.toc-5 { margin-left: 4rem; }
.toc-6 { margin-left: 5rem; }
</style>
{{ end }}
@ -22,12 +54,50 @@
<div class="edu-article flex-grow-1 post-content">
{{ .Article.Content }}
</div>
<div class="ml3 flex-shrink-0 w5">
I'm a sidebar!
<div class="sidebar ml3 flex-shrink-0 w-30">
<div class="toc-container flex flex-column">
{{ range .TOC }}
<a href="#{{ .ID }}" class="db ph2 pv1 br2 toc toc-{{ .Level }}">{{ .Text }}</a>
{{ end }}
</div>
</div>
</div>
<script>
const sidebar = document.querySelector('.sidebar');
const tocContainer = document.querySelector('.toc-container');
const tocEntries = Array.from(document.querySelectorAll('.toc')).map(tocLink => ({
link: tocLink,
heading: document.querySelector(tocLink.getAttribute('href')),
}));
// TOC
const FUDGE = 100;
const TOC_TOP_SPACING = 20;
document.addEventListener('scroll', () => {
// Stickiness
const stick = window.pageYOffset > sidebar.offsetTop-TOC_TOP_SPACING;
tocContainer.style.position = stick ? 'fixed' : 'static';
tocContainer.style.top = `${TOC_TOP_SPACING}px`;
// Active items
let activeEntry = null;
for (const toc of tocEntries) {
if (window.pageYOffset >= toc.heading.offsetTop-FUDGE) {
activeEntry = toc;
} else {
break;
}
}
for (const toc of tocEntries) {
toc.link.classList.remove('active');
}
if (activeEntry) {
activeEntry.link.classList.add('active');
}
});
// Notes
function toggleNotes() {
document.querySelector('.edu-article').classList.toggle('hide-notes',
document.querySelector('#hide-notes').checked,

View File

@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"git.handmade.network/hmn/hmn/src/db"
@ -66,6 +68,7 @@ func EducationArticle(c *RequestContext) ResponseData {
type articleData struct {
templates.BaseData
Article templates.EduArticle
TOC []TOCEntry
EditUrl string
DeleteUrl string
}
@ -96,6 +99,11 @@ func EducationArticle(c *RequestContext) ResponseData {
tmpl.Article.Content = template.HTML(reEduEditorsNote.ReplaceAllLiteralString(string(tmpl.Article.Content), ""))
}
// Generate TOC and stuff I dunno
html, tocEntries := generateTOC(string(tmpl.Article.Content))
tmpl.Article.Content = template.HTML(html)
tmpl.TOC = tocEntries
var res ResponseData
res.MustWriteTemplate("education_article.html", tmpl, c.Perf)
return res
@ -426,3 +434,31 @@ func eduArticleURL(a *models.EduArticle) string {
panic("unknown education article type")
}
}
var reHeading = regexp.MustCompile(`<h([1-6])>(.*?)</h[1-6]>`)
var reNotSimple = regexp.MustCompile(`[^a-zA-Z0-9-_]+`)
type TOCEntry struct {
Text string
ID string
Level int
}
func generateTOC(html string) (string, []TOCEntry) {
var entries []TOCEntry
replacinated := reHeading.ReplaceAllStringFunc(html, func(s string) string {
m := reHeading.FindStringSubmatch(s)
level := m[1]
content := m[2]
id := strings.ToLower(reNotSimple.ReplaceAllLiteralString(content, "-"))
entries = append(entries, TOCEntry{
Text: content,
ID: id,
Level: utils.Must1(strconv.Atoi(level)),
})
return fmt.Sprintf(`<h%s id="%s">%s</h%s>`, level, id, content, level)
})
return replacinated, entries
}