mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 04:48:25 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			494 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			494 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 Yusuke Inuzuka
 | 
						|
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
// Most of what follows is a subtly changed version of github.com/yuin/goldmark/extension/footnote.go
 | 
						|
 | 
						|
package common
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
	"unicode"
 | 
						|
 | 
						|
	"github.com/yuin/goldmark"
 | 
						|
	"github.com/yuin/goldmark/ast"
 | 
						|
	"github.com/yuin/goldmark/parser"
 | 
						|
	"github.com/yuin/goldmark/renderer"
 | 
						|
	"github.com/yuin/goldmark/renderer/html"
 | 
						|
	"github.com/yuin/goldmark/text"
 | 
						|
	"github.com/yuin/goldmark/util"
 | 
						|
)
 | 
						|
 | 
						|
// CleanValue will clean a value to make it safe to be an id
 | 
						|
// This function is quite different from the original goldmark function
 | 
						|
// and more closely matches the output from the shurcooL sanitizer
 | 
						|
// In particular Unicode letters and numbers are a lot more than a-zA-Z0-9...
 | 
						|
func CleanValue(value []byte) []byte {
 | 
						|
	value = bytes.TrimSpace(value)
 | 
						|
	rs := bytes.Runes(value)
 | 
						|
	result := make([]rune, 0, len(rs))
 | 
						|
	for _, r := range rs {
 | 
						|
		if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '-' {
 | 
						|
			result = append(result, unicode.ToLower(r))
 | 
						|
		}
 | 
						|
		if unicode.IsSpace(r) {
 | 
						|
			result = append(result, '-')
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return []byte(string(result))
 | 
						|
}
 | 
						|
 | 
						|
// Most of what follows is a subtly changed version of github.com/yuin/goldmark/extension/footnote.go
 | 
						|
 | 
						|
// A FootnoteLink struct represents a link to a footnote of Markdown
 | 
						|
// (PHP Markdown Extra) text.
 | 
						|
type FootnoteLink struct {
 | 
						|
	ast.BaseInline
 | 
						|
	Index int
 | 
						|
	Name  []byte
 | 
						|
}
 | 
						|
 | 
						|
// Dump implements Node.Dump.
 | 
						|
func (n *FootnoteLink) Dump(source []byte, level int) {
 | 
						|
	m := map[string]string{}
 | 
						|
	m["Index"] = fmt.Sprintf("%v", n.Index)
 | 
						|
	m["Name"] = fmt.Sprintf("%v", n.Name)
 | 
						|
	ast.DumpHelper(n, source, level, m, nil)
 | 
						|
}
 | 
						|
 | 
						|
// KindFootnoteLink is a NodeKind of the FootnoteLink node.
 | 
						|
var KindFootnoteLink = ast.NewNodeKind("GiteaFootnoteLink")
 | 
						|
 | 
						|
// Kind implements Node.Kind.
 | 
						|
func (n *FootnoteLink) Kind() ast.NodeKind {
 | 
						|
	return KindFootnoteLink
 | 
						|
}
 | 
						|
 | 
						|
// NewFootnoteLink returns a new FootnoteLink node.
 | 
						|
func NewFootnoteLink(index int, name []byte) *FootnoteLink {
 | 
						|
	return &FootnoteLink{
 | 
						|
		Index: index,
 | 
						|
		Name:  name,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// A FootnoteBackLink struct represents a link to a footnote of Markdown
 | 
						|
// (PHP Markdown Extra) text.
 | 
						|
type FootnoteBackLink struct {
 | 
						|
	ast.BaseInline
 | 
						|
	Index int
 | 
						|
	Name  []byte
 | 
						|
}
 | 
						|
 | 
						|
// Dump implements Node.Dump.
 | 
						|
func (n *FootnoteBackLink) Dump(source []byte, level int) {
 | 
						|
	m := map[string]string{}
 | 
						|
	m["Index"] = fmt.Sprintf("%v", n.Index)
 | 
						|
	m["Name"] = fmt.Sprintf("%v", n.Name)
 | 
						|
	ast.DumpHelper(n, source, level, m, nil)
 | 
						|
}
 | 
						|
 | 
						|
// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node.
 | 
						|
var KindFootnoteBackLink = ast.NewNodeKind("GiteaFootnoteBackLink")
 | 
						|
 | 
						|
// Kind implements Node.Kind.
 | 
						|
func (n *FootnoteBackLink) Kind() ast.NodeKind {
 | 
						|
	return KindFootnoteBackLink
 | 
						|
}
 | 
						|
 | 
						|
// NewFootnoteBackLink returns a new FootnoteBackLink node.
 | 
						|
func NewFootnoteBackLink(index int, name []byte) *FootnoteBackLink {
 | 
						|
	return &FootnoteBackLink{
 | 
						|
		Index: index,
 | 
						|
		Name:  name,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// A Footnote struct represents a footnote of Markdown
 | 
						|
// (PHP Markdown Extra) text.
 | 
						|
type Footnote struct {
 | 
						|
	ast.BaseBlock
 | 
						|
	Ref   []byte
 | 
						|
	Index int
 | 
						|
	Name  []byte
 | 
						|
}
 | 
						|
 | 
						|
// Dump implements Node.Dump.
 | 
						|
func (n *Footnote) Dump(source []byte, level int) {
 | 
						|
	m := map[string]string{}
 | 
						|
	m["Index"] = strconv.Itoa(n.Index)
 | 
						|
	m["Ref"] = string(n.Ref)
 | 
						|
	m["Name"] = string(n.Name)
 | 
						|
	ast.DumpHelper(n, source, level, m, nil)
 | 
						|
}
 | 
						|
 | 
						|
// KindFootnote is a NodeKind of the Footnote node.
 | 
						|
var KindFootnote = ast.NewNodeKind("GiteaFootnote")
 | 
						|
 | 
						|
// Kind implements Node.Kind.
 | 
						|
func (n *Footnote) Kind() ast.NodeKind {
 | 
						|
	return KindFootnote
 | 
						|
}
 | 
						|
 | 
						|
// NewFootnote returns a new Footnote node.
 | 
						|
func NewFootnote(ref []byte) *Footnote {
 | 
						|
	return &Footnote{
 | 
						|
		Ref:   ref,
 | 
						|
		Index: -1,
 | 
						|
		Name:  ref,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// A FootnoteList struct represents footnotes of Markdown
 | 
						|
// (PHP Markdown Extra) text.
 | 
						|
type FootnoteList struct {
 | 
						|
	ast.BaseBlock
 | 
						|
	Count int
 | 
						|
}
 | 
						|
 | 
						|
// Dump implements Node.Dump.
 | 
						|
func (n *FootnoteList) Dump(source []byte, level int) {
 | 
						|
	m := map[string]string{}
 | 
						|
	m["Count"] = fmt.Sprintf("%v", n.Count)
 | 
						|
	ast.DumpHelper(n, source, level, m, nil)
 | 
						|
}
 | 
						|
 | 
						|
// KindFootnoteList is a NodeKind of the FootnoteList node.
 | 
						|
var KindFootnoteList = ast.NewNodeKind("GiteaFootnoteList")
 | 
						|
 | 
						|
// Kind implements Node.Kind.
 | 
						|
func (n *FootnoteList) Kind() ast.NodeKind {
 | 
						|
	return KindFootnoteList
 | 
						|
}
 | 
						|
 | 
						|
// NewFootnoteList returns a new FootnoteList node.
 | 
						|
func NewFootnoteList() *FootnoteList {
 | 
						|
	return &FootnoteList{
 | 
						|
		Count: 0,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var footnoteListKey = parser.NewContextKey()
 | 
						|
 | 
						|
type footnoteBlockParser struct{}
 | 
						|
 | 
						|
var defaultFootnoteBlockParser = &footnoteBlockParser{}
 | 
						|
 | 
						|
// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
 | 
						|
// footnotes of the Markdown(PHP Markdown Extra) text.
 | 
						|
func NewFootnoteBlockParser() parser.BlockParser {
 | 
						|
	return defaultFootnoteBlockParser
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) Trigger() []byte {
 | 
						|
	return []byte{'['}
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
 | 
						|
	line, segment := reader.PeekLine()
 | 
						|
	pos := pc.BlockOffset()
 | 
						|
	if pos < 0 || line[pos] != '[' {
 | 
						|
		return nil, parser.NoChildren
 | 
						|
	}
 | 
						|
	pos++
 | 
						|
	if pos > len(line)-1 || line[pos] != '^' {
 | 
						|
		return nil, parser.NoChildren
 | 
						|
	}
 | 
						|
	open := pos + 1
 | 
						|
	closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint
 | 
						|
	closes := pos + 1 + closure
 | 
						|
	next := closes + 1
 | 
						|
	if closure > -1 {
 | 
						|
		if next >= len(line) || line[next] != ':' {
 | 
						|
			return nil, parser.NoChildren
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		return nil, parser.NoChildren
 | 
						|
	}
 | 
						|
	padding := segment.Padding
 | 
						|
	label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
 | 
						|
	if util.IsBlank(label) {
 | 
						|
		return nil, parser.NoChildren
 | 
						|
	}
 | 
						|
	item := NewFootnote(label)
 | 
						|
 | 
						|
	pos = next + 1 - padding
 | 
						|
	if pos >= len(line) {
 | 
						|
		reader.Advance(pos)
 | 
						|
		return item, parser.NoChildren
 | 
						|
	}
 | 
						|
	reader.AdvanceAndSetPadding(pos, padding)
 | 
						|
	return item, parser.HasChildren
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
 | 
						|
	line, _ := reader.PeekLine()
 | 
						|
	if util.IsBlank(line) {
 | 
						|
		return parser.Continue | parser.HasChildren
 | 
						|
	}
 | 
						|
	childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
 | 
						|
	if childpos < 0 {
 | 
						|
		return parser.Close
 | 
						|
	}
 | 
						|
	reader.AdvanceAndSetPadding(childpos, padding)
 | 
						|
	return parser.Continue | parser.HasChildren
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
 | 
						|
	var list *FootnoteList
 | 
						|
	if tlist := pc.Get(footnoteListKey); tlist != nil {
 | 
						|
		list = tlist.(*FootnoteList)
 | 
						|
	} else {
 | 
						|
		list = NewFootnoteList()
 | 
						|
		pc.Set(footnoteListKey, list)
 | 
						|
		node.Parent().InsertBefore(node.Parent(), node, list)
 | 
						|
	}
 | 
						|
	node.Parent().RemoveChild(node.Parent(), node)
 | 
						|
	list.AppendChild(list, node)
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) CanInterruptParagraph() bool {
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
type footnoteParser struct{}
 | 
						|
 | 
						|
var defaultFootnoteParser = &footnoteParser{}
 | 
						|
 | 
						|
// NewFootnoteParser returns a new parser.InlineParser that can parse
 | 
						|
// footnote links of the Markdown(PHP Markdown Extra) text.
 | 
						|
func NewFootnoteParser() parser.InlineParser {
 | 
						|
	return defaultFootnoteParser
 | 
						|
}
 | 
						|
 | 
						|
func (s *footnoteParser) Trigger() []byte {
 | 
						|
	// footnote syntax probably conflict with the image syntax.
 | 
						|
	// So we need trigger this parser with '!'.
 | 
						|
	return []byte{'!', '['}
 | 
						|
}
 | 
						|
 | 
						|
func (s *footnoteParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
 | 
						|
	line, segment := block.PeekLine()
 | 
						|
	pos := 1
 | 
						|
	if len(line) > 0 && line[0] == '!' {
 | 
						|
		pos++
 | 
						|
	}
 | 
						|
	if pos >= len(line) || line[pos] != '^' {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	pos++
 | 
						|
	if pos >= len(line) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	open := pos
 | 
						|
	closure := util.FindClosure(line[pos:], '[', ']', false, false) //nolint
 | 
						|
	if closure < 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	closes := pos + closure
 | 
						|
	value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
 | 
						|
	block.Advance(closes + 1)
 | 
						|
 | 
						|
	var list *FootnoteList
 | 
						|
	if tlist := pc.Get(footnoteListKey); tlist != nil {
 | 
						|
		list = tlist.(*FootnoteList)
 | 
						|
	}
 | 
						|
	if list == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	index := 0
 | 
						|
	name := []byte{}
 | 
						|
	for def := list.FirstChild(); def != nil; def = def.NextSibling() {
 | 
						|
		d := def.(*Footnote)
 | 
						|
		if bytes.Equal(d.Ref, value) {
 | 
						|
			if d.Index < 0 {
 | 
						|
				list.Count++
 | 
						|
				d.Index = list.Count
 | 
						|
				val := CleanValue(d.Name)
 | 
						|
				if len(val) == 0 {
 | 
						|
					val = []byte(strconv.Itoa(d.Index))
 | 
						|
				}
 | 
						|
				d.Name = pc.IDs().Generate(val, KindFootnote)
 | 
						|
			}
 | 
						|
			index = d.Index
 | 
						|
			name = d.Name
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if index == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return NewFootnoteLink(index, name)
 | 
						|
}
 | 
						|
 | 
						|
type footnoteASTTransformer struct{}
 | 
						|
 | 
						|
var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
 | 
						|
 | 
						|
// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
 | 
						|
// insert a footnote list to the last of the document.
 | 
						|
func NewFootnoteASTTransformer() parser.ASTTransformer {
 | 
						|
	return defaultFootnoteASTTransformer
 | 
						|
}
 | 
						|
 | 
						|
func (a *footnoteASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
 | 
						|
	var list *FootnoteList
 | 
						|
	if tlist := pc.Get(footnoteListKey); tlist != nil {
 | 
						|
		list = tlist.(*FootnoteList)
 | 
						|
	} else {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	pc.Set(footnoteListKey, nil)
 | 
						|
	for footnote := list.FirstChild(); footnote != nil; {
 | 
						|
		container := footnote
 | 
						|
		next := footnote.NextSibling()
 | 
						|
		if fc := container.LastChild(); fc != nil && ast.IsParagraph(fc) {
 | 
						|
			container = fc
 | 
						|
		}
 | 
						|
		footnoteNode := footnote.(*Footnote)
 | 
						|
		index := footnoteNode.Index
 | 
						|
		name := footnoteNode.Name
 | 
						|
		if index < 0 {
 | 
						|
			list.RemoveChild(list, footnote)
 | 
						|
		} else {
 | 
						|
			container.AppendChild(container, NewFootnoteBackLink(index, name))
 | 
						|
		}
 | 
						|
		footnote = next
 | 
						|
	}
 | 
						|
	list.SortChildren(func(n1, n2 ast.Node) int {
 | 
						|
		if n1.(*Footnote).Index < n2.(*Footnote).Index {
 | 
						|
			return -1
 | 
						|
		}
 | 
						|
		return 1
 | 
						|
	})
 | 
						|
	if list.Count <= 0 {
 | 
						|
		list.Parent().RemoveChild(list.Parent(), list)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	node.AppendChild(node, list)
 | 
						|
}
 | 
						|
 | 
						|
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
 | 
						|
// renders FootnoteLink nodes.
 | 
						|
type FootnoteHTMLRenderer struct {
 | 
						|
	html.Config
 | 
						|
}
 | 
						|
 | 
						|
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
 | 
						|
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
 | 
						|
	r := &FootnoteHTMLRenderer{
 | 
						|
		Config: html.NewConfig(),
 | 
						|
	}
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt.SetHTMLOption(&r.Config)
 | 
						|
	}
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
 | 
						|
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 | 
						|
	reg.Register(KindFootnoteLink, r.renderFootnoteLink)
 | 
						|
	reg.Register(KindFootnoteBackLink, r.renderFootnoteBackLink)
 | 
						|
	reg.Register(KindFootnote, r.renderFootnote)
 | 
						|
	reg.Register(KindFootnoteList, r.renderFootnoteList)
 | 
						|
}
 | 
						|
 | 
						|
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
						|
	if entering {
 | 
						|
		n := node.(*FootnoteLink)
 | 
						|
		is := strconv.Itoa(n.Index)
 | 
						|
		_, _ = w.WriteString(`<sup id="fnref:`)
 | 
						|
		_, _ = w.Write(n.Name)
 | 
						|
		_, _ = w.WriteString(`"><a href="#fn:`)
 | 
						|
		_, _ = w.Write(n.Name)
 | 
						|
		_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
 | 
						|
		_, _ = w.WriteString(is)
 | 
						|
		_, _ = w.WriteString(`</a></sup>`)
 | 
						|
	}
 | 
						|
	return ast.WalkContinue, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
						|
	if entering {
 | 
						|
		n := node.(*FootnoteBackLink)
 | 
						|
		_, _ = w.WriteString(` <a href="#fnref:`)
 | 
						|
		_, _ = w.Write(n.Name)
 | 
						|
		_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
 | 
						|
		_, _ = w.WriteString("↩︎")
 | 
						|
		_, _ = w.WriteString(`</a>`)
 | 
						|
	}
 | 
						|
	return ast.WalkContinue, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
						|
	n := node.(*Footnote)
 | 
						|
	if entering {
 | 
						|
		_, _ = w.WriteString(`<li id="fn:`)
 | 
						|
		_, _ = w.Write(n.Name)
 | 
						|
		_, _ = w.WriteString(`" role="doc-endnote"`)
 | 
						|
		if node.Attributes() != nil {
 | 
						|
			html.RenderAttributes(w, node, html.ListItemAttributeFilter)
 | 
						|
		}
 | 
						|
		_, _ = w.WriteString(">\n")
 | 
						|
	} else {
 | 
						|
		_, _ = w.WriteString("</li>\n")
 | 
						|
	}
 | 
						|
	return ast.WalkContinue, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
						|
	tag := "div"
 | 
						|
	if entering {
 | 
						|
		_, _ = w.WriteString("<")
 | 
						|
		_, _ = w.WriteString(tag)
 | 
						|
		_, _ = w.WriteString(` class="footnotes" role="doc-endnotes"`)
 | 
						|
		if node.Attributes() != nil {
 | 
						|
			html.RenderAttributes(w, node, html.GlobalAttributeFilter)
 | 
						|
		}
 | 
						|
		_ = w.WriteByte('>')
 | 
						|
		if r.Config.XHTML {
 | 
						|
			_, _ = w.WriteString("\n<hr />\n")
 | 
						|
		} else {
 | 
						|
			_, _ = w.WriteString("\n<hr>\n")
 | 
						|
		}
 | 
						|
		_, _ = w.WriteString("<ol>\n")
 | 
						|
	} else {
 | 
						|
		_, _ = w.WriteString("</ol>\n")
 | 
						|
		_, _ = w.WriteString("</")
 | 
						|
		_, _ = w.WriteString(tag)
 | 
						|
		_, _ = w.WriteString(">\n")
 | 
						|
	}
 | 
						|
	return ast.WalkContinue, nil
 | 
						|
}
 | 
						|
 | 
						|
type footnoteExtension struct{}
 | 
						|
 | 
						|
// FootnoteExtension represents the Gitea Footnote
 | 
						|
var FootnoteExtension = &footnoteExtension{}
 | 
						|
 | 
						|
// Extend extends the markdown converter with the Gitea Footnote parser
 | 
						|
func (e *footnoteExtension) Extend(m goldmark.Markdown) {
 | 
						|
	m.Parser().AddOptions(
 | 
						|
		parser.WithBlockParsers(
 | 
						|
			util.Prioritized(NewFootnoteBlockParser(), 999),
 | 
						|
		),
 | 
						|
		parser.WithInlineParsers(
 | 
						|
			util.Prioritized(NewFootnoteParser(), 101),
 | 
						|
		),
 | 
						|
		parser.WithASTTransformers(
 | 
						|
			util.Prioritized(NewFootnoteASTTransformer(), 999),
 | 
						|
		),
 | 
						|
	)
 | 
						|
	m.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
						|
		util.Prioritized(NewFootnoteHTMLRenderer(), 500),
 | 
						|
	))
 | 
						|
}
 |