Use markdown frontmatter to provide Table of contents, language and frontmatter rendering (#11047)
* Add control for the rendering of the frontmatter * Add control to include a TOC * Add control to set language - allows control of ToC header and CJK glyph choice. Signed-off-by: Andrew Thornton art27@cantab.nettokarchuk/v1.17
parent
d3fc9c08c8
commit
812cfd0ad9
@ -0,0 +1,107 @@ |
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markdown |
||||
|
||||
import "github.com/yuin/goldmark/ast" |
||||
|
||||
// Details is a block that contains Summary and details
|
||||
type Details struct { |
||||
ast.BaseBlock |
||||
} |
||||
|
||||
// Dump implements Node.Dump .
|
||||
func (n *Details) Dump(source []byte, level int) { |
||||
ast.DumpHelper(n, source, level, nil, nil) |
||||
} |
||||
|
||||
// KindDetails is the NodeKind for Details
|
||||
var KindDetails = ast.NewNodeKind("Details") |
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *Details) Kind() ast.NodeKind { |
||||
return KindDetails |
||||
} |
||||
|
||||
// NewDetails returns a new Paragraph node.
|
||||
func NewDetails() *Details { |
||||
return &Details{ |
||||
BaseBlock: ast.BaseBlock{}, |
||||
} |
||||
} |
||||
|
||||
// IsDetails returns true if the given node implements the Details interface,
|
||||
// otherwise false.
|
||||
func IsDetails(node ast.Node) bool { |
||||
_, ok := node.(*Details) |
||||
return ok |
||||
} |
||||
|
||||
// Summary is a block that contains the summary of details block
|
||||
type Summary struct { |
||||
ast.BaseBlock |
||||
} |
||||
|
||||
// Dump implements Node.Dump .
|
||||
func (n *Summary) Dump(source []byte, level int) { |
||||
ast.DumpHelper(n, source, level, nil, nil) |
||||
} |
||||
|
||||
// KindSummary is the NodeKind for Summary
|
||||
var KindSummary = ast.NewNodeKind("Summary") |
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *Summary) Kind() ast.NodeKind { |
||||
return KindSummary |
||||
} |
||||
|
||||
// NewSummary returns a new Summary node.
|
||||
func NewSummary() *Summary { |
||||
return &Summary{ |
||||
BaseBlock: ast.BaseBlock{}, |
||||
} |
||||
} |
||||
|
||||
// IsSummary returns true if the given node implements the Summary interface,
|
||||
// otherwise false.
|
||||
func IsSummary(node ast.Node) bool { |
||||
_, ok := node.(*Summary) |
||||
return ok |
||||
} |
||||
|
||||
// Icon is an inline for a fomantic icon
|
||||
type Icon struct { |
||||
ast.BaseInline |
||||
Name []byte |
||||
} |
||||
|
||||
// Dump implements Node.Dump .
|
||||
func (n *Icon) Dump(source []byte, level int) { |
||||
m := map[string]string{} |
||||
m["Name"] = string(n.Name) |
||||
ast.DumpHelper(n, source, level, m, nil) |
||||
} |
||||
|
||||
// KindIcon is the NodeKind for Icon
|
||||
var KindIcon = ast.NewNodeKind("Icon") |
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *Icon) Kind() ast.NodeKind { |
||||
return KindIcon |
||||
} |
||||
|
||||
// NewIcon returns a new Paragraph node.
|
||||
func NewIcon(name string) *Icon { |
||||
return &Icon{ |
||||
BaseInline: ast.BaseInline{}, |
||||
Name: []byte(name), |
||||
} |
||||
} |
||||
|
||||
// IsIcon returns true if the given node implements the Icon interface,
|
||||
// otherwise false.
|
||||
func IsIcon(node ast.Node) bool { |
||||
_, ok := node.(*Icon) |
||||
return ok |
||||
} |
@ -0,0 +1,163 @@ |
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markdown |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/yuin/goldmark/ast" |
||||
east "github.com/yuin/goldmark/extension/ast" |
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
// RenderConfig represents rendering configuration for this file
|
||||
type RenderConfig struct { |
||||
Meta string |
||||
Icon string |
||||
TOC bool |
||||
Lang string |
||||
} |
||||
|
||||
// ToRenderConfig converts a yaml.MapSlice to a RenderConfig
|
||||
func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) { |
||||
if meta == nil { |
||||
return |
||||
} |
||||
found := false |
||||
var giteaMetaControl yaml.MapItem |
||||
for _, item := range meta { |
||||
strKey, ok := item.Key.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
strKey = strings.TrimSpace(strings.ToLower(strKey)) |
||||
switch strKey { |
||||
case "gitea": |
||||
giteaMetaControl = item |
||||
found = true |
||||
case "include_toc": |
||||
val, ok := item.Value.(bool) |
||||
if !ok { |
||||
continue |
||||
} |
||||
rc.TOC = val |
||||
case "lang": |
||||
val, ok := item.Value.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
val = strings.TrimSpace(val) |
||||
if len(val) == 0 { |
||||
continue |
||||
} |
||||
rc.Lang = val |
||||
} |
||||
} |
||||
|
||||
if found { |
||||
switch v := giteaMetaControl.Value.(type) { |
||||
case string: |
||||
switch v { |
||||
case "none": |
||||
rc.Meta = "none" |
||||
case "table": |
||||
rc.Meta = "table" |
||||
default: // "details"
|
||||
rc.Meta = "details" |
||||
} |
||||
case yaml.MapSlice: |
||||
for _, item := range v { |
||||
strKey, ok := item.Key.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
strKey = strings.TrimSpace(strings.ToLower(strKey)) |
||||
switch strKey { |
||||
case "meta": |
||||
val, ok := item.Value.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
switch strings.TrimSpace(strings.ToLower(val)) { |
||||
case "none": |
||||
rc.Meta = "none" |
||||
case "table": |
||||
rc.Meta = "table" |
||||
default: // "details"
|
||||
rc.Meta = "details" |
||||
} |
||||
case "details_icon": |
||||
val, ok := item.Value.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
rc.Icon = strings.TrimSpace(strings.ToLower(val)) |
||||
case "include_toc": |
||||
val, ok := item.Value.(bool) |
||||
if !ok { |
||||
continue |
||||
} |
||||
rc.TOC = val |
||||
case "lang": |
||||
val, ok := item.Value.(string) |
||||
if !ok { |
||||
continue |
||||
} |
||||
val = strings.TrimSpace(val) |
||||
if len(val) == 0 { |
||||
continue |
||||
} |
||||
rc.Lang = val |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (rc *RenderConfig) toMetaNode(meta yaml.MapSlice) ast.Node { |
||||
switch rc.Meta { |
||||
case "table": |
||||
return metaToTable(meta) |
||||
case "details": |
||||
return metaToDetails(meta, rc.Icon) |
||||
default: |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func metaToTable(meta yaml.MapSlice) ast.Node { |
||||
table := east.NewTable() |
||||
alignments := []east.Alignment{} |
||||
for range meta { |
||||
alignments = append(alignments, east.AlignNone) |
||||
} |
||||
row := east.NewTableRow(alignments) |
||||
for _, item := range meta { |
||||
cell := east.NewTableCell() |
||||
cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Key)))) |
||||
row.AppendChild(row, cell) |
||||
} |
||||
table.AppendChild(table, east.NewTableHeader(row)) |
||||
|
||||
row = east.NewTableRow(alignments) |
||||
for _, item := range meta { |
||||
cell := east.NewTableCell() |
||||
cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Value)))) |
||||
row.AppendChild(row, cell) |
||||
} |
||||
table.AppendChild(table, row) |
||||
return table |
||||
} |
||||
|
||||
func metaToDetails(meta yaml.MapSlice, icon string) ast.Node { |
||||
details := NewDetails() |
||||
summary := NewSummary() |
||||
summary.AppendChild(summary, NewIcon(icon)) |
||||
details.AppendChild(details, summary) |
||||
details.AppendChild(details, metaToTable(meta)) |
||||
|
||||
return details |
||||
} |
@ -0,0 +1,49 @@ |
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markdown |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/url" |
||||
|
||||
"github.com/unknwon/i18n" |
||||
"github.com/yuin/goldmark/ast" |
||||
) |
||||
|
||||
func createTOCNode(toc []Header, lang string) ast.Node { |
||||
details := NewDetails() |
||||
summary := NewSummary() |
||||
|
||||
summary.AppendChild(summary, ast.NewString([]byte(i18n.Tr(lang, "toc")))) |
||||
details.AppendChild(details, summary) |
||||
ul := ast.NewList('-') |
||||
details.AppendChild(details, ul) |
||||
currentLevel := 6 |
||||
for _, header := range toc { |
||||
if header.Level < currentLevel { |
||||
currentLevel = header.Level |
||||
} |
||||
} |
||||
for _, header := range toc { |
||||
for currentLevel > header.Level { |
||||
ul = ul.Parent().(*ast.List) |
||||
currentLevel-- |
||||
} |
||||
for currentLevel < header.Level { |
||||
newL := ast.NewList('-') |
||||
ul.AppendChild(ul, newL) |
||||
currentLevel++ |
||||
ul = newL |
||||
} |
||||
li := ast.NewListItem(currentLevel * 2) |
||||
a := ast.NewLink() |
||||
a.Destination = []byte(fmt.Sprintf("#%s", url.PathEscape(header.ID))) |
||||
a.AppendChild(a, ast.NewString([]byte(header.Text))) |
||||
li.AppendChild(li, a) |
||||
ul.AppendChild(ul, li) |
||||
} |
||||
|
||||
return details |
||||
} |
Loading…
Reference in new issue