mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-23 21:32:41 +08:00
Support inline images
This commit is contained in:
parent
2b756daf37
commit
fd97ee70f9
14
README.md
14
README.md
@ -300,8 +300,8 @@ By default, mark provides several built-in templates and macros:
|
||||
|
||||
* template: `ac:youtube` to include YouTube Widget. Parameters:
|
||||
- URL: YouTube video endpoint
|
||||
- Width: Width in px. Defualts to "640px"
|
||||
- Height: Height in px. Defualts to "360px"
|
||||
- Width: Width in px. Defaults to "640px"
|
||||
- Height: Height in px. Defaults to "360px"
|
||||
|
||||
See: https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube
|
||||
|
||||
@ -537,6 +537,15 @@ And this is how to link when the linktext is the same as the [Pagetitle](ac:)
|
||||
Link to a [page title with space](<ac:With Space>)
|
||||
```
|
||||
|
||||
### Upload and included inline images
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
will automatically upload the inlined image as an attachment and inline the image using the `ac:image` template.
|
||||
|
||||
If the file is not found, it will inline the image using the `ac:image` template and link to the image.
|
||||
|
||||
### Add width for an image
|
||||
|
||||
Use the following macro:
|
||||
@ -552,6 +561,7 @@ And attach any image with the following
|
||||
```
|
||||
The width will be the commented html after the image (in this case 300px).
|
||||
|
||||
Currently this is not compatible with the automated upload of inline images.
|
||||
|
||||
### Render Mermaid Diagram
|
||||
|
||||
|
4
main.go
4
main.go
@ -364,7 +364,7 @@ func processFile(
|
||||
markdown = mark.DropDocumentLeadingH1(markdown)
|
||||
}
|
||||
|
||||
html, _ := mark.CompileMarkdown(markdown, stdlib, cCtx.String("mermaid-provider"))
|
||||
html, _ := mark.CompileMarkdown(markdown, stdlib, file, cCtx.String("mermaid-provider"))
|
||||
fmt.Println(html)
|
||||
os.Exit(0)
|
||||
}
|
||||
@ -441,7 +441,7 @@ func processFile(
|
||||
markdown = mark.DropDocumentLeadingH1(markdown)
|
||||
}
|
||||
|
||||
html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, cCtx.String("mermaid-provider"))
|
||||
html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cCtx.String("mermaid-provider"))
|
||||
|
||||
// Resolve attachements detected from markdown
|
||||
_, err = mark.ResolveAttachments(
|
||||
|
@ -166,7 +166,8 @@ func SubstituteLinks(markdown []byte, links []LinkSubstitution) []byte {
|
||||
}
|
||||
|
||||
func parseLinks(markdown string) []markdownLink {
|
||||
re := regexp.MustCompile(`\[[^\]]+\]\((([^\)#]+)?#?([^\)]+)?)\)`)
|
||||
// Matches links but not inline images
|
||||
re := regexp.MustCompile(`[^\!]\[[^\]]+\]\((([^\)#]+)?#?([^\)]+)?)\)`)
|
||||
matches := re.FindAllStringSubmatch(markdown, -1)
|
||||
|
||||
links := make([]markdownLink, len(matches))
|
||||
|
@ -3,11 +3,13 @@ package mark
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
cparser "github.com/kovetskiy/mark/pkg/mark/parser"
|
||||
"github.com/kovetskiy/mark/pkg/mark/stdlib"
|
||||
"github.com/kovetskiy/mark/pkg/mark/vfs"
|
||||
"github.com/reconquest/pkg/log"
|
||||
"github.com/yuin/goldmark"
|
||||
|
||||
@ -49,16 +51,18 @@ func (m BlockQuoteLevelMap) Level(node ast.Node) int {
|
||||
type ConfluenceRenderer struct {
|
||||
html.Config
|
||||
Stdlib *stdlib.Lib
|
||||
Path string
|
||||
MermaidProvider string
|
||||
LevelMap BlockQuoteLevelMap
|
||||
Attachments []Attachment
|
||||
}
|
||||
|
||||
// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
|
||||
func NewConfluenceRenderer(stdlib *stdlib.Lib, mermaidProvider string, opts ...html.Option) renderer.NodeRenderer {
|
||||
func NewConfluenceRenderer(stdlib *stdlib.Lib, path string, mermaidProvider string, opts ...html.Option) renderer.NodeRenderer {
|
||||
return &ConfluenceRenderer{
|
||||
Config: html.NewConfig(),
|
||||
Stdlib: stdlib,
|
||||
Path: path,
|
||||
MermaidProvider: mermaidProvider,
|
||||
LevelMap: nil,
|
||||
Attachments: []Attachment{},
|
||||
@ -84,7 +88,7 @@ func (r *ConfluenceRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegister
|
||||
// reg.Register(ast.KindAutoLink, r.renderNode)
|
||||
// reg.Register(ast.KindCodeSpan, r.renderNode)
|
||||
// reg.Register(ast.KindEmphasis, r.renderNode)
|
||||
// reg.Register(ast.KindImage, r.renderNode)
|
||||
reg.Register(ast.KindImage, r.renderImage)
|
||||
reg.Register(ast.KindLink, r.renderLink)
|
||||
// reg.Register(ast.KindRawHTML, r.renderNode)
|
||||
// reg.Register(ast.KindText, r.renderNode)
|
||||
@ -372,11 +376,13 @@ func (r *ConfluenceRenderer) renderFencedCodeBlock(writer util.BufWriter, source
|
||||
Width string
|
||||
Height string
|
||||
Title string
|
||||
Alt string
|
||||
Attachment string
|
||||
}{
|
||||
attachment.Width,
|
||||
attachment.Height,
|
||||
attachment.Name,
|
||||
"",
|
||||
attachment.Filename,
|
||||
},
|
||||
)
|
||||
@ -463,10 +469,72 @@ func (r *ConfluenceRenderer) renderCodeBlock(writer util.BufWriter, source []byt
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, mermaidProvider string) (string, []Attachment) {
|
||||
// renderImage renders an inline image
|
||||
func (r *ConfluenceRenderer) renderImage(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
n := node.(*ast.Image)
|
||||
|
||||
attachments, err := ResolveLocalAttachments(vfs.LocalOS, filepath.Dir(r.Path), []string{string(n.Destination)})
|
||||
|
||||
// We were unable to resolve it locally, treat as URL
|
||||
if err != nil {
|
||||
err = r.Stdlib.Templates.ExecuteTemplate(
|
||||
writer,
|
||||
"ac:image",
|
||||
struct {
|
||||
Width string
|
||||
Height string
|
||||
Title string
|
||||
Alt string
|
||||
Attachment string
|
||||
Url string
|
||||
}{
|
||||
"",
|
||||
"",
|
||||
string(n.Title),
|
||||
string(nodeToHTMLText(n, source)),
|
||||
"",
|
||||
string(n.Destination),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
|
||||
r.Attachments = append(r.Attachments, attachments[0])
|
||||
|
||||
err = r.Stdlib.Templates.ExecuteTemplate(
|
||||
writer,
|
||||
"ac:image",
|
||||
struct {
|
||||
Width string
|
||||
Height string
|
||||
Title string
|
||||
Alt string
|
||||
Attachment string
|
||||
Url string
|
||||
}{
|
||||
"",
|
||||
"",
|
||||
string(n.Title),
|
||||
string(nodeToHTMLText(n, source)),
|
||||
attachments[0].Filename,
|
||||
"",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ast.WalkStop, err
|
||||
}
|
||||
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
|
||||
func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, mermaidProvider string) (string, []Attachment) {
|
||||
log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
|
||||
|
||||
confluenceRenderer := NewConfluenceRenderer(stdlib, mermaidProvider)
|
||||
confluenceRenderer := NewConfluenceRenderer(stdlib, path, mermaidProvider)
|
||||
|
||||
converter := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
@ -530,3 +598,18 @@ func ExtractDocumentLeadingH1(markdown []byte) string {
|
||||
return string(groups[1])
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/yuin/goldmark/blob/c446c414ef3a41fb562da0ae5badd18f1502c42f/renderer/html/html.go
|
||||
func nodeToHTMLText(n ast.Node, source []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
if s, ok := c.(*ast.String); ok && s.IsCode() {
|
||||
buf.Write(s.Text(source))
|
||||
} else if !c.HasChildren() {
|
||||
buf.Write(util.EscapeHTML(c.Text(source)))
|
||||
} else {
|
||||
buf.Write(nodeToHTMLText(c, source))
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func TestCompileMarkdown(t *testing.T) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
actual, _ := CompileMarkdown(markdown, lib, "")
|
||||
actual, _ := CompileMarkdown(markdown, lib, filename, "")
|
||||
test.EqualValues(string(html), actual, filename+" vs "+htmlname)
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func templates(api *confluence.API) (*template.Template, error) {
|
||||
`{{ if .Page}}`,
|
||||
/**/ `<ac:parameter ac:name="page">`,
|
||||
/**/ `<ac:link>`,
|
||||
/**/ `<ri:page ri:content-title="{{ .Page}}"/>`,
|
||||
/**/ `<ri:page ri:content-title="{{ .Page }}"/>`,
|
||||
/**/ `</ac:link>`,
|
||||
/**/ `</ac:parameter>`,
|
||||
`{{printf "\n"}}{{end}}`,
|
||||
@ -217,10 +217,16 @@ func templates(api *confluence.API) (*template.Template, error) {
|
||||
`ac:emoticon`: text(
|
||||
`<ac:emoticon ac:name="{{ .Name }}"/>`,
|
||||
),
|
||||
|
||||
`ac:image`: text(
|
||||
`<ac:image{{ if .Width}} ac:width="{{ .Width }}"{{end}}{{ if .Height }} ac:height="{{ .Height }}"{{end}}{{ if .Title }} ac:title="{{ .Title }}"{{end}}>{{printf "\n"}}`,
|
||||
`<ri:attachment ri:filename="{{ .Attachment | convertAttachment }}"/>{{printf "\n"}}`,
|
||||
`</ac:image>{{printf "\n"}}`,
|
||||
`<ac:image`,
|
||||
`{{ if .Width }} ac:width="{{ .Width }}"{{end}}`,
|
||||
`{{ if .Height }} ac:height="{{ .Height }}"{{end}}`,
|
||||
`{{ if .Title }} ac:title="{{ .Title }}"{{end}}`,
|
||||
`{{ if .Alt }} ac:alt="{{ .Alt }}"{{end}}>`,
|
||||
`{{ if .Attachment }}<ri:attachment ri:filename="{{ .Attachment | convertAttachment }}"/>{{end}}`,
|
||||
`{{ if .Url }}<ri:url ri:value="{{ .Url }}"/>{{end}}`,
|
||||
`</ac:image>`,
|
||||
),
|
||||
|
||||
/* https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube */
|
||||
@ -240,9 +246,9 @@ func templates(api *confluence.API) (*template.Template, error) {
|
||||
`ac:iframe`: text(
|
||||
`<ac:structured-macro ac:name="iframe">{{printf "\n"}}`,
|
||||
`<ac:parameter ac:name="src"><ri:url ri:value="{{ .URL }}" /></ac:parameter>{{printf "\n"}}`,
|
||||
`{{ if .Frameborder}}<ac:parameter ac:name="frameborder">{{ .Frameborder }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`{{ if .Scrolling}}<ac:parameter ac:name="id">{{ .Scrolling }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`{{ if .Align}}<ac:parameter ac:name="align">{{ .Align }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`{{ if .Frameborder }}<ac:parameter ac:name="frameborder">{{ .Frameborder }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`{{ if .Scrolling }}<ac:parameter ac:name="id">{{ .Scrolling }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`{{ if .Align }}<ac:parameter ac:name="align">{{ .Align }}</ac:parameter>{{printf "\n"}}{{end}}`,
|
||||
`<ac:parameter ac:name="width">{{ or .Width "640px" }}</ac:parameter>{{printf "\n"}}`,
|
||||
`<ac:parameter ac:name="height">{{ or .Height "360px" }}</ac:parameter>{{printf "\n"}}`,
|
||||
`</ac:structured-macro>{{printf "\n"}}`,
|
||||
|
2
pkg/mark/testdata/links.html
vendored
2
pkg/mark/testdata/links.html
vendored
@ -4,6 +4,8 @@
|
||||
<p>Use <ac:link><ri:page ri:content-title="AnotherPage"/><ac:plain-text-link-body><![CDATA[AnotherPage]]></ac:plain-text-link-body></ac:link></p>
|
||||
<p>Use <ac:link><ri:page ri:content-title="Another Page"/><ac:plain-text-link-body><![CDATA[Another Page]]></ac:plain-text-link-body></ac:link></p>
|
||||
<p>Use <ac:link><ri:page ri:content-title="Page With Space"/><ac:plain-text-link-body><![CDATA[page link with spaces]]></ac:plain-text-link-body></ac:link></p>
|
||||
<p><ac:image ac:alt="My Image"><ri:attachment ri:filename="test.png"/></ac:image></p>
|
||||
<p><ac:image ac:alt="My External Image"><ri:url ri:value="http://confluence.atlassian.com/images/logo/confluence_48_trans.png"/></ac:image></p>
|
||||
<p>Use footnotes link <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
|
||||
<div class="footnotes" role="doc-endnotes">
|
||||
<hr />
|
||||
|
7
pkg/mark/testdata/links.md
vendored
7
pkg/mark/testdata/links.md
vendored
@ -10,5 +10,10 @@ Use [Another Page](ac:)
|
||||
|
||||
Use [page link with spaces](<ac:Page With Space>)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Use footnotes link [^1]
|
||||
[^1]: a footnote link
|
||||
[^1]: a footnote link
|
||||
|
||||
|
BIN
pkg/mark/testdata/test.png
vendored
Normal file
BIN
pkg/mark/testdata/test.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Loading…
x
Reference in New Issue
Block a user