feat: add support for '--image-align'

This commit is contained in:
Johan Fagerberg 2026-02-18 09:15:51 +01:00 committed by Manuel Rüger
parent 875723da90
commit c32cd79dc8
8 changed files with 62 additions and 2 deletions

View File

@ -30,6 +30,7 @@ File in the extended format should follow the specification:
<!-- Attachment: <local path> --> <!-- Attachment: <local path> -->
<!-- Label: <label 1> --> <!-- Label: <label 1> -->
<!-- Label: <label 2> --> <!-- Label: <label 2> -->
<!-- Image-Align: <left|center|right> -->
<page contents> <page contents>
``` ```
@ -73,6 +74,12 @@ Setting the sidebar creates a column on the right side. You're able to add any
You can set a page emoji icon by specifying the icon in the headers. You can set a page emoji icon by specifying the icon in the headers.
```markdown
<!-- 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).
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.:
@ -845,6 +852,7 @@ GLOBAL OPTIONS:
--d2-scale float defines the scaling factor for d2 renderings. (default: 1) [$MARK_D2_SCALE] --d2-scale float defines the scaling factor for d2 renderings. (default: 1) [$MARK_D2_SCALE]
--features string [ --features string ] Enables optional features. Current features: d2, mermaid, mention, mkdocsadmonitions (default: "mermaid", "mention") [$MARK_FEATURES] --features string [ --features string ] Enables optional features. Current features: d2, mermaid, mention, mkdocsadmonitions (default: "mermaid", "mention") [$MARK_FEATURES]
--insecure-skip-tls-verify skip TLS certificate verification (useful for self-signed certificates) [$MARK_INSECURE_SKIP_TLS_VERIFY] --insecure-skip-tls-verify skip TLS certificate verification (useful for self-signed certificates) [$MARK_INSECURE_SKIP_TLS_VERIFY]
--image-align string set image alignment (left, center, right). Can be overridden per-file via the Image-Align header. [$MARK_IMAGE_ALIGN]
--help, -h show help --help, -h show help
--version, -v print the version --version, -v print the version
``` ```
@ -859,6 +867,7 @@ password = "password-or-api-key-for-confluence-cloud"
base-url = "http://confluence.local" base-url = "http://confluence.local"
title-from-h1 = true title-from-h1 = true
drop-h1 = true drop-h1 = true
image-align = "center"
``` ```
**NOTE**: Labels aren't supported when using `minor-edit`! **NOTE**: Labels aren't supported when using `minor-edit`!

View File

@ -53,7 +53,7 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) {
util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MarkConfig), 100), util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MarkConfig), 100),
util.Prioritized(crenderer.NewConfluenceHTMLBlockRenderer(c.Stdlib), 100), util.Prioritized(crenderer.NewConfluenceHTMLBlockRenderer(c.Stdlib), 100),
util.Prioritized(crenderer.NewConfluenceHeadingRenderer(c.MarkConfig.DropFirstH1), 100), util.Prioritized(crenderer.NewConfluenceHeadingRenderer(c.MarkConfig.DropFirstH1), 100),
util.Prioritized(crenderer.NewConfluenceImageRenderer(c.Stdlib, c, c.Path), 100), util.Prioritized(crenderer.NewConfluenceImageRenderer(c.Stdlib, c, c.Path, c.MarkConfig.ImageAlign), 100),
util.Prioritized(crenderer.NewConfluenceParagraphRenderer(), 100), util.Prioritized(crenderer.NewConfluenceParagraphRenderer(), 100),
util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100), util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100),
util.Prioritized(crenderer.NewConfluenceTaskListRenderer(), 100), util.Prioritized(crenderer.NewConfluenceTaskListRenderer(), 100),

View File

@ -26,6 +26,7 @@ const (
HeaderInclude = `Include` HeaderInclude = `Include`
HeaderSidebar = `Sidebar` HeaderSidebar = `Sidebar`
ContentAppearance = `Content-Appearance` ContentAppearance = `Content-Appearance`
HeaderImageAlign = `Image-Align`
) )
type Meta struct { type Meta struct {
@ -39,6 +40,7 @@ type Meta struct {
Attachments []string Attachments []string
Labels []string Labels []string
ContentAppearance string ContentAppearance string
ImageAlign string
} }
const ( const (
@ -130,6 +132,17 @@ func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFi
meta.ContentAppearance = FullWidthContentAppearance meta.ContentAppearance = FullWidthContentAppearance
} }
case HeaderImageAlign:
align := strings.ToLower(strings.TrimSpace(value))
if align != "left" && align != "center" && align != "right" {
log.Warningf(
nil,
`unknown image alignment %q, expected one of: left, center, right; passing through to Confluence`,
value,
)
}
meta.ImageAlign = align
default: default:
log.Errorf( log.Errorf(
nil, nil,

View File

@ -20,15 +20,17 @@ type ConfluenceImageRenderer struct {
Stdlib *stdlib.Lib Stdlib *stdlib.Lib
Path string Path string
Attachments attachment.Attacher Attachments attachment.Attacher
ImageAlign string
} }
// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer // NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
func NewConfluenceImageRenderer(stdlib *stdlib.Lib, attachments attachment.Attacher, path string, opts ...html.Option) renderer.NodeRenderer { func NewConfluenceImageRenderer(stdlib *stdlib.Lib, attachments attachment.Attacher, path string, imageAlign string, opts ...html.Option) renderer.NodeRenderer {
return &ConfluenceImageRenderer{ return &ConfluenceImageRenderer{
Config: html.NewConfig(), Config: html.NewConfig(),
Stdlib: stdlib, Stdlib: stdlib,
Path: path, Path: path,
Attachments: attachments, Attachments: attachments,
ImageAlign: imageAlign,
} }
} }
@ -55,6 +57,7 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
writer, writer,
"ac:image", "ac:image",
struct { struct {
Align string
Width string Width string
Height string Height string
Title string Title string
@ -62,6 +65,7 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
Attachment string Attachment string
Url string Url string
}{ }{
r.ImageAlign,
"", "",
"", "",
string(n.Title), string(n.Title),
@ -78,6 +82,7 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
writer, writer,
"ac:image", "ac:image",
struct { struct {
Align string
Width string Width string
Height string Height string
Title string Title string
@ -85,6 +90,7 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
Attachment string Attachment string
Url string Url string
}{ }{
r.ImageAlign,
"", "",
"", "",
string(n.Title), string(n.Title),

View File

@ -211,6 +211,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 .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 }}`,

View File

@ -6,4 +6,5 @@ type MarkConfig struct {
DropFirstH1 bool DropFirstH1 bool
StripNewlines bool StripNewlines bool
Features []string Features []string
ImageAlign string
} }

View File

@ -223,6 +223,7 @@ func processFile(
DropFirstH1: cmd.Bool("drop-h1"), DropFirstH1: cmd.Bool("drop-h1"),
StripNewlines: cmd.Bool("strip-linebreaks"), StripNewlines: cmd.Bool("strip-linebreaks"),
Features: cmd.StringSlice("features"), Features: cmd.StringSlice("features"),
ImageAlign: getImageAlign(cmd, meta),
} }
html, _ := mark.CompileMarkdown(markdown, stdlib, file, cfg) html, _ := mark.CompileMarkdown(markdown, stdlib, file, cfg)
fmt.Println(html) fmt.Println(html)
@ -302,6 +303,7 @@ func processFile(
DropFirstH1: cmd.Bool("drop-h1"), DropFirstH1: cmd.Bool("drop-h1"),
StripNewlines: cmd.Bool("strip-linebreaks"), StripNewlines: cmd.Bool("strip-linebreaks"),
Features: cmd.StringSlice("features"), Features: cmd.StringSlice("features"),
ImageAlign: getImageAlign(cmd, meta),
} }
html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cfg) html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cfg)
@ -474,6 +476,28 @@ func determineLabelsToAdd(meta *metadata.Meta, labelInfo *confluence.LabelInfo)
return labels return labels
} }
func getImageAlign(cmd *cli.Command, meta *metadata.Meta) string {
// Header comment takes precedence over CLI flag
if meta != nil && meta.ImageAlign != "" {
return meta.ImageAlign
}
cliAlign := cmd.String("image-align")
if cliAlign != "" {
align := strings.ToLower(strings.TrimSpace(cliAlign))
if align != "left" && align != "center" && align != "right" {
log.Warningf(
nil,
"unknown --image-align value %q, expected one of: left, center, right; passing through to Confluence",
cliAlign,
)
}
return align
}
return ""
}
func ConfigFilePath() string { func ConfigFilePath() string {
fp, err := os.UserConfigDir() fp, err := os.UserConfigDir()
if err != nil { if err != nil {

View File

@ -213,6 +213,12 @@ var Flags = []cli.Flag{
Usage: "skip TLS certificate verification (useful for self-signed certificates)", Usage: "skip TLS certificate verification (useful for self-signed certificates)",
Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_INSECURE_SKIP_TLS_VERIFY"), altsrctoml.TOML("insecure-skip-tls-verify", altsrc.NewStringPtrSourcer(&filename))), Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_INSECURE_SKIP_TLS_VERIFY"), altsrctoml.TOML("insecure-skip-tls-verify", altsrc.NewStringPtrSourcer(&filename))),
}, },
&cli.StringFlag{
Name: "image-align",
Value: "",
Usage: "set image alignment (left, center, right). Can be overridden per-file via the Image-Align header.",
Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_IMAGE_ALIGN"), altsrctoml.TOML("image-align", altsrc.NewStringPtrSourcer(&filename))),
},
} }
// CheckFlags validates combinations and values of global flags. // CheckFlags validates combinations and values of global flags.