Skip to content

Commit

Permalink
Support inline images
Browse files Browse the repository at this point in the history
  • Loading branch information
mrueg committed May 3, 2023
1 parent 2b756da commit fd97ee7
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 18 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
![Example](../images/examples.png)
```
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:
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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(
Expand Down
3 changes: 2 additions & 1 deletion pkg/mark/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
91 changes: 87 additions & 4 deletions pkg/mark/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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{},
Expand All @@ -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)
Expand Down Expand Up @@ -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,
},
)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()
}
2 changes: 1 addition & 1 deletion pkg/mark/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
20 changes: 13 additions & 7 deletions pkg/mark/stdlib/stdlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand All @@ -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 */
Expand All @@ -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"}}`,
Expand Down
2 changes: 2 additions & 0 deletions pkg/mark/testdata/links.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 />
Expand Down
7 changes: 6 additions & 1 deletion pkg/mark/testdata/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ Use [Another Page](ac:)

Use [page link with spaces](<ac:Page With Space>)

![My Image](test.png)

![My External Image](http://confluence.atlassian.com/images/logo/confluence_48_trans.png)

Use footnotes link [^1]
[^1]: a footnote link
[^1]: a footnote link

Binary file added pkg/mark/testdata/test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit fd97ee7

Please sign in to comment.