mirror of
https://github.com/kovetskiy/mark.git
synced 2026-03-14 14:17:37 +08:00
feat: add support for image dimensions
This commit is contained in:
parent
c32cd79dc8
commit
4d887bde74
@ -78,7 +78,14 @@ You can set a page emoji icon by specifying the icon in the headers.
|
|||||||
<!-- Image-Align: center -->
|
<!-- Image-Align: center -->
|
||||||
```
|
```
|
||||||
|
|
||||||
You can set the alignment for all images in the page. Common values are `left`, `center`, and `right`. This adds the `ac:align` attribute to image tags. Can also be set globally via the `--image-align` CLI option (per-page header takes precedence).
|
You can set the alignment for all images in the page. Common values are `left`, `center`, and `right`. This adds the `ac:align` attribute to image tags and also sets the corresponding `ac:layout` attribute:
|
||||||
|
- `left` → `ac:align="left" ac:layout="align-start"`
|
||||||
|
- `center` → `ac:align="center" ac:layout="center"`
|
||||||
|
- `right` → `ac:align="right" ac:layout="align-end"`
|
||||||
|
|
||||||
|
**Note**: Images with width >= 760px automatically use `ac:align="wide"` with `ac:layout="center"` instead of the configured alignment, as Confluence requires this for wide images.
|
||||||
|
|
||||||
|
Custom values are passed through as-is with only the `ac:align` attribute. Can also be set globally via the `--image-align` CLI option (per-page header takes precedence).
|
||||||
|
|
||||||
Mark supports Go templates, which can be included into article by using path
|
Mark supports Go templates, which can be included into article by using path
|
||||||
to the template relative to current working dir, e.g.:
|
to the template relative to current working dir, e.g.:
|
||||||
|
|||||||
@ -4,11 +4,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kovetskiy/mark/confluence"
|
"github.com/kovetskiy/mark/confluence"
|
||||||
@ -210,12 +215,20 @@ func prepareAttachment(opener vfs.Opener, base, name string) (Attachment, error)
|
|||||||
return Attachment{}, karma.Format(err, "unable to read file: %q", attachmentPath)
|
return Attachment{}, karma.Format(err, "unable to read file: %q", attachmentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Attachment{
|
attachment := Attachment{
|
||||||
Name: name,
|
Name: name,
|
||||||
Filename: strings.ReplaceAll(name, "/", "_"),
|
Filename: strings.ReplaceAll(name, "/", "_"),
|
||||||
FileBytes: fileBytes,
|
FileBytes: fileBytes,
|
||||||
Replace: name,
|
Replace: name,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
// Try to detect image dimensions
|
||||||
|
if config, _, err := image.DecodeConfig(bytes.NewReader(fileBytes)); err == nil {
|
||||||
|
attachment.Width = strconv.Itoa(config.Width)
|
||||||
|
attachment.Height = strconv.Itoa(config.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompileAttachmentLinks(markdown []byte, attachments []Attachment) []byte {
|
func CompileAttachmentLinks(markdown []byte, attachments []Attachment) []byte {
|
||||||
|
|||||||
@ -139,10 +139,14 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
|
|||||||
return ast.WalkStop, err
|
return ast.WalkStop, err
|
||||||
}
|
}
|
||||||
r.Attachments.Attach(attachment)
|
r.Attachments.Attach(attachment)
|
||||||
|
|
||||||
|
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
|
||||||
|
|
||||||
err = r.Stdlib.Templates.ExecuteTemplate(
|
err = r.Stdlib.Templates.ExecuteTemplate(
|
||||||
writer,
|
writer,
|
||||||
"ac:image",
|
"ac:image",
|
||||||
struct {
|
struct {
|
||||||
|
Align string
|
||||||
Width string
|
Width string
|
||||||
Height string
|
Height string
|
||||||
Title string
|
Title string
|
||||||
@ -150,6 +154,7 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
|
|||||||
Attachment string
|
Attachment string
|
||||||
Url string
|
Url string
|
||||||
}{
|
}{
|
||||||
|
effectiveAlign,
|
||||||
attachment.Width,
|
attachment.Width,
|
||||||
attachment.Height,
|
attachment.Height,
|
||||||
attachment.Name,
|
attachment.Name,
|
||||||
@ -170,10 +175,14 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
|
|||||||
return ast.WalkStop, err
|
return ast.WalkStop, err
|
||||||
}
|
}
|
||||||
r.Attachments.Attach(attachment)
|
r.Attachments.Attach(attachment)
|
||||||
|
|
||||||
|
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
|
||||||
|
|
||||||
err = r.Stdlib.Templates.ExecuteTemplate(
|
err = r.Stdlib.Templates.ExecuteTemplate(
|
||||||
writer,
|
writer,
|
||||||
"ac:image",
|
"ac:image",
|
||||||
struct {
|
struct {
|
||||||
|
Align string
|
||||||
Width string
|
Width string
|
||||||
Height string
|
Height string
|
||||||
Title string
|
Title string
|
||||||
@ -181,6 +190,7 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
|
|||||||
Attachment string
|
Attachment string
|
||||||
Url string
|
Url string
|
||||||
}{
|
}{
|
||||||
|
effectiveAlign,
|
||||||
attachment.Width,
|
attachment.Width,
|
||||||
attachment.Height,
|
attachment.Height,
|
||||||
attachment.Name,
|
attachment.Name,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package renderer
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kovetskiy/mark/attachment"
|
"github.com/kovetskiy/mark/attachment"
|
||||||
@ -15,6 +16,30 @@ import (
|
|||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// calculateAlign determines the appropriate ac:align value based on width
|
||||||
|
// Images >= 760px wide use "wide", otherwise use the configured alignment
|
||||||
|
func calculateAlign(configuredAlign string, width string) string {
|
||||||
|
if configuredAlign == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == "" {
|
||||||
|
return configuredAlign
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse width and check if >= 760
|
||||||
|
widthInt, err := strconv.Atoi(width)
|
||||||
|
if err != nil {
|
||||||
|
return configuredAlign
|
||||||
|
}
|
||||||
|
|
||||||
|
if widthInt >= 760 {
|
||||||
|
return "wide"
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuredAlign
|
||||||
|
}
|
||||||
|
|
||||||
type ConfluenceImageRenderer struct {
|
type ConfluenceImageRenderer struct {
|
||||||
html.Config
|
html.Config
|
||||||
Stdlib *stdlib.Lib
|
Stdlib *stdlib.Lib
|
||||||
@ -78,6 +103,8 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
|
|||||||
|
|
||||||
r.Attachments.Attach(attachments[0])
|
r.Attachments.Attach(attachments[0])
|
||||||
|
|
||||||
|
effectiveAlign := calculateAlign(r.ImageAlign, attachments[0].Width)
|
||||||
|
|
||||||
err = r.Stdlib.Templates.ExecuteTemplate(
|
err = r.Stdlib.Templates.ExecuteTemplate(
|
||||||
writer,
|
writer,
|
||||||
"ac:image",
|
"ac:image",
|
||||||
@ -90,9 +117,9 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
|
|||||||
Attachment string
|
Attachment string
|
||||||
Url string
|
Url string
|
||||||
}{
|
}{
|
||||||
r.ImageAlign,
|
effectiveAlign,
|
||||||
"",
|
attachments[0].Width,
|
||||||
"",
|
attachments[0].Height,
|
||||||
string(n.Title),
|
string(n.Title),
|
||||||
string(nodeToHTMLText(n, source)),
|
string(nodeToHTMLText(n, source)),
|
||||||
attachments[0].Filename,
|
attachments[0].Filename,
|
||||||
|
|||||||
@ -212,6 +212,7 @@ func templates(api *confluence.API) (*template.Template, error) {
|
|||||||
`ac:image`: text(
|
`ac:image`: text(
|
||||||
`<ac:image`,
|
`<ac:image`,
|
||||||
`{{ if .Align }} ac:align="{{ .Align }}"{{ end }}`,
|
`{{ if .Align }} ac:align="{{ .Align }}"{{ end }}`,
|
||||||
|
`{{ if eq .Align "left" }} ac:layout="align-start"{{ else if eq .Align "center" }} ac:layout="center"{{ else if eq .Align "right" }} ac:layout="align-end"{{ else if eq .Align "wide" }} ac:layout="center"{{ end }}`,
|
||||||
`{{ if .Width }} ac:width="{{ .Width }}"{{ end }}`,
|
`{{ if .Width }} ac:width="{{ .Width }}"{{ end }}`,
|
||||||
`{{ if .Height }} ac:height="{{ .Height }}"{{ end }}`,
|
`{{ if .Height }} ac:height="{{ .Height }}"{{ end }}`,
|
||||||
`{{ if .Title }} ac:title="{{ .Title }}"{{ end }}`,
|
`{{ if .Title }} ac:title="{{ .Title }}"{{ end }}`,
|
||||||
|
|||||||
2
testdata/links.html
vendored
2
testdata/links.html
vendored
@ -5,7 +5,7 @@
|
|||||||
<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="test_link"/><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="test_link"/><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:width="1000" ac:height="631" 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?key1=value1&key2=value2"/></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?key1=value1&key2=value2"/></ac:image></p>
|
||||||
<p><ac:link><ri:page ri:content-title="test_link"/><ac:plain-text-link-body><![CDATA[My test_link]]></ac:plain-text-link-body></ac:link></p>
|
<p><ac:link><ri:page ri:content-title="test_link"/><ac:plain-text-link-body><![CDATA[My test_link]]></ac:plain-text-link-body></ac:link></p>
|
||||||
<p><ac:link><ri:page ri:content-title="test_link_link"/><ac:plain-text-link-body><![CDATA[Another [Link]]]></ac:plain-text-link-body></ac:link></p>
|
<p><ac:link><ri:page ri:content-title="test_link_link"/><ac:plain-text-link-body><![CDATA[Another [Link]]]></ac:plain-text-link-body></ac:link></p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user