diff --git a/README.md b/README.md index 4194abf..f4e2ac6 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,14 @@ You can set a page emoji icon by specifying the icon in the headers. ``` -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.: diff --git a/attachment/attachment.go b/attachment/attachment.go index cd3bd6b..06ce50c 100644 --- a/attachment/attachment.go +++ b/attachment/attachment.go @@ -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 { diff --git a/renderer/fencedcodeblock.go b/renderer/fencedcodeblock.go index 1e5e9b4..064b099 100644 --- a/renderer/fencedcodeblock.go +++ b/renderer/fencedcodeblock.go @@ -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, diff --git a/renderer/image.go b/renderer/image.go index 474ca7f..52d08a2 100644 --- a/renderer/image.go +++ b/renderer/image.go @@ -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, diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 370fcb0..a22665b 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -212,6 +212,7 @@ func templates(api *confluence.API) (*template.Template, error) { `ac:image`: text( `Use

Use

Use

-

+