Skip to content

Commit

Permalink
cache pages hashtags to improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
emad-elsaid committed Jan 13, 2025
1 parent d9f0944 commit 209f94c
Showing 1 changed file with 125 additions and 91 deletions.
216 changes: 125 additions & 91 deletions extensions/hashtags/hashtags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,30 @@ import (
var templates embed.FS

func init() {
RegisterExtension(Hashtags{})
h := Hashtags{
pages: make(map[Page][]*HashTag),
}

RegisterExtension(&h)
}

type Hashtags struct{}
type Hashtags struct {
pages map[Page][]*HashTag
mu sync.Mutex
}

func (Hashtags) Name() string { return "hashtags" }
func (Hashtags) Init() {
Get(`/+/tags`, tagsHandler)
Get(`/+/tag/{tag}`, tagHandler)
RegisterWidget(WidgetAfterView, 1, relatedPages)
func (*Hashtags) Name() string { return "hashtags" }
func (h *Hashtags) Init() {
Get(`/+/tags`, h.tagsHandler)
Get(`/+/tag/{tag}`, h.tagHandler)
RegisterWidget(WidgetAfterView, 1, h.relatedPages)
RegisterBuildPage("/+/tags", true)
RegisterLink(links)
RegisterTemplate(templates, "templates")
shortcode.RegisterShortCode("hashtag-pages", shortcode.ShortCode{Render: hashtagPages})
shortcode.RegisterShortCode("hashtag-pages", shortcode.ShortCode{Render: h.hashtagPages})

Listen(PageChanged, h.PageChanged)
Listen(PageDeleted, h.PageDeleted)

MarkdownConverter().Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(&HashTag{}, 0),
Expand All @@ -48,92 +58,32 @@ func (Hashtags) Init() {
))
}

func links(Page) []Command {
return []Command{link{}}
}

type link struct{}
func (h *Hashtags) PageChanged(p Page) error {
delete(h.pages, p)

func (l link) Icon() string { return "fa-solid fa-tags" }
func (l link) Name() string { return "Hashtags" }
func (l link) Attrs() map[template.HTMLAttr]any {
return map[template.HTMLAttr]any{
"href": "/+/tags",
}
return nil
}

type HashTag struct {
ast.BaseInline
value []byte
unique unique.Handle[string]
func (h *Hashtags) PageDeleted(p Page) error {
return h.PageChanged(p)
}

func (h *HashTag) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindHashTag, renderHashtag)
}

func (h *HashTag) Trigger() []byte {
return []byte{'#'}
}

func (h *HashTag) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
l, _ := block.PeekLine()
if len(l) < 1 {
return nil
}

var line string = string(l)

var i int
for ui, c := range line {
if ui == 0 {
i += utf8.RuneLen(c)
continue
}

if !(unicode.In(c, unicode.Letter, unicode.Number, unicode.Dash) || c == '_') || unicode.IsSpace(c) {
break
}
func (h *Hashtags) hashtagsFor(p Page) []*HashTag {
h.mu.Lock()
defer h.mu.Unlock()

i += utf8.RuneLen(c)
if tags, ok := h.pages[p]; ok {
return tags
}
if i > len(line) || i == 1 {
return nil
}
block.Advance(i)
tag := line[1:i]
return &HashTag{
value: []byte(tag),
unique: unique.Make(strings.ToLower(tag)),
}
}

func (h *HashTag) Dump(source []byte, level int) {
m := map[string]string{
"value": fmt.Sprintf("%#v", h.value),
}
ast.DumpHelper(h, source, level, m, nil)
}

var KindHashTag = ast.NewNodeKind("Hashtag")

func (h *HashTag) Kind() ast.NodeKind {
return KindHashTag
}

func renderHashtag(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering || n.Kind() != KindHashTag {
return ast.WalkContinue, nil
}
_, tree := p.AST()
tags := FindAllInAST[*HashTag](tree)
h.pages[p] = tags

tag := n.(*HashTag)
fmt.Fprintf(writer, `<a href="/+/tag/%s" class="tag">%s</a>`, tag.value, tag.value)
RegisterBuildPage(fmt.Sprintf("/+/tag/%s", tag.value), true)
RegisterBuildPage(fmt.Sprintf("/+/tag/%s", strings.ToLower(string(tag.value))), true)
return ast.WalkContinue, nil
return tags
}

func tagsHandler(r Request) Output {
func (h *Hashtags) tagsHandler(r Request) Output {
tags := map[string][]Page{}
var lck sync.Mutex

Expand Down Expand Up @@ -167,25 +117,24 @@ func tagsHandler(r Request) Output {
})
}

func tagHandler(r Request) Output {
func (h *Hashtags) tagHandler(r Request) Output {
tag := r.PathValue("tag")

return Render("tag", Locals{
"page": DynamicPage{NameVal: "#" + tag},
"pages": tagPages(r.Context(), tag),
"pages": h.tagPages(r.Context(), tag),
})
}

func tagPages(ctx context.Context, hashtag string) []Page {
func (h *Hashtags) tagPages(ctx context.Context, hashtag string) []Page {
uniqHandle := unique.Make(strings.ToLower(hashtag))

return MapPage(ctx, func(p Page) Page {
if p.Name() == Config.Index {
return nil
}

_, tree := p.AST()
tags := FindAllInAST[*HashTag](tree)
tags := h.hashtagsFor(p)
for _, t := range tags {
if uniqHandle == t.unique {
return p
Expand All @@ -196,7 +145,7 @@ func tagPages(ctx context.Context, hashtag string) []Page {
})
}

func relatedPages(p Page) template.HTML {
func (h *Hashtags) relatedPages(p Page) template.HTML {
if p.Name() == Config.Index {
return ""
}
Expand Down Expand Up @@ -229,9 +178,9 @@ func relatedPages(p Page) template.HTML {
})
}

func hashtagPages(hashtag Markdown) template.HTML {
func (h *Hashtags) hashtagPages(hashtag Markdown) template.HTML {
hashtag_value := strings.Trim(string(hashtag), "# \n")
pages := tagPages(context.Background(), hashtag_value)
pages := h.tagPages(context.Background(), hashtag_value)

slices.SortFunc(pages, func(a, b Page) int {
if modtime := b.ModTime().Compare(a.ModTime()); modtime != 0 {
Expand All @@ -244,3 +193,88 @@ func hashtagPages(hashtag Markdown) template.HTML {
output := Partial("hashtag-pages", Locals{"pages": pages})
return template.HTML(output)
}

func links(Page) []Command {
return []Command{link{}}
}

type link struct{}

func (l link) Icon() string { return "fa-solid fa-tags" }
func (l link) Name() string { return "Hashtags" }
func (l link) Attrs() map[template.HTMLAttr]any {
return map[template.HTMLAttr]any{
"href": "/+/tags",
}
}

type HashTag struct {
ast.BaseInline
value []byte
unique unique.Handle[string]
}

func (h *HashTag) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindHashTag, renderHashtag)
}

func (h *HashTag) Trigger() []byte {
return []byte{'#'}
}

func (h *HashTag) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
l, _ := block.PeekLine()
if len(l) < 1 {
return nil
}

var line string = string(l)

var i int
for ui, c := range line {
if ui == 0 {
i += utf8.RuneLen(c)
continue
}

if !(unicode.In(c, unicode.Letter, unicode.Number, unicode.Dash) || c == '_') || unicode.IsSpace(c) {
break
}

i += utf8.RuneLen(c)
}
if i > len(line) || i == 1 {
return nil
}
block.Advance(i)
tag := line[1:i]
return &HashTag{
value: []byte(tag),
unique: unique.Make(strings.ToLower(tag)),
}
}

func (h *HashTag) Dump(source []byte, level int) {
m := map[string]string{
"value": fmt.Sprintf("%#v", h.value),
}
ast.DumpHelper(h, source, level, m, nil)
}

var KindHashTag = ast.NewNodeKind("Hashtag")

func (h *HashTag) Kind() ast.NodeKind {
return KindHashTag
}

func renderHashtag(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering || n.Kind() != KindHashTag {
return ast.WalkContinue, nil
}

tag := n.(*HashTag)
fmt.Fprintf(writer, `<a href="/+/tag/%s" class="tag">%s</a>`, tag.value, tag.value)
RegisterBuildPage(fmt.Sprintf("/+/tag/%s", tag.value), true)
RegisterBuildPage(fmt.Sprintf("/+/tag/%s", strings.ToLower(string(tag.value))), true)
return ast.WalkContinue, nil
}

0 comments on commit 209f94c

Please sign in to comment.