diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-18 03:05:19 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-17 15:05:19 -0400 |
commit | 1ab16e48cccc086e7f97fb3ae8a293fe47a3a452 (patch) | |
tree | 24f19185b5606ea605f5d74d6369dbfd4dc2d189 /modules/markup/markdown | |
parent | f045e58cc7ba076a0ac4b0b5bf0702fa155eaa59 (diff) | |
download | gitea-1ab16e48cccc086e7f97fb3ae8a293fe47a3a452.tar.gz gitea-1ab16e48cccc086e7f97fb3ae8a293fe47a3a452.zip |
Improve Wiki TOC (#24137)
The old code has a lot of technical debts, eg: `repo/wiki/view.tmpl` /
`Iterate`
This PR improves the Wiki TOC display and improves the code.
---------
Co-authored-by: delvh <dev.lh@web.de>
Diffstat (limited to 'modules/markup/markdown')
-rw-r--r-- | modules/markup/markdown/convertyaml.go | 2 | ||||
-rw-r--r-- | modules/markup/markdown/goldmark.go | 34 | ||||
-rw-r--r-- | modules/markup/markdown/markdown.go | 23 | ||||
-rw-r--r-- | modules/markup/markdown/renderconfig.go | 79 | ||||
-rw-r--r-- | modules/markup/markdown/renderconfig_test.go | 10 | ||||
-rw-r--r-- | modules/markup/markdown/toc.go | 8 |
6 files changed, 84 insertions, 72 deletions
diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go index 6e90847e06..1675b68be2 100644 --- a/modules/markup/markdown/convertyaml.go +++ b/modules/markup/markdown/convertyaml.go @@ -34,7 +34,7 @@ func nodeToTable(meta *yaml.Node) ast.Node { func mappingNodeToTable(meta *yaml.Node) ast.Node { table := east.NewTable() - alignments := []east.Alignment{} + alignments := make([]east.Alignment, 0, len(meta.Content)/2) for i := 0; i < len(meta.Content); i += 2 { alignments = append(alignments, east.AlignNone) } diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 50b438219b..816e93b700 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -34,16 +34,17 @@ type ASTTransformer struct{} // Transform transforms the given AST tree. func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { firstChild := node.FirstChild() - createTOC := false + tocMode := "" ctx := pc.Get(renderContextKey).(*markup.RenderContext) rc := pc.Get(renderConfigKey).(*RenderConfig) + + tocList := make([]markup.Header, 0, 20) if rc.yamlNode != nil { metaNode := rc.toMetaNode() if metaNode != nil { node.InsertBefore(node, firstChild, metaNode) } - createTOC = rc.TOC - ctx.TableOfContents = make([]markup.Header, 0, 100) + tocMode = rc.TOC } attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote]) @@ -59,15 +60,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) } } - text := n.Text(reader.Source()) + txt := n.Text(reader.Source()) header := markup.Header{ - Text: util.BytesToReadOnlyString(text), + Text: util.BytesToReadOnlyString(txt), Level: v.Level, } if id, found := v.AttributeString("id"); found { header.ID = util.BytesToReadOnlyString(id.([]byte)) } - ctx.TableOfContents = append(ctx.TableOfContents, header) + tocList = append(tocList, header) case *ast.Image: // Images need two things: // @@ -201,14 +202,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa return ast.WalkContinue, nil }) - if createTOC && len(ctx.TableOfContents) > 0 { - lang := rc.Lang - if len(lang) == 0 { - lang = setting.Langs[0] - } - tocNode := createTOCNode(ctx.TableOfContents, lang) - if tocNode != nil { + showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main" + showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar + if len(tocList) > 0 && (showTocInMain || showTocInSidebar) { + if showTocInMain { + tocNode := createTOCNode(tocList, rc.Lang, nil) node.InsertBefore(node, firstChild, tocNode) + } else { + tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"}) + ctx.SidebarTocNode = tocNode } } @@ -373,7 +375,11 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast. func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { var err error if entering { - _, err = w.WriteString("<details>") + if _, err = w.WriteString("<details"); err != nil { + return ast.WalkStop, err + } + html.RenderAttributes(w, node, nil) + _, err = w.WriteString(">") } else { _, err = w.WriteString("</details>") } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index f1ffea8872..d4a7195dc5 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -29,8 +29,8 @@ import ( ) var ( - converter goldmark.Markdown - once = sync.Once{} + specMarkdown goldmark.Markdown + specMarkdownOnce sync.Once ) var ( @@ -56,7 +56,7 @@ func (l *limitWriter) Write(data []byte) (int, error) { if err != nil { return n, err } - return n, fmt.Errorf("Rendered content too large - truncating render") + return n, fmt.Errorf("rendered content too large - truncating render") } n, err := l.w.Write(data) l.sum += int64(n) @@ -73,10 +73,10 @@ func newParserContext(ctx *markup.RenderContext) parser.Context { return pc } -// actualRender renders Markdown to HTML without handling special links. -func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { - once.Do(func() { - converter = goldmark.New( +// SpecializedMarkdown sets up the Gitea specific markdown extensions +func SpecializedMarkdown() goldmark.Markdown { + specMarkdownOnce.Do(func() { + specMarkdown = goldmark.New( goldmark.WithExtensions( extension.NewTable( extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)), @@ -139,13 +139,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) ) // Override the original Tasklist renderer! - converter.Renderer().AddOptions( + specMarkdown.Renderer().AddOptions( renderer.WithNodeRenderers( util.Prioritized(NewHTMLRenderer(), 10), ), ) }) + return specMarkdown +} +// actualRender renders Markdown to HTML without handling special links. +func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { + converter := SpecializedMarkdown() lw := &limitWriter{ w: output, limit: setting.UI.MaxDisplayFileSize * 3, @@ -174,7 +179,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) buf = giteautil.NormalizeEOL(buf) rc := &RenderConfig{ - Meta: "table", + Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)), Icon: "table", Lang: "", } diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go index f9c9cbc5f4..691df74312 100644 --- a/modules/markup/markdown/renderconfig.go +++ b/modules/markup/markdown/renderconfig.go @@ -7,32 +7,42 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/modules/markup" + "github.com/yuin/goldmark/ast" "gopkg.in/yaml.v3" ) // RenderConfig represents rendering configuration for this file type RenderConfig struct { - Meta string + Meta markup.RenderMetaMode Icon string - TOC bool + TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view Lang string yamlNode *yaml.Node } +func renderMetaModeFromString(s string) markup.RenderMetaMode { + switch strings.TrimSpace(strings.ToLower(s)) { + case "none": + return markup.RenderMetaAsNone + case "table": + return markup.RenderMetaAsTable + default: // "details" + return markup.RenderMetaAsDetails + } +} + // UnmarshalYAML implement yaml.v3 UnmarshalYAML func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { if rc == nil { - rc = &RenderConfig{ - Meta: "table", - Icon: "table", - Lang: "", - } + return nil } + rc.yamlNode = value type commonRenderConfig struct { - TOC bool `yaml:"include_toc"` + TOC string `yaml:"include_toc"` Lang string `yaml:"lang"` } var basic commonRenderConfig @@ -54,58 +64,45 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { if err := value.Decode(&stringBasic); err == nil { if stringBasic.Gitea != "" { - switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) { - case "none": - rc.Meta = "none" - case "table": - rc.Meta = "table" - default: // "details" - rc.Meta = "details" - } + rc.Meta = renderMetaModeFromString(stringBasic.Gitea) } return nil } - type giteaControl struct { + type yamlRenderConfig struct { Meta *string `yaml:"meta"` Icon *string `yaml:"details_icon"` - TOC *bool `yaml:"include_toc"` + TOC *string `yaml:"include_toc"` Lang *string `yaml:"lang"` } - type complexGiteaConfig struct { - Gitea *giteaControl `yaml:"gitea"` + type yamlRenderConfigWrapper struct { + Gitea *yamlRenderConfig `yaml:"gitea"` } - var complex complexGiteaConfig - if err := value.Decode(&complex); err != nil { - return fmt.Errorf("unable to decode into complexRenderConfig %w", err) + + var cfg yamlRenderConfigWrapper + if err := value.Decode(&cfg); err != nil { + return fmt.Errorf("unable to decode into yamlRenderConfigWrapper %w", err) } - if complex.Gitea == nil { + if cfg.Gitea == nil { return nil } - if complex.Gitea.Meta != nil { - switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) { - case "none": - rc.Meta = "none" - case "table": - rc.Meta = "table" - default: // "details" - rc.Meta = "details" - } + if cfg.Gitea.Meta != nil { + rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta) } - if complex.Gitea.Icon != nil { - rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon)) + if cfg.Gitea.Icon != nil { + rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon)) } - if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" { - rc.Lang = *complex.Gitea.Lang + if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" { + rc.Lang = *cfg.Gitea.Lang } - if complex.Gitea.TOC != nil { - rc.TOC = *complex.Gitea.TOC + if cfg.Gitea.TOC != nil { + rc.TOC = *cfg.Gitea.TOC } return nil @@ -116,9 +113,9 @@ func (rc *RenderConfig) toMetaNode() ast.Node { return nil } switch rc.Meta { - case "table": + case markup.RenderMetaAsTable: return nodeToTable(rc.yamlNode) - case "details": + case markup.RenderMetaAsDetails: return nodeToDetails(rc.yamlNode, rc.Icon) default: return nil diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go index f7f5e885a3..c53acdc77a 100644 --- a/modules/markup/markdown/renderconfig_test.go +++ b/modules/markup/markdown/renderconfig_test.go @@ -60,7 +60,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { }, { "toc", &RenderConfig{ - TOC: true, + TOC: "true", Meta: "table", Icon: "table", Lang: "", @@ -68,7 +68,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { }, { "tocfalse", &RenderConfig{ - TOC: false, + TOC: "false", Meta: "table", Icon: "table", Lang: "", @@ -78,7 +78,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "toclang", &RenderConfig{ Meta: "table", Icon: "table", - TOC: true, + TOC: "true", Lang: "testlang", }, ` include_toc: true @@ -120,7 +120,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "complex2", &RenderConfig{ Lang: "two", Meta: "table", - TOC: true, + TOC: "true", Icon: "smiley", }, ` lang: one @@ -155,7 +155,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang) } if got.TOC != tt.expected.TOC { - t.Errorf("TOC Expected %t Got %t", tt.expected.TOC, got.TOC) + t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC) } }) } diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index 3715f031af..9602040931 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -13,10 +13,14 @@ import ( "github.com/yuin/goldmark/ast" ) -func createTOCNode(toc []markup.Header, lang string) ast.Node { +func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]string) ast.Node { details := NewDetails() summary := NewSummary() + for k, v := range detailsAttrs { + details.SetAttributeString(k, []byte(v)) + } + summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc")))) details.AppendChild(details, summary) ul := ast.NewList('-') @@ -40,7 +44,7 @@ func createTOCNode(toc []markup.Header, lang string) ast.Node { } li := ast.NewListItem(currentLevel * 2) a := ast.NewLink() - a.Destination = []byte(fmt.Sprintf("#%s", url.PathEscape(header.ID))) + a.Destination = []byte(fmt.Sprintf("#%s", url.QueryEscape(header.ID))) a.AppendChild(a, ast.NewString([]byte(header.Text))) li.AppendChild(li, a) ul.AppendChild(ul, li) |