Skip to content

feat: Obsidian-style image width and local image upload#19

Merged
pilat merged 1 commit into
mainfrom
feat/image-width-local-upload
Jun 21, 2026
Merged

feat: Obsidian-style image width and local image upload#19
pilat merged 1 commit into
mainfrom
feat/image-width-local-upload

Conversation

@pilat

@pilat pilat commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Why

Local images referenced from markdown rendered as broken in Confluence. The converter emitted an <ri:attachment> reference, but nothing ever uploaded the file — so only remote (http(s)) images and rendered mermaid diagrams actually worked end-to-end. There was also no way to control how large an image displays.

What

  • Obsidian-style sizing. ![alt|300](url) sets width, ![alt|300x200](url) sets width and height — emitted as ac:width / ac:height on <ac:image>. Only a trailing |<digits> or |<digits>x<digits> segment is treated as a size; anything else (e.g. ![a|b](url)) stays verbatim in the alt text, so legitimate pipes keep working. No parser extension needed — the data already lives in node.alt.
  • Local image upload. A new preprocess pass walks the AST, reads each referenced local file, and feeds it into the existing attachment pipeline (the same one mermaid uses). Works for both Obsidian-sized and plain ![](./pic.png) references.

How it behaves

  • Attachment filenames are content-hashed (<stem>-<md5>.<ext>). Confluence attachments are flat per page, so two different files both named pic.png would otherwise overwrite each other; hashing also dedupes the same image referenced twice into a single upload.
  • A missing or unsupported-type image warns and skips — one broken reference never fails the whole sync (mirrors how mermaid handles a bad diagram). The converter falls back to the basename so the page still syncs.
  • Remote URLs and data: URIs are untouched.

Notes

  • Verified end-to-end against a real Confluence page: three local screenshots upload and render at their declared widths.
  • Tests cover width parsing (including the non-numeric-pipe and empty-alt cases), the basename fallback, content-hashing, dedup, and the warn-and-skip paths.
  • No version bump or CHANGELOG entry here — that goes in a separate release PR.

Summary by CodeRabbit

  • New Features

    • Added support for Obsidian-style image sizing syntax (e.g., ![alt|300](url) or ![alt|300x200](url)).
    • Implemented local image preprocessing and upload with deterministic content-based filenames.
  • Tests

    • Added comprehensive test coverage for image preprocessing and sizing functionality.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pilat, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 58 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b768e47-eef0-446b-a85e-d48f1c0d1d90

📥 Commits

Reviewing files that changed from the base of the PR and between 07f23a6 and 1132ef1.

📒 Files selected for processing (7)
  • src/elements/image.ts
  • src/images/preprocess.ts
  • src/sync.ts
  • src/types.ts
  • test/converter.test.ts
  • test/images.test.ts
  • test/sync.test.ts
📝 Walkthrough

Walkthrough

Adds a preprocessImages pipeline step that walks the mdast AST for local image references, reads each file, computes an MD5-based content-hashed filename, and registers binary data in context.attachments and URL-to-filename mappings in a new context.localImages map. The image element converter is updated to look up hashed filenames and to parse Obsidian-style alt|width / alt|WxH sizing syntax into ac:width/ac:height attributes.

Changes

Local Image Upload and Obsidian Sizing

Layer / File(s) Summary
ConversionContext type extension
src/types.ts
Adds localImages: Map<string, string> to ConversionContext to track original URL → hashed filename mappings.
Image preprocessing module
src/images/preprocess.ts
New module implementing MIME-type lookup, local URL classification, AST image collection, and preprocessImages which reads local files, hashes content for deterministic filenames, and populates context.attachments and context.localImages.
Image converter: hashed names and alt sizing
src/elements/image.ts
Local attachment resolution now uses context.localImages.get(url) with basename fallback; introduces AltSize type and parseAltSize helper to extract `
Sync pipeline wiring
src/sync.ts
Adds dirname and preprocessImages imports, initializes localImages in the conversion context, and inserts the preprocessImages call after preprocessMermaid.
Tests
test/images.test.ts, test/converter.test.ts, test/sync.test.ts
Full Vitest suite for preprocessImages (hashed upload, missing-file warnings, unsupported extensions, data-URI skipping, deduplication); extended image converter tests for Obsidian sizing syntax; context helpers and sync test patched with localImages map.

Sequence Diagram(s)

sequenceDiagram
  participant syncFile as syncFile (sync.ts)
  participant preprocessImages as preprocessImages
  participant disk as Disk (baseDir)
  participant context as ConversionContext
  participant imageConverter as image converter

  syncFile->>context: init localImages = new Map()
  syncFile->>preprocessImages: preprocessImages(ast, context, dirname)
  loop each local image node in AST
    preprocessImages->>disk: readFile(url)
    disk-->>preprocessImages: bytes
    preprocessImages->>preprocessImages: MD5 hash → hashedFilename
    preprocessImages->>context: attachments.set(hashedFilename, {data, contentType})
    preprocessImages->>context: localImages.set(url, hashedFilename)
  end
  preprocessImages-->>syncFile: done
  syncFile->>imageConverter: convert image node
  imageConverter->>context: localImages.get(url) → hashedFilename
  imageConverter-->>syncFile: ac:image XML with ri:attachment + ac:width/ac:height
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hippity-hop through the image files,
Hashing each pixel across many miles,
|300x200 parsed with flair,
Obsidian sizing now floats in the air,
No more bare basenames—content hashed and bright,
This rabbit attached every image just right! 🖼️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main features introduced: Obsidian-style image sizing and local image upload functionality, matching the primary changes across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/image-width-local-upload

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/sync.ts (1)

54-67: 💤 Low value

Comment could be more accurate.

Line 65's comment says "Upload local image files" but preprocessImages only reads and registers them in context.attachments. The actual upload happens later at line 132 via uploadAttachments.

📝 Suggested comment fix
-  // Upload local image files referenced by the markdown (populates context.attachments + localImages)
+  // Read and register local image files referenced by the markdown (populates context.attachments + localImages)
   await preprocessImages(ast, context, dirname(absolutePath))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sync.ts` around lines 54 - 67, The comment for the preprocessImages
function call is inaccurate. It states that preprocessImages "Upload local image
files" but the function actually only reads and registers them in
context.attachments and context.localImages. Update the comment above the
preprocessImages call to clarify that it prepares and registers local image
files rather than uploading them, and note that the actual upload happens later
during the uploadAttachments call.
test/images.test.ts (1)

88-101: ⚡ Quick win

Add converter output assertion for data: URIs.

This test verifies that preprocessing skips data: URIs, but it doesn't verify what the converter actually outputs for them. Given the data: URI handling issue in src/elements/image.ts, this test should also assert the expected converter behavior.

🧪 Suggested test extension
       expect(context.attachments.size).toBe(0)
       expect(context.localImages.size).toBe(0)
       expect(warn).not.toHaveBeenCalled()
+
+      // Verify converter output (should skip or handle data: URIs appropriately)
+      const xml = convert(ast, context)
+      // TODO: Update this assertion once data: URI handling is fixed in the converter
+      expect(xml).not.toContain('ri:attachment')
     } finally {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/images.test.ts` around lines 88 - 101, The test for skipping data: URIs
in the test function starting at line 88 verifies that no attachments or
warnings are created, but it doesn't assert what the converter actually outputs
for data: URIs. After the preprocessImages call in this test, add an assertion
that checks the actual converter output by examining the processed AST or the
result to verify that data: URIs are handled correctly in the converted content,
ensuring the converter properly preserves or processes these inline data URIs as
expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/elements/image.ts`:
- Around line 5-26: The image converter registered with registerConverter<Image>
does not handle data: URIs correctly. Currently, the isRemote check only detects
http:// and https://, so data: URIs fall through to the local file path logic,
which produces invalid markup like ri:attachment with the full data URI as a
filename. Add a check to detect data: URIs (starting with "data:") before or
within the isRemote condition to prevent them from reaching the local attachment
handling. Based on PR objectives that data: URIs should be "left untouched,"
either skip rendering them (return empty string or null) or return them as-is
rather than wrapping them in ri:attachment tags.

---

Nitpick comments:
In `@src/sync.ts`:
- Around line 54-67: The comment for the preprocessImages function call is
inaccurate. It states that preprocessImages "Upload local image files" but the
function actually only reads and registers them in context.attachments and
context.localImages. Update the comment above the preprocessImages call to
clarify that it prepares and registers local image files rather than uploading
them, and note that the actual upload happens later during the uploadAttachments
call.

In `@test/images.test.ts`:
- Around line 88-101: The test for skipping data: URIs in the test function
starting at line 88 verifies that no attachments or warnings are created, but it
doesn't assert what the converter actually outputs for data: URIs. After the
preprocessImages call in this test, add an assertion that checks the actual
converter output by examining the processed AST or the result to verify that
data: URIs are handled correctly in the converted content, ensuring the
converter properly preserves or processes these inline data URIs as expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c755d280-cdcd-48f3-b876-20430ecc6e30

📥 Commits

Reviewing files that changed from the base of the PR and between e2220a2 and 07f23a6.

📒 Files selected for processing (7)
  • src/elements/image.ts
  • src/images/preprocess.ts
  • src/sync.ts
  • src/types.ts
  • test/converter.test.ts
  • test/images.test.ts
  • test/sync.test.ts

Comment thread src/elements/image.ts
@pilat pilat self-assigned this Jun 21, 2026
@pilat pilat force-pushed the feat/image-width-local-upload branch from 07f23a6 to ab0aca9 Compare June 21, 2026 17:34
@pilat pilat force-pushed the feat/image-width-local-upload branch from 09a83f2 to 1132ef1 Compare June 21, 2026 18:08
@pilat pilat merged commit f222711 into main Jun 21, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant