Skip to content

WIP: Katex rendering with yaml frontmatter control #20640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,12 @@ ROUTER = console
;; List of file extensions that should be rendered/edited as Markdown
;; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma
;FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
;;
;; Enables math inline and block detection
;ENABLE_MATH = true
;;
;; Enables in addition inline block detection using single dollars
;ENABLE_INLINE_DOLLAR_MATH = false

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Expand Down
2 changes: 2 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `CUSTOM_URL_SCHEMES`: Use a comma separated list (ftp,git,svn) to indicate additional
URL hyperlinks to be rendered in Markdown. URLs beginning in http and https are
always displayed
- `ENABLE_MATH`: **true**: Enables detection of `\(...\)`, `\[...\]` and `$$...$$` blocks as math blocks
- `ENABLE_INLINE_DOLLAR_MATH`: **false**: In addition enables detection of `$...$` as inline math.

## Server (`server`)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.66.4
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
mvdan.cc/xurls/v2 v2.4.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.11
Expand Down Expand Up @@ -287,7 +288,6 @@ require (
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

Expand Down
85 changes: 85 additions & 0 deletions modules/markup/markdown/config/convertyaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2022 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 config

import (
"code.gitea.io/gitea/modules/markup/markdown/extension"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"gopkg.in/yaml.v3"
)

func nodeToTable(meta *yaml.Node) ast.Node {
for {
if meta == nil {
return nil
}
switch meta.Kind {
case yaml.DocumentNode:
meta = meta.Content[0]
continue
default:
}
break
}
switch meta.Kind {
case yaml.MappingNode:
return mappingNodeToTable(meta)
case yaml.SequenceNode:
return sequenceNodeToTable(meta)
default:
return ast.NewString([]byte(meta.Value))
}
}

func mappingNodeToTable(meta *yaml.Node) ast.Node {
table := east.NewTable()
alignments := []east.Alignment{}
for i := 0; i < len(meta.Content); i += 2 {
alignments = append(alignments, east.AlignNone)
}

headerRow := east.NewTableRow(alignments)
valueRow := east.NewTableRow(alignments)
for i := 0; i < len(meta.Content); i += 2 {
cell := east.NewTableCell()

cell.AppendChild(cell, nodeToTable(meta.Content[i]))
headerRow.AppendChild(headerRow, cell)

if i+1 < len(meta.Content) {
cell = east.NewTableCell()
cell.AppendChild(cell, nodeToTable(meta.Content[i+1]))
valueRow.AppendChild(valueRow, cell)
}
}

table.AppendChild(table, east.NewTableHeader(headerRow))
table.AppendChild(table, valueRow)
return table
}

func sequenceNodeToTable(meta *yaml.Node) ast.Node {
table := east.NewTable()
alignments := []east.Alignment{east.AlignNone}
for _, item := range meta.Content {
row := east.NewTableRow(alignments)
cell := east.NewTableCell()
cell.AppendChild(cell, nodeToTable(item))
row.AppendChild(row, cell)
table.AppendChild(table, row)
}
return table
}

func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
details := extension.NewDetails()
summary := extension.NewSummary()
summary.AppendChild(summary, extension.NewIcon(icon))
details.AppendChild(details, summary)
details.AppendChild(details, nodeToTable(meta))

return details
}
208 changes: 208 additions & 0 deletions modules/markup/markdown/config/renderconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// 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 config

import (
"fmt"
"strings"

"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"gopkg.in/yaml.v3"
)

var renderConfigKey = parser.NewContextKey()

func GetRenderConfig(pc parser.Context) *RenderConfig {
return pc.Get(renderConfigKey).(*RenderConfig)
}

func SetRenderConfig(pc parser.Context, rc *RenderConfig) {
pc.Set(renderConfigKey, rc)
}

// RenderConfig represents rendering configuration for this file
type RenderConfig struct {
Meta string
Icon string
TOC bool
Lang string
Math *MathConfig
yamlNode *yaml.Node
}

type MathConfig struct {
InlineDollar bool `yaml:"inline_dollar"`
InlineLatex bool `yaml:"inline_latex"`
DisplayDollar bool `yaml:"display_dollar"`
DisplayLatex bool `yaml:"display_latex"`
}

// UnmarshalYAML implement yaml.v3 UnmarshalYAML
func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
rc.yamlNode = value

basic := &yamlRenderConfig{}
err := value.Decode(basic)
if err != nil {
return fmt.Errorf("failed to decode basic: %w", err)
}

if basic.Lang != "" {
rc.Lang = basic.Lang
}

rc.TOC = basic.TOC

if basic.Math != nil {
rc.Math = basic.Math
}

if basic.Gitea != nil {
if basic.Gitea.Meta != nil {
rc.Meta = *basic.Gitea.Meta
}
if basic.Gitea.Icon != nil {
rc.Icon = *basic.Gitea.Icon
}
if basic.Gitea.Lang != nil {
rc.Lang = *basic.Gitea.Lang
}
if basic.Gitea.TOC != nil {
rc.TOC = *basic.Gitea.TOC
}
if basic.Gitea.Math != nil {
rc.Math = basic.Gitea.Math
}
}

return nil
}

type yamlRenderConfig struct {
TOC bool `yaml:"include_toc"`
Lang string `yaml:"lang"`
Math *MathConfig `yaml:"math"`
Gitea *yamlGitea `yaml:"gitea"`
}

type yamlGitea struct {
Meta *string
Icon *string `yaml:"details_icon"`
TOC *bool `yaml:"include_toc"`
Lang *string
Math *MathConfig
}

func (y *yamlGitea) UnmarshalYAML(node *yaml.Node) error {
var controlString string
if err := node.Decode(&controlString); err == nil {
var meta string
switch strings.TrimSpace(strings.ToLower(controlString)) {
case "none":
meta = "none"
case "table":
meta = "table"
default: // "details"
meta = "details"
}
y.Meta = &meta
return nil
}

type yExactType yamlGitea
yExact := (*yExactType)(y)
if err := node.Decode(yExact); err != nil {
return fmt.Errorf("unable to parse yamlGitea: %w", err)
}

return nil
}

func (m *MathConfig) UnmarshalYAML(node *yaml.Node) error {
var controlBool bool
if err := node.Decode(&controlBool); err == nil {
m.InlineLatex = controlBool
m.DisplayLatex = controlBool
m.DisplayDollar = controlBool
// Not InlineDollar
m.InlineDollar = false
return nil
}

var enableMathStrs []string
if err := node.Decode(&enableMathStrs); err != nil {
var enableMathStr string
if err := node.Decode(&enableMathStr); err == nil {
m.InlineLatex = false
m.DisplayLatex = false
m.DisplayDollar = false
m.InlineDollar = false
if enableMathStr == "" {
enableMathStr = "true"
}
enableMathStrs = strings.Split(enableMathStr, ",")
}
}
if enableMathStrs != nil {
for _, value := range enableMathStrs {
value = strings.TrimSpace(strings.ToLower(value))
set := true
if value != "" && value[0] == '!' {
set = false
value = value[1:]
}
switch strings.TrimSpace(strings.ToLower(value)) {
case "none":
fallthrough
case "false":
m.InlineLatex = !set
m.DisplayLatex = !set
m.DisplayDollar = !set
m.InlineDollar = !set
case "all":
m.InlineLatex = set
m.DisplayLatex = set
m.DisplayDollar = set
m.InlineDollar = set
return nil
case "inline_dollar":
m.InlineDollar = set
case "inline_latex":
m.InlineLatex = set
case "display_dollar":
m.DisplayDollar = set
case "display_latex":
m.DisplayLatex = set
case "true":
m.InlineLatex = set
m.DisplayLatex = set
m.DisplayDollar = set
}
}
return nil
}

type mExactType MathConfig
mExact := (*mExactType)(m)
if err := node.Decode(mExact); err != nil {
return fmt.Errorf("unable to parse MathConfig: %w", err)
}
return nil
}

func (rc *RenderConfig) ToMetaNode() ast.Node {
if rc.yamlNode == nil {
return nil
}
switch rc.Meta {
case "table":
return nodeToTable(rc.yamlNode)
case "details":
return nodeToDetails(rc.yamlNode, rc.Icon)
default:
return nil
}
}
Loading