761 Commits

Author SHA1 Message Date
dependabot[bot]
13e7b496ee build(deps): bump golang.org/x/text from 0.35.0 to 0.36.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.35.0 to 0.36.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 03:09:49 +09:00
Manuel Rüger
ac264210b5 Feature/robust comment preservation (#768)
This is based on guoweis-work PR https://github.com/kovetskiy/mark/pull/145

* feat(confluence): add support for fetching page body and inline comments

* feat(cmd): add --preserve-comments flag to preserve inline comments

* feat(mark): implement context-aware inline comment preservation

* test(mark): add tests for context-aware MergeComments logic

* fix: remove empty else branch in MergeComments to fix SA9003

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* perf: compile markerRegex once as package-level variable

Avoids recompiling the inline comment marker regex on every call to
MergeComments, which matters for pages with many comment markers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: guard against nil comments pointer in MergeComments

Prevents a panic when GetInlineComments returns nil (e.g. on pages
where the inline comments feature is not enabled).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add edge-case tests for MergeComments; fix overlapping replacement

Four new test cases:
- SelectionMissing: comment dropped gracefully when text is gone from new body
- OverlappingSelections: overlapping comments no longer corrupt the body;
  the later match (by position) wins and the earlier overlapping one is dropped
- NilComments: nil pointer returns new body unchanged
- HTMLEntities: &lt;, &gt;, &#39; selections match correctly

Also fixes the overlapping replacement bug: apply back-to-front and skip any
replacement whose end exceeds the start of an already-applied one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: escape ref attribute value in inline comment marker XML

Use html.EscapeString on r.ref before interpolating it into the
ac:ref attribute to prevent malformed XML if the value ever contains
quotes or other special characters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use first occurrence when no context is available in MergeComments

Without context the old code left distance=0 for every match and
updated bestStart on each iteration, so the final result depended on
whichever occurrence was visited last (non-deterministic with respect
to the search order).

Restructure the loop to break immediately on the first match when
hasCtx is false, making the behaviour explicit and deterministic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: log warning when overlapping inline comment marker is dropped

Previously the overlap was silently skipped. Now a zerolog Warn message
is emitted with the ref, the conflicting byte offsets, and the ref of
the already-placed marker, so users can see which comment was lost
rather than silently getting incomplete output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: warn when inline comments are silently dropped in MergeComments

Three cases now emit a zerolog Warn instead of silently discarding:

1. Comment location != "inline": logs ref and actual location.
2. Selected text not found in new body: logs ref and selection text.
3. Overlapping replacement (existing): adds selection text to the
   already-present overlap warning for easier diagnosis.

Also adds a selection field to the replacement struct so the overlap
warning can report the dropped text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: update markerRegex to match markers with nested tags

Replace ([^<]*) with (?s)(.*?) so the pattern:
- Matches marker content that contains nested inline tags (e.g. <strong>)
- Matches across newlines ((?s) / DOTALL mode)

The old character class [^<]* stopped at the first < inside the
marker body, causing the context-extraction step to miss any comment
whose original selection spanned formatted text.

Add TestMergeComments_NestedTags to cover this path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: guard against empty OriginalSelection in MergeComments

strings.Index(s, "") always returns 0, so an empty escapedSelection
would spin the search loop indefinitely (or panic when currentPos
advances past len(newBody)).

Skip comments with an empty selection early, emit a Warn log, and
add TestMergeComments_EmptySelection to cover the path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: paginate GetInlineComments to avoid silently truncating results

The Confluence child/comment endpoint is paginated. The previous
single-request implementation silently dropped any comments beyond
the server's default page size.

Changes:
- Add Links (context, next) to InlineComments struct so the _links
  field from each page response is decoded.
- Rewrite GetInlineComments to loop with limit/start parameters
  (pageSize=100), accumulating all results, following the same pattern
  used by GetAttachments and label fetching.
- Add TestMergeComments_DuplicateMarkerRef to cover the deduplication
  guard added in the previous commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix UTF-8 safety, API compat, log verbosity

- levenshteinDistance: convert to []rune before empty-string checks so
  rune counts (not byte counts) are returned for strings with multi-byte
  characters

- Add contextBefore/contextAfter helpers that use utf8.RuneStart to
  avoid slicing in the middle of a multi-byte UTF-8 sequence when
  extracting 100-char context windows from oldBody and newBody

- Add truncateSelection helper (50 runes + ellipsis) and apply it in all
  Warn log messages that include the selected text, preventing large or
  sensitive page content from appearing in logs

- Downgrade non-inline comment log from Warn to Debug with message
  'comment ignored during inline marker merge: not an inline comment';
  page-level comments are not inline markers and are not 'lost'

- Restore original one-argument GetPageByID (expand='ancestors,version')
  and add GetPageByIDExpanded for the one caller that needs a custom
  expand value, preserving backward compatibility for API consumers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address new PR review comments

- Remove custom min() function: shadows the Go 1.21+ built-in min for
  the entire package; the built-in handles the 3-arg call in
  levenshteinDistance identically

- Validate rune boundaries on strings.Index candidates: skip any match
  where start or end falls in the middle of a multi-byte UTF-8 rune
  to prevent corrupt UTF-8 output

- Defer preserve-comments API calls until after shouldUpdatePage is
  determined: avoids unnecessary GetPageByIDExpanded + GetInlineComments
  round-trips on no-op --changes-only runs

- Capitalize Usage string for --preserve-comments flag (util/flags.go)
  and matching README.md entry to match sentence case of surrounding flags

- Run gofmt on util/cli.go to fix struct literal field alignment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: document --preserve-comments feature in README

Add a dedicated 'Preserving Inline Comments' section under Tricks with:
- Usage examples (CLI flag and env var)
- Step-by-step explanation of the Levenshtein-based relocation algorithm
- Limitations (deleted text, overlapping selections, new pages,
  changes-only interaction)

Also add a cross-reference NOTE near the --preserve-comments flag entry
in the Usage section.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fix markdownlint errors in README

- Change unordered list markers from dashes to asterisks (MD004)
- Remove extra blank line before Issues section (MD012)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Extract named types for InlineComments; optimize Levenshtein search

- Introduce InlineCommentProperties, InlineCommentExtensions, and
  InlineCommentResult named types in confluence/api.go, replacing the
  anonymous nested struct in InlineComments.Results. Callers and tests
  can now construct/inspect comment objects without repeating the JSON
  shape.

- Simplify makeComments helper in mark_test.go to use the new named
  types directly, eliminating the verbose anonymous struct literal.

- Add two Levenshtein candidate-search optimisations in MergeComments:
  * Exact-context fast path: if both the before and after windows match
    exactly, take that occurrence immediately without computing distance.
  * Lower-bound pruning: skip the full O(m*n) Levenshtein computation
    for a candidate when the absolute difference in window lengths alone
    already meets or exceeds the current best distance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use stable sort with ref tie-breaker; fix README overlap description

- Replace slices.SortFunc with slices.SortStableFunc for the
  replacements slice, adding ref as a lexicographic tie-breaker when
  two markers resolve to the same start offset. This makes overlap
  resolution fully deterministic across runs.

- Correct the README limitation note: the *earlier* overlapping
  match (lower byte offset) is what gets dropped; the later one
  (higher byte offset, applied first in the back-to-front pass) is
  kept. The previous wording said 'the second one is dropped' which
  was ambiguous and inaccurate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix rune-based lower-bound pruning; clarify test comment

- Use utf8.RuneCountInString instead of len() for the Levenshtein
  lower-bound pruning computation. The levenshteinDistance function
  operates on rune slices, so byte-length differences can exceed the
  true rune-length difference for multibyte UTF-8 content, causing
  valid candidates to be incorrectly skipped.

- Update TestMergeComments_SelectionMissing comment to say the comment
  is 'dropped with a warning' rather than 'silently dropped', matching
  the actual behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add missing unit tests for helpers and MergeComments scenarios

Helper function tests:
- TestTruncateSelection: short/exact/long strings and multibyte runes
- TestLevenshteinDistance: empty strings, identical, insertions,
  deletions, substitutions, 'kitten/sitting', and a multibyte UTF-8
  case to exercise rune-based counting
- TestContextBefore / TestContextAfter: basic windowing, window larger
  than string, and a case where the raw byte offset lands mid-rune (é)
  to verify the rune-boundary correction logic

MergeComments scenario tests:
- TestMergeComments_MultipleComments: two non-overlapping comments both
  correctly applied via back-to-front replacement
- TestMergeComments_EmptyResults: non-nil InlineComments with zero
  results returns body unchanged
- TestMergeComments_NonInlineLocation: page-level comments (location
  != 'inline') are skipped; body unchanged
- TestMergeComments_NoContext: when a ref has no marker in oldBody the
  first occurrence of the selection in newBody is used
- TestMergeComments_UTF8: multibyte (Japanese) characters in both body
  and selection are handled correctly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix three correctness issues in MergeComments

- Fix html import shadowing: alias the 'html' import as 'stdhtml' to
  avoid shadowing by the local 'html' variable used throughout
  ProcessFile. Both callers updated: stdhtml.EscapeString for the
  ref attribute, htmlEscapeText for the selection search.

- Fix selection search with quotes/apostrophes: replace
  html.EscapeString for the selection with a new htmlEscapeText helper
  that only escapes &, <, > — not ' or ". Confluence storage HTML
  often leaves quotes and apostrophes unescaped in text nodes, so
  fully-escaped selections would fail to match and inline comments
  would be silently dropped. Add TestMergeComments_SelectionWithQuotes.

- Fix duplicate-ref warnings: move seenRefs[ref]=true to immediately
  after the duplicate-check, before the search loop. Previously seenRefs
  was only set on a successful match, so multiple results for the same
  MarkerRef with no match in the new body would each emit a 'dropped'
  warning. Add TestMergeComments_DuplicateMarkerRefDropped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Optimize levenshteinDistance to use two rolling rows instead of full matrix

Reduces memory allocation from O(m×n) to O(n) by keeping only the
previous and current rows. Also swaps r1/r2 so the shorter string is
used for column width, minimizing row allocation size.

This matters in MergeComments where levenshteinDistance is called for
every candidate match of every comment's selection in newBody — on
pages with many comments or short/common selections the number of
calls can be high.

Addresses thread [40] from PR review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix test description and README algorithm doc

mark_test.go (thread [43]):
- TestMergeComments_HTMLEntities: the description incorrectly claimed
  &#39; (apostrophe) was tested; the selection '<world>' contains no
  apostrophe. Updated comment to accurately describe what is covered
  (&lt;/&gt; entity matching) and note the &#39; limitation.
- Add TestMergeComments_ApostropheSelection: verifies a selection with
  a literal apostrophe is found when the new body also has a literal
  apostrophe (the common case from mark's renderer). This exercises
  the htmlEscapeText path which intentionally does not encode ' or ".

README.md (thread [42]):
- Step 2 of the algorithm description said context was recorded
  'immediately before and after the commented selection' which is
  ambiguous. Clarified that context windows are taken around the
  <ac:inline-comment-marker> tag boundaries in the old body (not
  around the raw selection text), so the context is stable even when
  the marker wraps additional inline markup such as <strong>.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Unexport mergeComments and cap candidate evaluation

Thread [44]: MergeComments was exported but is internal-only — only
called within the mark package and tested from the same package.
Unexport it to mergeComments to avoid expanding the public API surface
unnecessarily. Add a Go doc comment describing the function contract,
HTML expectations, and the candidate cap.

Thread [45]: The candidate-scoring loop had no upper bound. For short
or common selections (e.g. 'a', 'the') on large pages the loop could
invoke levenshteinDistance thousands of times, each allocating rune
and int slices. Add a maxCandidates=100 constant and break once that
many on-rune-boundary occurrences have been evaluated. The exact-context
fast-path and lower-bound pruning already skip many candidates before
Levenshtein is called, so in practice the cap is only reached for very
common selections where the 100th candidate is unlikely to be
meaningfully better than an earlier one anyway.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: fix HTMLEntities description and add ApostropheEncoded limitation test

Thread #43: TestMergeComments_HTMLEntities had a misleading note claiming it
covered the &#39; apostrophe case, but the selection under test ('<world>') did
not include an apostrophe. Remove that note and add a dedicated
TestMergeComments_ApostropheEncoded test that explicitly documents the known
limitation: when a Confluence body stores an apostrophe as the numeric entity
&#39;, mergeComments cannot locate the selection (htmlEscapeText does not
encode ' to &#39;), so the comment is dropped with a warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix CDATA selection fallback and extract contextWindowBytes constant

Thread #46: mergeComments only searched for htmlEscapeText(selection) and
would fail for selections inside CDATA-backed macro bodies (e.g. ac:code),
where < and > are stored as raw characters rather than HTML entities. Restructure
the search loop to build a searchForms slice: the escaped form is tried first
(covers normal XML text nodes), and the raw unescaped form is appended as a
fallback when they differ. A stopSearch flag exits early on an exact context
match or when maxCandidates is reached, preserving the same performance
guarantees as before. Add TestMergeComments_CDATASelection to cover this path.

Thread #47: The context-window size 100 was repeated in four places across
mergeComments (two in the context-extraction loop and two in the scoring loop).
Extract it to const contextWindowBytes = 100 so it is easy to tune and stays
consistent everywhere.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v16.3.0
2026-04-08 16:20:03 +02:00
Manuel Rüger
a43f5fec2e refactor: modernize Go primitives
- Replace interface{} with any (Go 1.18) across confluence/api.go,
  macro/macro.go, util/cli.go, util/error_handler.go, includes/templates.go
- Replace sort.SliceStable with slices.SortStableFunc + cmp.Compare (Go 1.21)
  in attachment/attachment.go, consistent with existing slices usage
- Replace fmt.Errorf("%s", msg) with errors.New(msg) in mark.go

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 01:39:29 +02:00
dependabot[bot]
4a5f3798eb build(deps): bump golang from 1.26.1 to 1.26.2
Bumps golang from 1.26.1 to 1.26.2.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 00:54:35 +02:00
dependabot[bot]
a29694ff41 build(deps): bump github.com/dreampuf/mermaid.go from 0.1.0 to 0.2.0
Bumps [github.com/dreampuf/mermaid.go](https://github.com/dreampuf/mermaid.go) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/dreampuf/mermaid.go/releases)
- [Commits](https://github.com/dreampuf/mermaid.go/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/dreampuf/mermaid.go
  dependency-version: 0.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 00:54:16 +02:00
Noam Asor
1c1eeb84fb feat: add GitHub Alerts transformer and renderers
Co-Authored-By: Manuel Rüger <manuel@rueg.eu>
v16.2.0
2026-03-31 20:54:07 +02:00
dependabot[bot]
b1de69c46a build(deps): bump golang.org/x/image from 0.20.0 to 0.38.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.20.0 to 0.38.0.
- [Commits](https://github.com/golang/image/compare/v0.20.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 18:28:47 +02:00
Manuel Rüger
97fa02511e fix: update mermaid.go to match NewRenderEngine signature and ensure cancellation v16.1.0 2026-03-30 11:44:34 +02:00
Manuel Rüger
ba67fdc54b docs: update README for v16 and document task lists 2026-03-30 11:23:01 +02:00
Manuel Rüger
0859bf4d08 refactor: replace karma-go with standard error handling 2026-03-30 11:23:01 +02:00
Manuel Rüger
e160121005 feat: replace logging with zerolog 2026-03-30 11:23:01 +02:00
dependabot[bot]
74c4a306c3 build(deps): bump github.com/urfave/cli/v3 from 3.7.0 to 3.8.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 14:18:00 +02:00
dependabot[bot]
5f01ad57c7 build(deps): bump DavidAnson/markdownlint-cli2-action from 22 to 23
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 22 to 23.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/v22...v23)

---
updated-dependencies:
- dependency-name: DavidAnson/markdownlint-cli2-action
  dependency-version: '23'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 14:17:50 +02:00
Manuel Rüger
724066906c refactor: replace regexputil-go with standard library SubexpIndex 2026-03-29 13:57:10 +02:00
Manuel Rüger
7be2325340 fix: restore fallback in GetUserByName for older Confluence APIs v16.0.2 2026-03-26 00:52:56 +01:00
dependabot[bot]
e3cb9afcf6 build(deps): bump github.com/dreampuf/mermaid.go from 0.0.39 to 0.0.40
Bumps [github.com/dreampuf/mermaid.go](https://github.com/dreampuf/mermaid.go) from 0.0.39 to 0.0.40.
- [Release notes](https://github.com/dreampuf/mermaid.go/releases)
- [Commits](https://github.com/dreampuf/mermaid.go/compare/v0.0.39...v0.0.40)

---
updated-dependencies:
- dependency-name: github.com/dreampuf/mermaid.go
  dependency-version: 0.0.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
v16.0.1
2026-03-25 14:14:35 +01:00
dependabot[bot]
d6403f3cf0 build(deps): bump github.com/yuin/goldmark from 1.8.1 to 1.8.2
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 14:08:31 +01:00
Manuel Rüger
5c59e4704b feat: integrate goldmark Pos() for better error reporting 2026-03-25 10:30:10 +01:00
dependabot[bot]
cad33f5097 build(deps): bump github.com/yuin/goldmark from 1.7.17 to 1.8.1
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.17 to 1.8.1.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.17...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 14:06:28 +01:00
dependabot[bot]
3752db4e28 build(deps): bump github.com/chromedp/chromedp from 0.14.2 to 0.15.1
Bumps [github.com/chromedp/chromedp](https://github.com/chromedp/chromedp) from 0.14.2 to 0.15.1.
- [Release notes](https://github.com/chromedp/chromedp/releases)
- [Commits](https://github.com/chromedp/chromedp/compare/v0.14.2...v0.15.1)

---
updated-dependencies:
- dependency-name: github.com/chromedp/chromedp
  dependency-version: 0.15.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 14:06:08 +01:00
Manuel Rüger
2fdcab25cc chore: bump module path to v16
Update Go module path from github.com/kovetskiy/mark to
github.com/kovetskiy/mark/v16 across all packages and imports,
following Go module versioning conventions for major versions >= 2.

Also update README installation instructions and version string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v16.0.0
2026-03-20 23:44:59 +01:00
copilot-swe-agent[bot]
9a26f657c7 Fix goreleaser config to point builds to ./cmd/mark
Co-authored-by: mrueg <489370+mrueg@users.noreply.github.com>
v15.5.0
2026-03-20 22:06:34 +01:00
copilot-swe-agent[bot]
61510ff358 Fix go install/get commands to use correct cmd/mark path
Co-authored-by: mrueg <489370+mrueg@users.noreply.github.com>
2026-03-20 22:06:34 +01:00
dependabot[bot]
98c9c949d3 build(deps): bump github.com/yuin/goldmark from 1.7.16 to 1.7.17
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.16 to 1.7.17.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.16...v1.7.17)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.7.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-20 22:06:27 +01:00
Manuel Rüger
17436efd17 renderer: HTML-escape admonition title and guard against empty attachments
mkDocsAdmonition: escape the admonition title with html.EscapeString
before inserting it into the Confluence storage format XML. An unescaped
title containing '<', '>', '&', or '"' would break the XML structure.

image: add a len(attachments)==0 guard before accessing attachments[0]
in the local-attachment code path. ResolveLocalAttachments always returns
either an error or the requested attachments, so this is currently
unreachable, but the explicit check prevents a future silent panic if the
function's behaviour changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
2b62ffd822 confluence: fix NewAPI double-slash and DeletePageLabel missing status check
NewAPI: normalize baseURL by trimming the trailing slash before building
rest and json-rpc endpoints. Previously the TrimSuffix only applied to
api.BaseURL but rest/json URLs were already constructed with the raw
(potentially trailing-slash) baseURL, producing double slashes like
'http://example.com//rest/api'.

DeletePageLabel: add a non-200/non-204 status check before the type
assertion. Without this guard any error response (400, 403, 500) would
fall through to request.Response.(*LabelInfo) and either panic or return
garbage data.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
807d057f7b stdlib: remove duplicate err check in New() and add XML escaping to user-controlled template values
Remove the dead second 'if err != nil' block after the already-checked
lib.Templates assignment.

Add html.EscapeString as 'xmlesc' template function and apply it to
user-controlled string parameters in ac:code, ac:status, and ac:box
templates. Values like .Title, .Color, .Language, and .Theme can contain
XML special characters (<, >, &, ") when supplied by users, which would
break Confluence storage format XML structure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
0d7caab5d8 fix: close response body on all paths in newErrorStatusNotOK
The 401 and 404 early-return paths returned without closing the HTTP
response body, leaking the underlying connection. Move the
defer body.Close() to the top of the function so it runs regardless
of which code path is taken.

fix: add HTTP status check to GetCurrentUser

GetCurrentUser did not validate the HTTP response status code. A
401/403/500 response was silently ignored and returned a zero-value
User pointer, causing callers (e.g. RestrictPageUpdatesCloud fallback)
to use an empty accountId.

fix: return nil on HTTP 204 from DeletePageLabel instead of panicking

DeletePageLabel accepted both 200 OK and 204 No Content as success, but
then unconditionally did request.Response.(*LabelInfo). On a 204 the
response body is empty so request.Response is nil; the type assertion
panics. Return (nil, nil) for 204 responses.

fix: paginate GetPageLabels to handle pages with >50 labels

A single request with the default page size silently truncated label
lists longer than the API default (~50). Add a pagination loop
matching the pattern used by GetAttachments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
14219aea59 fix: return error from Run() when ContinueOnError files fail
When --continue-on-error was set and one or more files failed to
process, Run() logged each failure but returned nil, making it
impossible for callers or CI systems to detect partial failures.

Track whether any file failed with a hasErrors flag and return a
descriptive error after all files have been attempted.

Update TestContinueOnError to reflect the corrected behaviour: the
test now asserts that an error IS returned (partial failure is
surfaced) while still verifying that all files in the batch are
attempted (not just the first one).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
7d4d0458ca fix: narrow vfs.Opener interface from ReadWriteCloser to ReadCloser
All callers only read from the opened file (io.ReadAll + Close). Using
io.ReadWriteCloser in the interface was misleading and violated the
principle of interface segregation. os.Open returns a read-only file
that satisfies io.ReadCloser but not the write contract implied by
io.ReadWriteCloser. Narrow both the interface and the implementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
d6b37affd3 fix: use index-based loop in ResolveLocalAttachments to persist checksums
The range loop 'for _, attachment := range attachments' copied each
element by value. Assigning attachment.Checksum inside the loop only
modified the local copy; the original slice element was never updated.
All returned attachments had empty Checksum fields, causing every
attachment to be treated as changed on every run (the checksum
comparison would never match).

Switch to an index-based loop so the checksum is written directly to
the slice element.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
ed6ae15500 fix: add HTTP status checks to GetUserByName; remove redundant FindHomePage check
GetUserByName made two REST requests without checking the HTTP status
codes. A 401/403/500 response would silently be treated as an empty
result set and return 'user not found' instead of the real error.
Add a status check after each request.

FindHomePage had 'StatusNotFound || != StatusOK' — the first clause
is always a subset of the second, making it dead code. Simplified to
just '!= StatusOK'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
0d735203dd fix: trim whitespace from ParseTitle result
ParseTitle returned lang[start:] without trimming, so inputs like
'python title  My Title' returned '  My Title' with leading spaces.
The extra whitespace propagated into the rendered Confluence title
element. Add strings.TrimSpace to remove leading/trailing whitespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
d714bc9d2b fix: move scanner.Err() check after loop and fix matches bounds check
Two bugs in ExtractMeta:

1. scanner.Err() was checked inside the Scan() loop. According to the
   bufio.Scanner docs, Err() returns nil during a successful scan; it
   only reflects an error after Scan() returns false. Moving the check
   after the loop ensures real I/O errors are surfaced instead of
   silently ignored.

2. The len(matches) guard was 'len(matches) > 1' but the code
   accessed matches[2] (requires len >= 3). reHeaderPatternV2 always
   produces a 3-element slice when it matches, but the condition was
   misleading and could panic if the regex were ever changed to make
   the second capture group optional. Changed to 'len(matches) > 2'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
cf0699c088 fix: remove dead level-map infrastructure in mkDocsAdmonitionRenderer
GenerateMkDocsAdmonitionLevel walked the AST looking for
ast.KindBlockquote nodes to build a nesting-level map, but the
renderer is registered for parser.KindAdmonition nodes. Because
admonition nodes were never added to the map, LevelMap.Level()
always returned 0 for every admonition, making the level check
in renderMkDocsAdmonition a no-op.

The intended behaviour (all admonitions rendered as Confluence
structured macros regardless of nesting) was accidentally working
because of this bug. Remove the dead MkDocsAdmonitionLevelMap type,
GenerateMkDocsAdmonitionLevel function, and LevelMap field, and
simplify renderMkDocsAdmonition to directly render the Confluence
macro for all known admonition types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
b7c9229da4 fix: RestrictPageUpdatesCloud now resolves allowedUser by name
The allowedUser parameter was completely ignored; the function always
restricted edits to the currently authenticated API user via
GetCurrentUser(). Resolve the specified user via GetUserByName first
and fall back to the current user only if that lookup fails, matching
the behaviour of RestrictPageUpdatesServer which uses the parameter
directly.

fix: paginate GetAttachments to handle pages with >100 attachments

The previous implementation fetched a single page of up to 1000
attachments. Pages with more than 1000 attachments would silently
miss some, causing attachment sync to skip or re-upload them.
Replace with a pagination loop (100 per page) that follows the
_links.next cursor until all attachments are retrieved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
fef39dc1e0 fix: preserve include directive in output on error in ProcessIncludes
All error paths in the ReplaceAllFunc callback returned nil, which
ReplaceAllFunc treats as an empty replacement, silently erasing the
guard (if err != nil) fired, every subsequent include in the file
was also erased. Return spec (the original directive bytes) instead
so failed includes are preserved and subsequent ones are not lost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
4c81c81fb3 fix: return original match on error in macro Apply()
Two bugs in the ReplaceAllFunc callback:
1. After yaml.Unmarshal failure, execution continued into Execute(),
   which could succeed and overwrite err with nil, silently swallowing
   the unmarshal error and producing output with default (empty) config.
2. After any error, the callback returned buffer.Bytes() (empty or
   partial) instead of the original match, corrupting the document.

Return the original match bytes unchanged on either error so the
directive is preserved in output and the error is not lost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
3e71d65f61 fix: remove unused newLabels parameter from UpdatePage
The newLabels parameter was accepted but never used in the function
body; labels are synced through the separate updateLabels/AddPageLabels
/DeletePageLabel calls. The dead parameter misled callers into thinking
labels were being set during the page update.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
5b87945f23 fix: use ancestry slice for warning log, not meta.Parents
ancestry grows to len(meta.Parents)+1. The subsequent warning log
indexed meta.Parents[len(ancestry)-1], which is one past the end of
meta.Parents, causing an out-of-bounds panic. Use ancestry[len(ancestry)-1]
to always reference the last element of the actual slice being validated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
566fd74efe fix: validate emoji rune from utf8.DecodeRuneInString
DecodeRuneInString returns utf8.RuneError for invalid UTF-8, which was
silently converted to the hex string "fffd" and sent to Confluence.
Return an error instead so the caller gets a clear diagnostic rather
than storing a replacement character as the page emoji.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
2a505a24a8 fix: parseLinks regex misses links at position 0 of string
The previous pattern [^\!]\[...\] required exactly one non-! character
before the opening bracket, so a markdown link at the very start of
anchors to start-of-string or a non-! character without consuming
a required preceding character.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
594c1e4fa0 fix: guard FirstChild() nil in paragraph renderer
A paragraph node with no children causes FirstChild() to return nil,
making both the entering and leaving Kind() checks panic. Cache the
result once and treat nil the same as a non-RawHTML child (emit <p>).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
9184e91268 fix: defer body.Close() before ReadAll to ensure it runs on read error
The defer was placed after io.ReadAll, so if ReadAll returned an
error the body would not be closed. Move the defer before the read.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
a4ef890247 fix: trim whitespace from stdin password
When password is read from stdin (e.g. echo password | mark ...),
the trailing newline was included in the password string, causing
authentication to fail. Use strings.TrimSpace to strip it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
a481424f7b fix: return error instead of panic from CompileMarkdown
Markdown conversion failures called panic(err), crashing the process
rather than allowing graceful error handling. Change the return type
to (string, []attachment.Attachment, error) and propagate the error.
Update all callers (mark.go, markdown_test.go) accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
32577c91f4 fix: return error instead of panic in ResolveAttachments; fix wrong slice index in log
Two issues in attachment handling:
1. GetAttachments failure called panic(err) instead of returning an
   error, crashing the process on any API failure.
2. The 'keeping unmodified' log loop indexed into the original
   attachments slice using the range of existing, causing wrong names
   to be logged and a potential out-of-bounds panic when existing is
   longer than attachments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
9e7f2cf9d5 fix: check os.ReadFile error before using file contents
http.DetectContentType was called with potentially nil/empty contents
when ReadFile failed; the error was only checked afterward. Move the
error check immediately after ReadFile so bad data is never used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 19:18:29 +01:00
Manuel Rüger
8205794e7b Update Makefile
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-13 01:05:32 +01:00
Manuel Rüger
664a99cd00 test: restore global log level after each SetLogLevel subtest
SetLogLevel mutates a process-global logger, leaking state into
subsequent tests and causing order-dependent failures. Save the
current level before each subtest and restore it via t.Cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 01:05:32 +01:00