feat: add support for --content-appearance

This commit is contained in:
Johan Fagerberg 2026-03-03 14:19:57 +01:00 committed by Manuel Rüger
parent d2717f031d
commit 9516939c7d
8 changed files with 96 additions and 12 deletions

View File

@ -838,6 +838,7 @@ GLOBAL OPTIONS:
--space string use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE] --space string use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE]
--parents string A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS] --parents string A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS]
--parents-delimiter string The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER] --parents-delimiter string The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER]
--content-appearance string default content appearance for pages without a Content-Appearance header. Possible values: full-width, fixed. [$MARK_CONTENT_APPEARANCE]
--mermaid-scale float defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE] --mermaid-scale float defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE]
--include-path string Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH] --include-path string Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH]
--changes-only Avoids re-uploading pages that haven't changed since the last run. [$MARK_CHANGES_ONLY] --changes-only Avoids re-uploading pages that haven't changed since the last run. [$MARK_CHANGES_ONLY]

View File

@ -29,7 +29,7 @@ func main() {
Flags: util.Flags, Flags: util.Flags,
EnableShellCompletion: true, EnableShellCompletion: true,
HideHelpCommand: true, HideHelpCommand: true,
Before: util.CheckMutuallyExclusiveTitleFlags, Before: util.CheckFlags,
Action: util.RunMark, Action: util.RunMark,
} }

View File

@ -51,7 +51,7 @@ var (
reHeaderPatternMacro = regexp.MustCompile(`<!-- Macro: .*`) reHeaderPatternMacro = regexp.MustCompile(`<!-- Macro: .*`)
) )
func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFilename bool, filename string, parents []string, titleAppendGeneratedHash bool) (*Meta, []byte, error) { func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFilename bool, filename string, parents []string, titleAppendGeneratedHash bool, defaultContentAppearance string) (*Meta, []byte, error) {
var ( var (
meta *Meta meta *Meta
offset int offset int
@ -81,7 +81,6 @@ func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFi
if meta == nil { if meta == nil {
meta = &Meta{} meta = &Meta{}
meta.Type = "page" // Default if not specified meta.Type = "page" // Default if not specified
meta.ContentAppearance = FullWidthContentAppearance // Default to full-width for backwards compatibility
} }
header := cases.Title(language.English).String(matches[1]) header := cases.Title(language.English).String(matches[1])
@ -152,10 +151,6 @@ func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFi
meta.Type = "page" meta.Type = "page"
} }
if meta.ContentAppearance == "" {
meta.ContentAppearance = FullWidthContentAppearance // Default to full-width for backwards compatibility
}
if titleFromH1 && meta.Title == "" { if titleFromH1 && meta.Title == "" {
meta.Title = ExtractDocumentLeadingH1(data) meta.Title = ExtractDocumentLeadingH1(data)
} }
@ -167,6 +162,17 @@ func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, titleFromFi
} }
} }
// Use the global content appearance flag if the header is not set in the document
if meta != nil && defaultContentAppearance != "" && meta.ContentAppearance == "" {
if strings.TrimSpace(defaultContentAppearance) == FixedContentAppearance {
meta.ContentAppearance = FixedContentAppearance
} else {
meta.ContentAppearance = FullWidthContentAppearance
}
} else if meta != nil && meta.ContentAppearance == "" {
meta.ContentAppearance = FullWidthContentAppearance // Default to full-width if nothing else is set for backwards compatibility
}
if meta == nil { if meta == nil {
return nil, data, nil return nil, data, nil
} }

View File

@ -60,3 +60,32 @@ func TestSetTitleFromFilename(t *testing.T) {
assert.Equal(t, "Already Title Cased", meta.Title) assert.Equal(t, "Already Title Cased", meta.Title)
}) })
} }
func TestExtractMetaContentAppearance(t *testing.T) {
t.Run("default fills missing content appearance", func(t *testing.T) {
data := []byte("<!-- Space: DOC -->\n<!-- Title: Example -->\n\nbody\n")
meta, _, err := ExtractMeta(data, "", false, false, "", nil, false, FixedContentAppearance)
assert.NoError(t, err)
assert.NotNil(t, meta)
assert.Equal(t, FixedContentAppearance, meta.ContentAppearance)
})
t.Run("header takes precedence over default", func(t *testing.T) {
data := []byte("<!-- Space: DOC -->\n<!-- Title: Example -->\n<!-- Content-Appearance: full-width -->\n\nbody\n")
meta, _, err := ExtractMeta(data, "", false, false, "", nil, false, FixedContentAppearance)
assert.NoError(t, err)
assert.NotNil(t, meta)
assert.Equal(t, FullWidthContentAppearance, meta.ContentAppearance)
})
t.Run("falls back to full-width when default isn't set", func(t *testing.T) {
data := []byte("<!-- Space: DOC -->\n<!-- Title: Example -->\n\nbody\n")
meta, _, err := ExtractMeta(data, "", false, false, "", nil, false, "")
assert.NoError(t, err)
assert.NotNil(t, meta)
assert.Equal(t, FullWidthContentAppearance, meta.ContentAppearance)
})
}

View File

@ -122,7 +122,7 @@ func resolveLink(
// This helps to determine if found link points to file that's // This helps to determine if found link points to file that's
// not markdown or have mark required metadata // not markdown or have mark required metadata
linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceForLinks, titleFromH1, titleFromFilename, filepath, parents, titleAppendGeneratedHash) linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceForLinks, titleFromH1, titleFromFilename, filepath, parents, titleAppendGeneratedHash, "")
if err != nil { if err != nil {
log.Errorf( log.Errorf(
err, err,

View File

@ -116,7 +116,7 @@ func processFile(
parents := strings.Split(cmd.String("parents"), cmd.String("parents-delimiter")) parents := strings.Split(cmd.String("parents"), cmd.String("parents-delimiter"))
meta, markdown, err := metadata.ExtractMeta(markdown, cmd.String("space"), cmd.Bool("title-from-h1"), cmd.Bool("title-from-filename"), file, parents, cmd.Bool("title-append-generated-hash")) meta, markdown, err := metadata.ExtractMeta(markdown, cmd.String("space"), cmd.Bool("title-from-h1"), cmd.Bool("title-from-filename"), file, parents, cmd.Bool("title-append-generated-hash"), cmd.String("content-appearance"))
if err != nil { if err != nil {
fatalErrorHandler.Handle(err, "unable to extract metadata from file %q", file) fatalErrorHandler.Handle(err, "unable to extract metadata from file %q", file)
return nil return nil

View File

@ -12,8 +12,9 @@ func runWithArgs(args []string) error {
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{Name: "title-from-h1"}, &cli.BoolFlag{Name: "title-from-h1"},
&cli.BoolFlag{Name: "title-from-filename"}, &cli.BoolFlag{Name: "title-from-filename"},
&cli.StringFlag{Name: "content-appearance"},
}, },
Before: CheckMutuallyExclusiveTitleFlags, Before: CheckFlags,
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
return nil return nil
}, },
@ -50,3 +51,26 @@ func TestCheckMutuallyExclusiveTitleFlags(t *testing.T) {
} }
}) })
} }
func TestContentAppearanceFlagValidation(t *testing.T) {
t.Run("fixed is accepted", func(t *testing.T) {
err := runWithArgs([]string{"cmd", "--content-appearance", "fixed"})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
t.Run("full-width is accepted", func(t *testing.T) {
err := runWithArgs([]string{"cmd", "--content-appearance", "full-width"})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
t.Run("invalid value is rejected", func(t *testing.T) {
err := runWithArgs([]string{"cmd", "--content-appearance", "nope"})
if err == nil {
t.Errorf("expected error, got nil")
}
})
}

View File

@ -3,6 +3,7 @@ package util
import ( import (
"context" "context"
"errors" "errors"
"fmt"
altsrc "github.com/urfave/cli-altsrc/v3" altsrc "github.com/urfave/cli-altsrc/v3"
altsrctoml "github.com/urfave/cli-altsrc/v3/toml" altsrctoml "github.com/urfave/cli-altsrc/v3/toml"
@ -164,6 +165,15 @@ var Flags = []cli.Flag{
Usage: "The delimiter used for the parents list", Usage: "The delimiter used for the parents list",
Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_PARENTS_DELIMITER"), altsrctoml.TOML("parents-delimiter", altsrc.NewStringPtrSourcer(&filename))), Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_PARENTS_DELIMITER"), altsrctoml.TOML("parents-delimiter", altsrc.NewStringPtrSourcer(&filename))),
}, },
&cli.StringFlag{
Name: "content-appearance",
Value: "",
Usage: "default content appearance for pages without a Content-Appearance header. Possible values: full-width, fixed.",
Sources: cli.NewValueSourceChain(
cli.EnvVar("MARK_CONTENT_APPEARANCE"),
altsrctoml.TOML("content-appearance", altsrc.NewStringPtrSourcer(&filename)),
),
},
&cli.FloatFlag{ &cli.FloatFlag{
Name: "mermaid-scale", Name: "mermaid-scale",
Value: 1.0, Value: 1.0,
@ -204,10 +214,24 @@ var Flags = []cli.Flag{
}, },
} }
// CheckMutuallyExclusiveTitleFlags checks if both title-from-h1 and title-from-filename are set // CheckFlags validates combinations and values of global flags.
func CheckMutuallyExclusiveTitleFlags(context context.Context, command *cli.Command) (context.Context, error) { func CheckFlags(context context.Context, command *cli.Command) (context.Context, error) {
if command.Bool("title-from-h1") && command.Bool("title-from-filename") { if command.Bool("title-from-h1") && command.Bool("title-from-filename") {
return context, errors.New("flags --title-from-h1 and --title-from-filename are mutually exclusive. Please specify only one") return context, errors.New("flags --title-from-h1 and --title-from-filename are mutually exclusive. Please specify only one")
} }
contentAppearance := command.String("content-appearance")
if contentAppearance != "" {
switch contentAppearance {
case "full-width", "fixed":
// ok
default:
return context, fmt.Errorf(
"invalid value for --content-appearance: %q (expected: full-width or fixed)",
contentAppearance,
)
}
}
return context, nil return context, nil
} }