heading.go 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package server
  2. import (
  3. "github.com/gomarkdown/markdown/ast"
  4. "strings"
  5. "fmt"
  6. )
  7. type Heading struct {
  8. Id string
  9. Level int
  10. Text string
  11. }
  12. func getDocumentHeadings(doc ast.Node) []Heading {
  13. ret := []Heading{}
  14. ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
  15. heading, ok := node.(*ast.Heading)
  16. if !ok {
  17. return ast.GoToNext
  18. }
  19. children := node.GetChildren()
  20. if len(children) > 0 && !entering {
  21. leaf := children[0].AsLeaf()
  22. if leaf == nil {
  23. return ast.GoToNext
  24. }
  25. ret = append(ret, Heading{
  26. Id: heading.HeadingID,
  27. Level: heading.Level,
  28. Text: string(leaf.Literal),
  29. })
  30. return ast.SkipChildren
  31. }
  32. return ast.GoToNext
  33. })
  34. return ret
  35. }
  36. type HeadingTree struct {
  37. Heading Heading
  38. Children HeadingTrees
  39. }
  40. type HeadingTrees []*HeadingTree
  41. func makeHeadingTrees(hs []Heading) HeadingTrees {
  42. if len(hs) == 0 {
  43. return nil
  44. }
  45. if len(hs) == 1 {
  46. return HeadingTrees{
  47. &HeadingTree{
  48. Heading: hs[0],
  49. },
  50. }
  51. }
  52. var (
  53. lasti int
  54. found bool
  55. )
  56. first := hs[0]
  57. rest := hs[1:]
  58. for i, h := range rest {
  59. if first.Level >= h.Level {
  60. lasti = i+1
  61. found = true
  62. break
  63. }
  64. }
  65. if !found {
  66. return HeadingTrees{
  67. &HeadingTree{
  68. Heading: first,
  69. Children: makeHeadingTrees(rest),
  70. },
  71. }
  72. }
  73. return append(
  74. makeHeadingTrees(hs[:lasti]),
  75. makeHeadingTrees(hs[lasti:])... ,
  76. )
  77. }
  78. func RenderHeadingTrees(trees HeadingTrees, first bool) string {
  79. var b strings.Builder
  80. fmt.Fprint(&b, "<nav")
  81. if first {
  82. fmt.Fprint(&b, " class=\"document\"")
  83. }
  84. fmt.Fprint(&b, ">")
  85. for _, tree := range trees {
  86. fmt.Fprintf(
  87. &b, "<a href=\"#%s\">%s</a>",
  88. tree.Heading.Id, tree.Heading.Text,
  89. )
  90. if len(tree.Children) > 0 {
  91. fmt.Fprint(&b, RenderHeadingTrees(tree.Children, false))
  92. }
  93. }
  94. fmt.Fprint(&b, "</nav>")
  95. return b.String()
  96. }