mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-24 05:42:40 +08:00
feat: Support page inclusion macro
Also generalize the ac_tag_parser a bit and support <ri:* /> tags as well
This commit is contained in:
parent
943a356508
commit
1285947ab3
@ -371,6 +371,10 @@ By default, mark provides several built-in templates and macros:
|
|||||||
|
|
||||||
See: https://confluence.atlassian.com/doc/blog-posts-macro-139470.html
|
See: https://confluence.atlassian.com/doc/blog-posts-macro-139470.html
|
||||||
|
|
||||||
|
* template: `ac:include` to include a page
|
||||||
|
- Page: the page to be included
|
||||||
|
- Space: the space the page is in (optional, otherwise same space)
|
||||||
|
|
||||||
* macro `@{...}` to mention user by name specified in the braces.
|
* macro `@{...}` to mention user by name specified in the braces.
|
||||||
|
|
||||||
## Template & Macros Usecases
|
## Template & Macros Usecases
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
cparser "github.com/kovetskiy/mark/pkg/mark/parser"
|
||||||
"github.com/kovetskiy/mark/pkg/mark/stdlib"
|
"github.com/kovetskiy/mark/pkg/mark/stdlib"
|
||||||
"github.com/reconquest/pkg/log"
|
"github.com/reconquest/pkg/log"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
@ -450,7 +451,7 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string {
|
|||||||
converter.Parser().AddOptions(parser.WithInlineParsers(
|
converter.Parser().AddOptions(parser.WithInlineParsers(
|
||||||
// Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse
|
// Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse
|
||||||
// the <ac:*/> tags.
|
// the <ac:*/> tags.
|
||||||
util.Prioritized(NewACTagParser(), 199),
|
util.Prioritized(cparser.NewConfluenceTagParser(), 199),
|
||||||
))
|
))
|
||||||
|
|
||||||
converter.Renderer().AddOptions(renderer.WithNodeRenderers(
|
converter.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package mark
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,25 +9,25 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewACTagParser returns an inline parser that parses <ac:* /> tags to ensure that Confluence specific tags are parsed
|
// NewConfluenceTagParser returns an inline parser that parses <ac:* /> and <ri:* /> tags to ensure that Confluence specific tags are parsed
|
||||||
// as ast.KindRawHtml so they are not escaped at render time. The parser must be registered with a higher priority
|
// as ast.KindRawHtml so they are not escaped at render time. The parser must be registered with a higher priority
|
||||||
// than goldmark's linkParser. Otherwise, the linkParser would parse the <ac:* /> tags.
|
// than goldmark's linkParser. Otherwise, the linkParser would parse the <ac:* /> tags.
|
||||||
func NewACTagParser() parser.InlineParser {
|
func NewConfluenceTagParser() parser.InlineParser {
|
||||||
return &acTagParser{}
|
return &confluenceTagParser{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ parser.InlineParser = (*acTagParser)(nil)
|
var _ parser.InlineParser = (*confluenceTagParser)(nil)
|
||||||
|
|
||||||
// acTagParser is a stripped down version of goldmark's rawHTMLParser.
|
// confluenceTagParser is a stripped down version of goldmark's rawHTMLParser.
|
||||||
// See: https://github.com/yuin/goldmark/blob/master/parser/raw_html.go
|
// See: https://github.com/yuin/goldmark/blob/master/parser/raw_html.go
|
||||||
type acTagParser struct {
|
type confluenceTagParser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *acTagParser) Trigger() []byte {
|
func (s *confluenceTagParser) Trigger() []byte {
|
||||||
return []byte{'<'}
|
return []byte{'<'}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *acTagParser) Parse(_ ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
func (s *confluenceTagParser) Parse(_ ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
line, _ := block.PeekLine()
|
line, _ := block.PeekLine()
|
||||||
if len(line) > 1 && util.IsAlphaNumeric(line[1]) {
|
if len(line) > 1 && util.IsAlphaNumeric(line[1]) {
|
||||||
return s.parseMultiLineRegexp(openTagRegexp, block, pc)
|
return s.parseMultiLineRegexp(openTagRegexp, block, pc)
|
||||||
@ -48,15 +48,15 @@ var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)`
|
|||||||
|
|
||||||
var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
|
var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
|
||||||
|
|
||||||
// Only match <ac:*/> tags
|
// Only match <ac:*/> and <ri:*/> tags
|
||||||
var openTagRegexp = regexp.MustCompile("^<ac:" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
|
var openTagRegexp = regexp.MustCompile("^<(ac|ri):" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
|
||||||
var closeTagRegexp = regexp.MustCompile("^</ac:" + tagnamePattern + `\s*>`)
|
var closeTagRegexp = regexp.MustCompile("^</ac:" + tagnamePattern + `\s*>`)
|
||||||
|
|
||||||
var openCDATA = []byte("<![CDATA[")
|
var openCDATA = []byte("<![CDATA[")
|
||||||
var closeCDATA = []byte("]]>")
|
var closeCDATA = []byte("]]>")
|
||||||
var closeDecl = []byte(">")
|
var closeDecl = []byte(">")
|
||||||
|
|
||||||
func (s *acTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Context) ast.Node {
|
func (s *confluenceTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Context) ast.Node {
|
||||||
savedLine, savedSegment := block.Position()
|
savedLine, savedSegment := block.Position()
|
||||||
node := ast.NewRawHTML()
|
node := ast.NewRawHTML()
|
||||||
for {
|
for {
|
||||||
@ -77,7 +77,7 @@ func (s *acTagParser) parseUntil(block text.Reader, closer []byte, _ parser.Cont
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *acTagParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, _ parser.Context) ast.Node {
|
func (s *confluenceTagParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, _ parser.Context) ast.Node {
|
||||||
sline, ssegment := block.Position()
|
sline, ssegment := block.Position()
|
||||||
if block.Match(reg) {
|
if block.Match(reg) {
|
||||||
node := ast.NewRawHTML()
|
node := ast.NewRawHTML()
|
@ -261,6 +261,18 @@ func templates(api *confluence.API) (*template.Template, error) {
|
|||||||
`</ac:structured-macro>{{printf "\n"}}`,
|
`</ac:structured-macro>{{printf "\n"}}`,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/* https://confluence.atlassian.com/conf59/include-page-macro-792499125.html */
|
||||||
|
|
||||||
|
`ac:include`: text(
|
||||||
|
`<ac:structured-macro ac:name="include">{{printf "\n"}}`,
|
||||||
|
`<ac:parameter ac:name="">{{printf "\n"}}`,
|
||||||
|
`<ac:link>{{printf "\n"}}`,
|
||||||
|
`<ri:page ri:content-title="{{ .Page }}" {{if .Space }}ri:space-key="{{ .Space }}"{{end}}/>{{printf "\n"}}`,
|
||||||
|
`</ac:link>{{printf "\n"}}`,
|
||||||
|
`</ac:parameter>{{printf "\n"}}`,
|
||||||
|
`</ac:structured-macro>{{printf "\n"}}`,
|
||||||
|
),
|
||||||
|
|
||||||
// TODO(seletskiy): more templates here
|
// TODO(seletskiy): more templates here
|
||||||
} {
|
} {
|
||||||
templates, err = templates.New(name).Parse(body)
|
templates, err = templates.New(name).Parse(body)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user