mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-24 05:42:40 +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:
|
* template: `ac:youtube` to include YouTube Widget. Parameters:
|
||||||
- URL: YouTube video endpoint
|
- URL: YouTube video endpoint
|
||||||
- Width: Width in px. Defualts to "640px"
|
- Width: Width in px. Defaults to "640px"
|
||||||
- Height: Height in px. Defualts to "360px"
|
- Height: Height in px. Defaults to "360px"
|
||||||
|
|
||||||
See: https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube
|
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>)
|
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
|
### Add width for an image
|
||||||
|
|
||||||
Use the following macro:
|
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).
|
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
|
### Render Mermaid Diagram
|
||||||
|
|
||||||
|
4
main.go
4
main.go
@ -364,7 +364,7 @@ func processFile(
|
|||||||
markdown = mark.DropDocumentLeadingH1(markdown)
|
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)
|
fmt.Println(html)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
@ -441,7 +441,7 @@ func processFile(
|
|||||||
markdown = mark.DropDocumentLeadingH1(markdown)
|
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
|
// Resolve attachements detected from markdown
|
||||||
_, err = mark.ResolveAttachments(
|
_, err = mark.ResolveAttachments(
|
||||||
|
@ -166,7 +166,8 @@ func SubstituteLinks(markdown []byte, links []LinkSubstitution) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseLinks(markdown string) []markdownLink {
|
func parseLinks(markdown string) []markdownLink {
|
||||||
re := regexp.MustCompile(`\[[^\]]+\]\((([^\)#]+)?#?([^\)]+)?)\)`)
|
// Matches links but not inline images
|
||||||
|
re := regexp.MustCompile(`[^\!]\[[^\]]+\]\((([^\)#]+)?#?([^\)]+)?)\)`)
|
||||||
matches := re.FindAllStringSubmatch(markdown, -1)
|
matches := re.FindAllStringSubmatch(markdown, -1)
|
||||||
|
|
||||||
links := make([]markdownLink, len(matches))
|
links := make([]markdownLink, len(matches))
|
||||||
|
@ -3,11 +3,13 @@ package mark
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cparser "github.com/kovetskiy/mark/pkg/mark/parser"
|
cparser "github.com/kovetskiy/mark/pkg/mark/parser"
|
||||||
"github.com/kovetskiy/mark/pkg/mark/stdlib"
|
"github.com/kovetskiy/mark/pkg/mark/stdlib"
|
||||||
|
"github.com/kovetskiy/mark/pkg/mark/vfs"
|
||||||
"github.com/reconquest/pkg/log"
|
"github.com/reconquest/pkg/log"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
|
||||||
@ -49,16 +51,18 @@ func (m BlockQuoteLevelMap) Level(node ast.Node) int {
|
|||||||
type ConfluenceRenderer struct {
|
type ConfluenceRenderer struct {
|
||||||
html.Config
|
html.Config
|
||||||
Stdlib *stdlib.Lib
|
Stdlib *stdlib.Lib
|
||||||
|
Path string
|
||||||
MermaidProvider string
|
MermaidProvider string
|
||||||
LevelMap BlockQuoteLevelMap
|
LevelMap BlockQuoteLevelMap
|
||||||
Attachments []Attachment
|
Attachments []Attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
|
// 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{
|
return &ConfluenceRenderer{
|
||||||
Config: html.NewConfig(),
|
Config: html.NewConfig(),
|
||||||
Stdlib: stdlib,
|
Stdlib: stdlib,
|
||||||
|
Path: path,
|
||||||
MermaidProvider: mermaidProvider,
|
MermaidProvider: mermaidProvider,
|
||||||
LevelMap: nil,
|
LevelMap: nil,
|
||||||
Attachments: []Attachment{},
|
Attachments: []Attachment{},
|
||||||
@ -84,7 +88,7 @@ func (r *ConfluenceRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegister
|
|||||||
// reg.Register(ast.KindAutoLink, r.renderNode)
|
// reg.Register(ast.KindAutoLink, r.renderNode)
|
||||||
// reg.Register(ast.KindCodeSpan, r.renderNode)
|
// reg.Register(ast.KindCodeSpan, r.renderNode)
|
||||||
// reg.Register(ast.KindEmphasis, 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.KindLink, r.renderLink)
|
||||||
// reg.Register(ast.KindRawHTML, r.renderNode)
|
// reg.Register(ast.KindRawHTML, r.renderNode)
|
||||||
// reg.Register(ast.KindText, r.renderNode)
|
// reg.Register(ast.KindText, r.renderNode)
|
||||||
@ -372,11 +376,13 @@ func (r *ConfluenceRenderer) renderFencedCodeBlock(writer util.BufWriter, source
|
|||||||
Width string
|
Width string
|
||||||
Height string
|
Height string
|
||||||
Title string
|
Title string
|
||||||
|
Alt string
|
||||||
Attachment string
|
Attachment string
|
||||||
}{
|
}{
|
||||||
attachment.Width,
|
attachment.Width,
|
||||||
attachment.Height,
|
attachment.Height,
|
||||||
attachment.Name,
|
attachment.Name,
|
||||||
|
"",
|
||||||
attachment.Filename,
|
attachment.Filename,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -463,10 +469,72 @@ func (r *ConfluenceRenderer) renderCodeBlock(writer util.BufWriter, source []byt
|
|||||||
return ast.WalkContinue, nil
|
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))
|
log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
|
||||||
|
|
||||||
confluenceRenderer := NewConfluenceRenderer(stdlib, mermaidProvider)
|
confluenceRenderer := NewConfluenceRenderer(stdlib, path, mermaidProvider)
|
||||||
|
|
||||||
converter := goldmark.New(
|
converter := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
@ -530,3 +598,18 @@ func ExtractDocumentLeadingH1(markdown []byte) string {
|
|||||||
return string(groups[1])
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
actual, _ := CompileMarkdown(markdown, lib, "")
|
actual, _ := CompileMarkdown(markdown, lib, filename, "")
|
||||||
test.EqualValues(string(html), actual, filename+" vs "+htmlname)
|
test.EqualValues(string(html), actual, filename+" vs "+htmlname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,10 +217,16 @@ func templates(api *confluence.API) (*template.Template, error) {
|
|||||||
`ac:emoticon`: text(
|
`ac:emoticon`: text(
|
||||||
`<ac:emoticon ac:name="{{ .Name }}"/>`,
|
`<ac:emoticon ac:name="{{ .Name }}"/>`,
|
||||||
),
|
),
|
||||||
|
|
||||||
`ac:image`: text(
|
`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"}}`,
|
`<ac:image`,
|
||||||
`<ri:attachment ri:filename="{{ .Attachment | convertAttachment }}"/>{{printf "\n"}}`,
|
`{{ if .Width }} ac:width="{{ .Width }}"{{end}}`,
|
||||||
`</ac:image>{{printf "\n"}}`,
|
`{{ 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 */
|
/* https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube */
|
||||||
|
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="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="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>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>
|
<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">
|
<div class="footnotes" role="doc-endnotes">
|
||||||
<hr />
|
<hr />
|
||||||
|
5
pkg/mark/testdata/links.md
vendored
5
pkg/mark/testdata/links.md
vendored
@ -10,5 +10,10 @@ Use [Another Page](ac:)
|
|||||||
|
|
||||||
Use [page link with spaces](<ac:Page With Space>)
|
Use [page link with spaces](<ac:Page With Space>)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Use footnotes link [^1]
|
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