feat: add support for image dimensions

This commit is contained in:
Johan Fagerberg 2026-02-19 09:44:33 +01:00 committed by Manuel Rüger
parent c32cd79dc8
commit 4d887bde74
6 changed files with 65 additions and 7 deletions

View File

@ -78,7 +78,14 @@ You can set a page emoji icon by specifying the icon in the headers.
<!-- 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
to the template relative to current working dir, e.g.:

View File

@ -4,11 +4,16 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"net/url"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"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{
attachment := Attachment{
Name: name,
Filename: strings.ReplaceAll(name, "/", "_"),
FileBytes: fileBytes,
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 {

View File

@ -139,10 +139,14 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
return ast.WalkStop, err
}
r.Attachments.Attach(attachment)
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
struct {
Align string
Width string
Height string
Title string
@ -150,6 +154,7 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
Attachment string
Url string
}{
effectiveAlign,
attachment.Width,
attachment.Height,
attachment.Name,
@ -170,10 +175,14 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
return ast.WalkStop, err
}
r.Attachments.Attach(attachment)
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
struct {
Align string
Width string
Height string
Title string
@ -181,6 +190,7 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
Attachment string
Url string
}{
effectiveAlign,
attachment.Width,
attachment.Height,
attachment.Name,

View File

@ -3,6 +3,7 @@ package renderer
import (
"bytes"
"path/filepath"
"strconv"
"strings"
"github.com/kovetskiy/mark/attachment"
@ -15,6 +16,30 @@ import (
"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 {
html.Config
Stdlib *stdlib.Lib
@ -78,6 +103,8 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
r.Attachments.Attach(attachments[0])
effectiveAlign := calculateAlign(r.ImageAlign, attachments[0].Width)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
@ -90,9 +117,9 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
Attachment string
Url string
}{
r.ImageAlign,
"",
"",
effectiveAlign,
attachments[0].Width,
attachments[0].Height,
string(n.Title),
string(nodeToHTMLText(n, source)),
attachments[0].Filename,

View File

@ -212,6 +212,7 @@ func templates(api *confluence.API) (*template.Template, error) {
`ac:image`: text(
`<ac:image`,
`{{ 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 .Height }} ac:height="{{ .Height }}"{{ end }}`,
`{{ if .Title }} ac:title="{{ .Title }}"{{ end }}`,

2
testdata/links.html vendored
View File

@ -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="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><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&amp;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_link"/><ac:plain-text-link-body><![CDATA[Another [Link]]]></ac:plain-text-link-body></ac:link></p>