diff --git a/README.md b/README.md index 3a57834..531a0ab 100644 --- a/README.md +++ b/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]() ``` +### 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: @@ -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 diff --git a/main.go b/main.go index 4a9b525..20a45fc 100644 --- a/main.go +++ b/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( diff --git a/pkg/mark/link.go b/pkg/mark/link.go index 17b654a..0215a8b 100644 --- a/pkg/mark/link.go +++ b/pkg/mark/link.go @@ -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)) diff --git a/pkg/mark/markdown.go b/pkg/mark/markdown.go index 9325e5e..3e84e41 100644 --- a/pkg/mark/markdown.go +++ b/pkg/mark/markdown.go @@ -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() +} diff --git a/pkg/mark/markdown_test.go b/pkg/mark/markdown_test.go index a4c5053..269d27f 100644 --- a/pkg/mark/markdown_test.go +++ b/pkg/mark/markdown_test.go @@ -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) } } diff --git a/pkg/mark/stdlib/stdlib.go b/pkg/mark/stdlib/stdlib.go index bf294b8..427f251 100644 --- a/pkg/mark/stdlib/stdlib.go +++ b/pkg/mark/stdlib/stdlib.go @@ -201,7 +201,7 @@ func templates(api *confluence.API) (*template.Template, error) { `{{ if .Page}}`, /**/ ``, /**/ ``, - /**/ ``, + /**/ ``, /**/ ``, /**/ ``, `{{printf "\n"}}{{end}}`, @@ -217,10 +217,16 @@ func templates(api *confluence.API) (*template.Template, error) { `ac:emoticon`: text( ``, ), + `ac:image`: text( - `{{printf "\n"}}`, - `{{printf "\n"}}`, - `{{printf "\n"}}`, + ``, + `{{ if .Attachment }}{{end}}`, + `{{ if .Url }}{{end}}`, + ``, ), /* 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( `{{printf "\n"}}`, `{{printf "\n"}}`, - `{{ if .Frameborder}}{{ .Frameborder }}{{printf "\n"}}{{end}}`, - `{{ if .Scrolling}}{{ .Scrolling }}{{printf "\n"}}{{end}}`, - `{{ if .Align}}{{ .Align }}{{printf "\n"}}{{end}}`, + `{{ if .Frameborder }}{{ .Frameborder }}{{printf "\n"}}{{end}}`, + `{{ if .Scrolling }}{{ .Scrolling }}{{printf "\n"}}{{end}}`, + `{{ if .Align }}{{ .Align }}{{printf "\n"}}{{end}}`, `{{ or .Width "640px" }}{{printf "\n"}}`, `{{ or .Height "360px" }}{{printf "\n"}}`, `{{printf "\n"}}`, diff --git a/pkg/mark/testdata/links.html b/pkg/mark/testdata/links.html index 625f00e..743676d 100644 --- a/pkg/mark/testdata/links.html +++ b/pkg/mark/testdata/links.html @@ -4,6 +4,8 @@

Use

Use

Use

+

+

Use footnotes link 1


diff --git a/pkg/mark/testdata/links.md b/pkg/mark/testdata/links.md index ce4a282..bee9e3e 100644 --- a/pkg/mark/testdata/links.md +++ b/pkg/mark/testdata/links.md @@ -10,5 +10,10 @@ Use [Another Page](ac:) Use [page link with spaces]() +![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 \ No newline at end of file +[^1]: a footnote link + diff --git a/pkg/mark/testdata/test.png b/pkg/mark/testdata/test.png new file mode 100644 index 0000000..42eb544 Binary files /dev/null and b/pkg/mark/testdata/test.png differ