A few education improvements
This commit is contained in:
parent
c489d0ffa9
commit
b27ddd1e7f
Binary file not shown.
|
@ -1,3 +1,5 @@
|
|||
//go:build !js
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
//go:build js
|
||||
|
||||
package config
|
||||
|
||||
var Config = HMNConfig{
|
||||
BaseUrl: "https://handmade.network",
|
||||
}
|
|
@ -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{
|
||||
|
@ -101,10 +110,15 @@ func makeGoldmark(opts ...goldmark.Option) goldmark.Markdown {
|
|||
util.Prioritized(parser.NewCodeSpanParser(), 100),
|
||||
util.Prioritized(parser.NewLinkParser(), 200),
|
||||
util.Prioritized(parser.NewAutoLinkParser(), 300),
|
||||
//util.Prioritized(parser.NewRawHTMLParser(), 400),
|
||||
// util.Prioritized(parser.NewRawHTMLParser(), 400),
|
||||
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...),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue