first-commit
This commit is contained in:
42
modules/markup/markdown/math/block_node.go
Normal file
42
modules/markup/markdown/math/block_node.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import "github.com/yuin/goldmark/ast"
|
||||
|
||||
// Block represents a display math block e.g. $$...$$ or \[...\]
|
||||
type Block struct {
|
||||
ast.BaseBlock
|
||||
Dollars bool
|
||||
Indent int
|
||||
Closed bool
|
||||
Inline bool
|
||||
}
|
||||
|
||||
// KindBlock is the node kind for math blocks
|
||||
var KindBlock = ast.NewNodeKind("MathBlock")
|
||||
|
||||
// NewBlock creates a new math Block
|
||||
func NewBlock(dollars bool, indent int) *Block {
|
||||
return &Block{
|
||||
Dollars: dollars,
|
||||
Indent: indent,
|
||||
}
|
||||
}
|
||||
|
||||
// Dump dumps the block to a string
|
||||
func (n *Block) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
ast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
// Kind returns KindBlock for math Blocks
|
||||
func (n *Block) Kind() ast.NodeKind {
|
||||
return KindBlock
|
||||
}
|
||||
|
||||
// IsRaw returns true as this block should not be processed further
|
||||
func (n *Block) IsRaw() bool {
|
||||
return true
|
||||
}
|
136
modules/markup/markdown/math/block_parser.go
Normal file
136
modules/markup/markdown/math/block_parser.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
type blockParser struct {
|
||||
parseDollars bool
|
||||
parseSquare bool
|
||||
endBytesDollars []byte
|
||||
endBytesSquare []byte
|
||||
}
|
||||
|
||||
// NewBlockParser creates a new math BlockParser
|
||||
func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser {
|
||||
return &blockParser{
|
||||
parseDollars: parseDollars,
|
||||
parseSquare: parseSquare,
|
||||
endBytesDollars: []byte{'$', '$'},
|
||||
endBytesSquare: []byte{'\\', ']'},
|
||||
}
|
||||
}
|
||||
|
||||
// Open parses the current line and returns a result of parsing.
|
||||
func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
|
||||
line, segment := reader.PeekLine()
|
||||
pos := pc.BlockOffset()
|
||||
if pos == -1 || len(line[pos:]) < 2 {
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
|
||||
var dollars bool
|
||||
if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
|
||||
dollars = true
|
||||
} else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' {
|
||||
if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
|
||||
// do not process escaped attention block: "> \[!NOTE\]"
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
dollars = false
|
||||
} else {
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
|
||||
node := NewBlock(dollars, pos)
|
||||
|
||||
// Now we need to check if the ending block is on the segment...
|
||||
endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare)
|
||||
idx := bytes.Index(line[pos+2:], endBytes)
|
||||
if idx >= 0 {
|
||||
// for case: "$$ ... $$ any other text" (this case will be handled by the inline parser)
|
||||
for i := pos + 2 + idx + 2; i < len(line); i++ {
|
||||
if line[i] != ' ' && line[i] != '\n' {
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
}
|
||||
segment.Start += pos + 2
|
||||
segment.Stop = segment.Start + idx
|
||||
node.Lines().Append(segment)
|
||||
node.Closed = true
|
||||
node.Inline = true
|
||||
return node, parser.Close | parser.NoChildren
|
||||
}
|
||||
|
||||
// for case "\[ ... ]" (no close marker on the same line)
|
||||
for i := pos + 2 + idx + 2; i < len(line); i++ {
|
||||
if line[i] != ' ' && line[i] != '\n' {
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
}
|
||||
|
||||
segment.Start += pos + 2
|
||||
node.Lines().Append(segment)
|
||||
return node, parser.NoChildren
|
||||
}
|
||||
|
||||
// Continue parses the current line and returns a result of parsing.
|
||||
func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||
block := node.(*Block)
|
||||
if block.Closed {
|
||||
return parser.Close
|
||||
}
|
||||
|
||||
line, segment := reader.PeekLine()
|
||||
w, pos := util.IndentWidth(line, reader.LineOffset())
|
||||
if w < 4 {
|
||||
endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare)
|
||||
if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
|
||||
if util.IsBlank(line[pos+len(endBytes):]) {
|
||||
newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
|
||||
reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
|
||||
return parser.Close
|
||||
}
|
||||
}
|
||||
}
|
||||
start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
|
||||
seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
|
||||
node.Lines().Append(seg)
|
||||
return parser.Continue | parser.NoChildren
|
||||
}
|
||||
|
||||
// Close will be called when the parser returns Close.
|
||||
func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// CanInterruptParagraph returns true if the parser can interrupt paragraphs,
|
||||
// otherwise false.
|
||||
func (b *blockParser) CanInterruptParagraph() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CanAcceptIndentedLine returns true if the parser can open new node when
|
||||
// the given line is being indented more than 3 spaces.
|
||||
func (b *blockParser) CanAcceptIndentedLine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Trigger returns a list of characters that triggers Parse method of
|
||||
// this parser.
|
||||
// If Trigger returns a nil, Open will be called with any lines.
|
||||
//
|
||||
// We leave this as nil as our parse method is quick enough
|
||||
func (b *blockParser) Trigger() []byte {
|
||||
return nil
|
||||
}
|
61
modules/markup/markdown/math/block_renderer.go
Normal file
61
modules/markup/markdown/math/block_renderer.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Block render output:
|
||||
// <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
|
||||
//
|
||||
// Keep in mind that there is another "code block" render in "func (r *GlodmarkRender) highlightingRenderer"
|
||||
// "highlightingRenderer" outputs the math block with extra "chroma" class:
|
||||
// <pre class="code-block is-loading"><code class="chroma language-math display">...</code></pre>
|
||||
//
|
||||
// Special classes:
|
||||
// * "is-loading": show a loading indicator
|
||||
// * "display": used by JS to decide to render as a block, otherwise render as inline
|
||||
|
||||
// BlockRenderer represents a renderer for math Blocks
|
||||
type BlockRenderer struct {
|
||||
renderInternal *internal.RenderInternal
|
||||
}
|
||||
|
||||
// NewBlockRenderer creates a new renderer for math Blocks
|
||||
func NewBlockRenderer(renderInternal *internal.RenderInternal) renderer.NodeRenderer {
|
||||
return &BlockRenderer{renderInternal: renderInternal}
|
||||
}
|
||||
|
||||
// RegisterFuncs registers the renderer for math Blocks
|
||||
func (r *BlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(KindBlock, r.renderBlock)
|
||||
}
|
||||
|
||||
func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) {
|
||||
l := n.Lines().Len()
|
||||
for i := range l {
|
||||
line := n.Lines().At(i)
|
||||
_, _ = w.Write(util.EscapeHTML(line.Value(source)))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
n := node.(*Block)
|
||||
if entering {
|
||||
codeHTML := giteaUtil.Iif[template.HTML](n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
|
||||
_, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(codeHTML)))
|
||||
r.writeLines(w, source, n)
|
||||
} else {
|
||||
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
48
modules/markup/markdown/math/inline_node.go
Normal file
48
modules/markup/markdown/math/inline_node.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Inline struct represents inline math e.g. $...$ or \(...\)
|
||||
type Inline struct {
|
||||
ast.BaseInline
|
||||
}
|
||||
|
||||
// Inline implements Inline.Inline.
|
||||
func (n *Inline) Inline() {}
|
||||
|
||||
// IsBlank returns if this inline node is empty
|
||||
func (n *Inline) IsBlank(source []byte) bool {
|
||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
text := c.(*ast.Text).Segment
|
||||
if !util.IsBlank(text.Value(source)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Dump renders this inline math as debug
|
||||
func (n *Inline) Dump(source []byte, level int) {
|
||||
ast.DumpHelper(n, source, level, nil, nil)
|
||||
}
|
||||
|
||||
// KindInline is the kind for math inline
|
||||
var KindInline = ast.NewNodeKind("MathInline")
|
||||
|
||||
// Kind returns KindInline
|
||||
func (n *Inline) Kind() ast.NodeKind {
|
||||
return KindInline
|
||||
}
|
||||
|
||||
// NewInline creates a new ast math inline node
|
||||
func NewInline() *Inline {
|
||||
return &Inline{
|
||||
BaseInline: ast.BaseInline{},
|
||||
}
|
||||
}
|
169
modules/markup/markdown/math/inline_parser.go
Normal file
169
modules/markup/markdown/math/inline_parser.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
type inlineParser struct {
|
||||
trigger []byte
|
||||
endBytesSingleDollar []byte
|
||||
endBytesDoubleDollar []byte
|
||||
endBytesParentheses []byte
|
||||
enableInlineDollar bool
|
||||
}
|
||||
|
||||
func NewInlineDollarParser(enableInlineDollar bool) parser.InlineParser {
|
||||
return &inlineParser{
|
||||
trigger: []byte{'$'},
|
||||
endBytesSingleDollar: []byte{'$'},
|
||||
endBytesDoubleDollar: []byte{'$', '$'},
|
||||
enableInlineDollar: enableInlineDollar,
|
||||
}
|
||||
}
|
||||
|
||||
var defaultInlineParenthesesParser = &inlineParser{
|
||||
trigger: []byte{'\\', '('},
|
||||
endBytesParentheses: []byte{'\\', ')'},
|
||||
}
|
||||
|
||||
func NewInlineParenthesesParser() parser.InlineParser {
|
||||
return defaultInlineParenthesesParser
|
||||
}
|
||||
|
||||
// Trigger triggers this parser on $ or \
|
||||
func (parser *inlineParser) Trigger() []byte {
|
||||
return parser.trigger
|
||||
}
|
||||
|
||||
func isPunctuation(b byte) bool {
|
||||
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
|
||||
}
|
||||
|
||||
func isParenthesesClose(b byte) bool {
|
||||
return b == ')'
|
||||
}
|
||||
|
||||
func isAlphanumeric(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
||||
}
|
||||
|
||||
// Parse parses the current line and returns a result of parsing.
|
||||
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
|
||||
if !bytes.HasPrefix(line, parser.trigger) {
|
||||
// We'll catch this one on the next time round
|
||||
return nil
|
||||
}
|
||||
|
||||
var startMarkLen int
|
||||
var stopMark []byte
|
||||
checkSurrounding := true
|
||||
if line[0] == '$' {
|
||||
startMarkLen = 1
|
||||
stopMark = parser.endBytesSingleDollar
|
||||
if len(line) > 1 {
|
||||
switch line[1] {
|
||||
case '$':
|
||||
startMarkLen = 2
|
||||
stopMark = parser.endBytesDoubleDollar
|
||||
case '`':
|
||||
pos := 1
|
||||
for ; pos < len(line) && line[pos] == '`'; pos++ {
|
||||
}
|
||||
startMarkLen = pos
|
||||
stopMark = bytes.Repeat([]byte{'`'}, pos)
|
||||
stopMark[len(stopMark)-1] = '$'
|
||||
checkSurrounding = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startMarkLen = 2
|
||||
stopMark = parser.endBytesParentheses
|
||||
}
|
||||
|
||||
if line[0] == '$' && !parser.enableInlineDollar && (len(line) == 1 || line[1] != '`') {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkSurrounding {
|
||||
precedingCharacter := block.PrecendingCharacter()
|
||||
if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
|
||||
// need to exclude things like `a$` from being considered a start
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// move the opener marker point at the start of the text
|
||||
opener := startMarkLen
|
||||
|
||||
// Now look for an ending line
|
||||
depth := 0
|
||||
ender := -1
|
||||
for i := opener; i < len(line); i++ {
|
||||
if depth == 0 && bytes.HasPrefix(line[i:], stopMark) {
|
||||
succeedingCharacter := byte(0)
|
||||
if i+len(stopMark) < len(line) {
|
||||
succeedingCharacter = line[i+len(stopMark)]
|
||||
}
|
||||
// check valid ending character
|
||||
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
|
||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
|
||||
if checkSurrounding && !isValidEndingChar {
|
||||
break
|
||||
}
|
||||
ender = i
|
||||
break
|
||||
}
|
||||
if line[i] == '\\' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
switch line[i] {
|
||||
case '{':
|
||||
depth++
|
||||
case '}':
|
||||
depth--
|
||||
}
|
||||
}
|
||||
if ender == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
block.Advance(opener)
|
||||
_, pos := block.Position()
|
||||
node := NewInline()
|
||||
|
||||
segment := pos.WithStop(pos.Start + ender - opener)
|
||||
node.AppendChild(node, ast.NewRawTextSegment(segment))
|
||||
block.Advance(ender - opener + len(stopMark))
|
||||
trimBlock(node, block)
|
||||
return node
|
||||
}
|
||||
|
||||
func trimBlock(node *Inline, block text.Reader) {
|
||||
if node.IsBlank(block.Source()) {
|
||||
return
|
||||
}
|
||||
|
||||
// trim first space and last space
|
||||
first := node.FirstChild().(*ast.Text)
|
||||
if !(!first.Segment.IsEmpty() && block.Source()[first.Segment.Start] == ' ') {
|
||||
return
|
||||
}
|
||||
|
||||
last := node.LastChild().(*ast.Text)
|
||||
if !(!last.Segment.IsEmpty() && block.Source()[last.Segment.Stop-1] == ' ') {
|
||||
return
|
||||
}
|
||||
|
||||
first.Segment = first.Segment.WithStart(first.Segment.Start + 1)
|
||||
last.Segment = last.Segment.WithStop(last.Segment.Stop - 1)
|
||||
}
|
53
modules/markup/markdown/math/inline_renderer.go
Normal file
53
modules/markup/markdown/math/inline_renderer.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Inline render output:
|
||||
// <code class="language-math">...</code>
|
||||
|
||||
// InlineRenderer is an inline renderer
|
||||
type InlineRenderer struct {
|
||||
renderInternal *internal.RenderInternal
|
||||
}
|
||||
|
||||
// NewInlineRenderer returns a new renderer for inline math
|
||||
func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRenderer {
|
||||
return &InlineRenderer{renderInternal: renderInternal}
|
||||
}
|
||||
|
||||
func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if entering {
|
||||
_, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(`<code class="language-math">`)))
|
||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
segment := c.(*ast.Text).Segment
|
||||
value := util.EscapeHTML(segment.Value(source))
|
||||
if bytes.HasSuffix(value, []byte("\n")) {
|
||||
_, _ = w.Write(value[:len(value)-1])
|
||||
if c != n.LastChild() {
|
||||
_, _ = w.Write([]byte(" "))
|
||||
}
|
||||
} else {
|
||||
_, _ = w.Write(value)
|
||||
}
|
||||
}
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
_, _ = w.WriteString(`</code>`)
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
// RegisterFuncs registers the renderer for inline math nodes
|
||||
func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(KindInline, r.renderInline)
|
||||
}
|
60
modules/markup/markdown/math/math.go
Normal file
60
modules/markup/markdown/math/math.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Enabled bool
|
||||
ParseInlineDollar bool // inline $$ xxx $$ text
|
||||
ParseInlineParentheses bool // inline \( xxx \) text
|
||||
ParseBlockDollar bool // block $$ multiple-line $$ text
|
||||
ParseBlockSquareBrackets bool // block \[ multiple-line \] text
|
||||
}
|
||||
|
||||
// Extension is a math extension
|
||||
type Extension struct {
|
||||
renderInternal *internal.RenderInternal
|
||||
options Options
|
||||
}
|
||||
|
||||
// NewExtension creates a new math extension with the provided options
|
||||
func NewExtension(renderInternal *internal.RenderInternal, opts ...Options) *Extension {
|
||||
opt := giteaUtil.OptionalArg(opts)
|
||||
r := &Extension{
|
||||
renderInternal: renderInternal,
|
||||
options: opt,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Extend extends goldmark with our parsers and renderers
|
||||
func (e *Extension) Extend(m goldmark.Markdown) {
|
||||
if !e.options.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
var inlines []util.PrioritizedValue
|
||||
if e.options.ParseInlineParentheses {
|
||||
inlines = append(inlines, util.Prioritized(NewInlineParenthesesParser(), 501))
|
||||
}
|
||||
inlines = append(inlines, util.Prioritized(NewInlineDollarParser(e.options.ParseInlineDollar), 502))
|
||||
|
||||
m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
|
||||
m.Parser().AddOptions(parser.WithBlockParsers(
|
||||
util.Prioritized(NewBlockParser(e.options.ParseBlockDollar, e.options.ParseBlockSquareBrackets), 701),
|
||||
))
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
|
||||
util.Prioritized(NewInlineRenderer(e.renderInternal), 502),
|
||||
))
|
||||
}
|
Reference in New Issue
Block a user