381 lines
9.8 KiB
Markdown
381 lines
9.8 KiB
Markdown
# Obsidian Image Rendering - Implementation Guide
|
|
|
|
## Overview
|
|
|
|
ObsiGate now supports comprehensive Obsidian-compatible image rendering with intelligent multi-strategy path resolution. This document provides implementation details, testing guidance, and troubleshooting tips.
|
|
|
|
---
|
|
|
|
## Features Implemented
|
|
|
|
### ✅ Supported Image Syntaxes
|
|
|
|
1. **Standard Markdown with HTML attributes** (Obsidian-compatible)
|
|
```markdown
|
|
[<img width="180" height="60" src="path/to/image.svg"/>](https://example.com)
|
|
```
|
|
- Preserves `width` and `height` attributes
|
|
- Maintains clickable link wrapper
|
|
- Resolves `src` through the resolution pipeline
|
|
|
|
2. **Obsidian wiki-link embed with full path**
|
|
```markdown
|
|
![[06_Boite_a_Outils/6.2_Attachments/image.svg]]
|
|
```
|
|
- Full vault-relative path
|
|
- Resolves relative to vault root
|
|
|
|
3. **Obsidian wiki-link embed with filename only**
|
|
```markdown
|
|
![[image.svg]]
|
|
```
|
|
- Filename only, no path
|
|
- Resolved using attachment index built at startup
|
|
|
|
4. **Standard Markdown image**
|
|
```markdown
|
|

|
|
```
|
|
- Goes through multi-strategy resolution pipeline
|
|
- External URLs (http://, https://) are preserved unchanged
|
|
|
|
### ✅ Attachment Index
|
|
|
|
- **Startup scan**: Asynchronous scan of all vaults for image files
|
|
- **Supported formats**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`, `.bmp`, `.ico`
|
|
- **Index structure**: `{vault_name: {filename_lower: [absolute_path, ...]}}`
|
|
- **Resolution cache**: Results cached per vault + filename for performance
|
|
- **Logging**: Number of attachments indexed per vault logged at startup
|
|
|
|
### ✅ Multi-Strategy Path Resolution
|
|
|
|
Priority order (stops at first successful resolution):
|
|
|
|
| Priority | Strategy | Description |
|
|
|----------|----------|-------------|
|
|
| 1 | Absolute path | If path is absolute and file exists |
|
|
| 2 | Config attachments folder | Resolve relative to `VAULT_N_ATTACHMENTS_PATH` |
|
|
| 3 | Startup index (unique match) | Lookup filename in index; use if only one match |
|
|
| 4 | Same directory | Resolve relative to current markdown file's directory |
|
|
| 5 | Vault root relative | Resolve relative to vault root |
|
|
| 6 | Startup index (closest match) | If multiple matches, pick best path match |
|
|
| 7 | Fallback | Display styled placeholder with tooltip |
|
|
|
|
### ✅ Configuration Schema
|
|
|
|
New environment variables per vault:
|
|
|
|
```bash
|
|
# Required
|
|
VAULT_1_NAME=MyVault
|
|
VAULT_1_PATH=/vaults/MyVault
|
|
|
|
# Optional - Image configuration
|
|
VAULT_1_ATTACHMENTS_PATH=06_Boite_a_Outils/6.2_Attachments # Relative path
|
|
VAULT_1_SCAN_ATTACHMENTS=true # Default: true
|
|
```
|
|
|
|
### ✅ API Endpoints
|
|
|
|
**Serve Image**
|
|
```
|
|
GET /api/image/{vault_name}?path=relative/path/to/image.png
|
|
```
|
|
- Returns image with proper MIME type
|
|
- Supports all common image formats
|
|
- Path traversal protection
|
|
|
|
**Rescan Vault Attachments**
|
|
```
|
|
POST /api/attachments/rescan/{vault_name}
|
|
```
|
|
- Clears cache for the vault
|
|
- Re-scans vault directory for images
|
|
- Returns attachment count
|
|
|
|
**Attachment Statistics**
|
|
```
|
|
GET /api/attachments/stats?vault={vault_name}
|
|
```
|
|
- Returns attachment counts per vault
|
|
- Optional vault filter
|
|
|
|
### ✅ Frontend Styling
|
|
|
|
**Image Rendering**
|
|
- Images displayed with `max-width: 100%` for responsiveness
|
|
- Rounded corners and subtle shadow
|
|
- Hover effect on linked images
|
|
|
|
**Placeholder for Missing Images**
|
|
- Styled error box with dashed border
|
|
- Shows filename in monospace font
|
|
- Tooltip displays full path
|
|
- Red color scheme for visibility
|
|
|
|
---
|
|
|
|
## Testing Guide
|
|
|
|
### Test Case 1: Standard Markdown Image
|
|
|
|
**Markdown:**
|
|
```markdown
|
|

|
|
```
|
|
|
|
**Expected:**
|
|
- Image resolves via multi-strategy resolution
|
|
- Displays with proper styling
|
|
- Shows placeholder if not found
|
|
|
|
### Test Case 2: Wiki-link with Full Path
|
|
|
|
**Markdown:**
|
|
```markdown
|
|
![[Assets/Images/diagram.svg]]
|
|
```
|
|
|
|
**Expected:**
|
|
- Resolves relative to vault root
|
|
- SVG renders inline
|
|
- Maintains aspect ratio
|
|
|
|
### Test Case 3: Wiki-link with Filename Only
|
|
|
|
**Markdown:**
|
|
```markdown
|
|
![[logo.png]]
|
|
```
|
|
|
|
**Expected:**
|
|
- Searches attachment index
|
|
- Resolves to unique match if only one exists
|
|
- Shows placeholder if not found or ambiguous
|
|
|
|
### Test Case 4: HTML Image in Link
|
|
|
|
**Markdown:**
|
|
```markdown
|
|
[<img width="200" height="100" src="banner.jpg"/>](https://example.com)
|
|
```
|
|
|
|
**Expected:**
|
|
- Preserves width and height attributes
|
|
- Image is clickable and links to URL
|
|
- Resolves banner.jpg through resolution pipeline
|
|
|
|
### Test Case 5: External Image URL
|
|
|
|
**Markdown:**
|
|
```markdown
|
|

|
|
```
|
|
|
|
**Expected:**
|
|
- URL preserved unchanged
|
|
- Image loaded from external source
|
|
- No resolution attempted
|
|
|
|
### Test Case 6: Missing Image
|
|
|
|
**Markdown:**
|
|
```markdown
|
|
![[nonexistent.png]]
|
|
```
|
|
|
|
**Expected:**
|
|
- Displays: `[image not found: nonexistent.png]`
|
|
- Styled with red dashed border
|
|
- Tooltip shows full attempted path
|
|
|
|
### Test Case 7: Attachments Path Priority
|
|
|
|
**Setup:**
|
|
```bash
|
|
VAULT_1_ATTACHMENTS_PATH=Attachments
|
|
```
|
|
|
|
**Markdown:**
|
|
```markdown
|
|
![[photo.jpg]]
|
|
```
|
|
|
|
**Expected:**
|
|
- Checks `Attachments/photo.jpg` first (strategy 2)
|
|
- Falls back to index search if not found
|
|
- Logs resolution strategy used
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Images Not Displaying
|
|
|
|
**Symptom:** Images show as placeholders even though files exist
|
|
|
|
**Checks:**
|
|
1. Verify attachment index was built at startup:
|
|
```bash
|
|
docker logs obsigate | grep "indexed.*attachments"
|
|
```
|
|
|
|
2. Check attachment stats:
|
|
```bash
|
|
curl http://localhost:2020/api/attachments/stats
|
|
```
|
|
|
|
3. Verify file permissions (Docker must be able to read images)
|
|
|
|
4. Check if image extension is supported (see `IMAGE_EXTENSIONS` in `attachment_indexer.py`)
|
|
|
|
**Solution:**
|
|
- Rescan attachments: `curl -X POST http://localhost:2020/api/attachments/rescan/VaultName`
|
|
- Check Docker volume mounts in `docker-compose.yml`
|
|
- Verify `VAULT_N_SCAN_ATTACHMENTS` is not set to `false`
|
|
|
|
### Attachment Scan Disabled
|
|
|
|
**Symptom:** Log shows "attachment scanning disabled"
|
|
|
|
**Cause:** `VAULT_N_SCAN_ATTACHMENTS=false` in environment
|
|
|
|
**Solution:**
|
|
- Remove the variable or set to `true`
|
|
- Restart container: `docker-compose restart obsigate`
|
|
|
|
### Wrong Image Resolved (Multiple Matches)
|
|
|
|
**Symptom:** Image with common filename resolves to wrong file
|
|
|
|
**Cause:** Multiple files with same name in different directories
|
|
|
|
**Solution:**
|
|
1. Use full path syntax: `![[folder/subfolder/image.png]]`
|
|
2. Configure `VAULT_N_ATTACHMENTS_PATH` to prioritize specific folder
|
|
3. Rename files to be unique
|
|
|
|
### Performance Issues with Large Vaults
|
|
|
|
**Symptom:** Slow startup or high memory usage
|
|
|
|
**Cause:** Large number of images being indexed
|
|
|
|
**Optimization:**
|
|
1. Disable scanning for vaults without images:
|
|
```bash
|
|
VAULT_N_SCAN_ATTACHMENTS=false
|
|
```
|
|
|
|
2. Use specific attachments folder to reduce scan scope:
|
|
```bash
|
|
VAULT_N_ATTACHMENTS_PATH=Images
|
|
```
|
|
|
|
3. Monitor memory usage:
|
|
```bash
|
|
docker stats obsigate
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Module Structure
|
|
|
|
```
|
|
backend/
|
|
├── attachment_indexer.py # Image scanning and indexing
|
|
├── image_processor.py # Markdown preprocessing
|
|
├── indexer.py # Vault indexing (updated)
|
|
└── main.py # API endpoints (updated)
|
|
```
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
Startup:
|
|
├─ indexer.build_index()
|
|
│ ├─ Scans markdown files
|
|
│ └─ Calls attachment_indexer.build_attachment_index()
|
|
└─ attachment_indexer builds image index per vault
|
|
|
|
Rendering:
|
|
├─ User requests markdown file
|
|
├─ main._render_markdown() called
|
|
│ ├─ image_processor.preprocess_images()
|
|
│ │ ├─ Detects all 4 image syntaxes
|
|
│ │ ├─ Calls resolve_image_path() for each
|
|
│ │ └─ Transforms to /api/image/{vault}?path=...
|
|
│ └─ mistune renders to HTML
|
|
└─ Frontend displays with styled images
|
|
|
|
Image Serving:
|
|
├─ Browser requests /api/image/{vault}?path=...
|
|
├─ main.api_image() validates and resolves path
|
|
├─ Determines MIME type
|
|
└─ Returns image bytes with proper content-type
|
|
```
|
|
|
|
### Resolution Cache
|
|
|
|
- **Key:** `(vault_name, image_src)`
|
|
- **Value:** `Optional[Path]` (resolved absolute path or None)
|
|
- **Invalidation:** On vault rescan
|
|
- **Thread-safe:** Protected by `_attachment_lock`
|
|
|
|
---
|
|
|
|
## Performance Characteristics
|
|
|
|
| Operation | Complexity | Notes |
|
|
|-----------|-----------|-------|
|
|
| Attachment scan | O(n) | n = number of files in vault |
|
|
| Image resolution (cached) | O(1) | Hash table lookup |
|
|
| Image resolution (uncached) | O(k) | k = number of strategies (max 7) |
|
|
| Rescan vault | O(n) | Rebuilds index for one vault |
|
|
|
|
**Memory Usage:**
|
|
- ~100 bytes per indexed image (filename + path)
|
|
- Resolution cache grows with unique image references
|
|
- Cache cleared on rescan
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements for future versions:
|
|
|
|
1. **Lazy loading**: Only index images when first accessed
|
|
2. **Image thumbnails**: Generate and cache thumbnails for large images
|
|
3. **Image metadata**: Extract and display EXIF data
|
|
4. **Batch rescan**: Rescan all vaults with one command
|
|
5. **File watcher**: Auto-rescan on filesystem changes
|
|
6. **Image optimization**: Compress images on-the-fly
|
|
7. **CDN support**: Serve images from external CDN
|
|
|
|
---
|
|
|
|
## Acceptance Criteria Status
|
|
|
|
- [x] All 4 image syntaxes render correctly in markdown preview
|
|
- [x] Startup scan completes without blocking UI (async/background)
|
|
- [x] Images with filename-only wiki-links resolve via index
|
|
- [x] Config `attachmentsPath` used as priority lookup
|
|
- [x] Unresolved images show visible placeholder, not broken icon
|
|
- [x] No regression on standard markdown image syntax `![]()`
|
|
- [x] Rescan command works and updates display without restart
|
|
|
|
---
|
|
|
|
## Version Information
|
|
|
|
**Implementation Date:** 2025
|
|
**ObsiGate Version:** 1.2.0 (pending)
|
|
**Python Version:** 3.11+
|
|
**Dependencies:** No new dependencies required
|
|
|
|
---
|
|
|
|
*For questions or issues, refer to the main README.md or open an issue on the project repository.*
|