Project: Pixelfed Photo Viewer
What
Test app that fetches and displays photos from a Pixelfed account using their API.
Why
Explore Pixelfed API integration and understand how to pull user media for potential future projects.
Future projects will avoid a javascript on the client because:
- No javascript needed for the UI, the modal effect can be done on desktop with CSS and mobile night not need modals (how would permalinks work? page refresh to unique URLs?)
- If API calls are needed (e.g. for images from an external service like Pixelfed) then they would be made at build time with the static site generator (Astro now, possibly Eleventy in the future)
- Won’t need API calls for all galleries because sometimes the assets will be from my own AWS bucket, just like with the blog posts.be
Lessons Learned
See log.md for development notes and lessons learned.
Scope
In Scope
- Fetch photos from
http://pixelfed.social/@alabut
- Display photos in a simple grid/list view
- Basic error handling for API failures
- User authentication using personal access token (simple one-time setup)
Out of Scope
- Photo uploads or modifications
- Caching/offline support (for now)
- Advanced filtering or search
- Creating draft blog posts on website from photos
- Syndication to other platforms (Instagram, Mastodon, etc.)
- Downloading/saving photos to AWS S3/CloudFront (photos remain hosted on Pixelfed)
Success Criteria
(A running task list of features)
Notes
- Pixelfed is ActivityPub/Mastodon compatible, so Mastodon API docs may apply
- Keeping this framework-agnostic since migrating to Eleventy soon
- Simple HTML file approach for easy throwaway testing
Future Vision
(This test app is a stepping stone toward:)
- Pixelfed as central photo hub (upload from phone)
- Pull photos into website as draft blog posts
- Download photos from Pixelfed API and store in S3/CloudFront for reliable hosting (website serves from S3, not Pixelfed)
- Eventually syndicate to Instagram/other Mastodon instances
- Goal: Write once to Pixelfed, distribute everywhere
- Goal: Take advantage of Pixelfed’s slick mobile app as a way to update my website while on the go
Why this test matters: Understanding the Pixelfed API response structure
and photo/metadata extraction will be crucial for the blog post draft system.
Technical Approach
- Single HTML file with embedded JavaScript (vanilla JS, no frameworks)
- Use browser’s
fetch() API to call Pixelfed API endpoints with authentication
- Authentication: Personal Access Token (stored in code for this test - will need server-side solution for production)
- Display results directly in the HTML page
- Photos remain hosted on Pixelfed servers (we fetch metadata/URLs, then reference images directly)
Key Technical Details
- CSS Grid Layout: Modal uses
display: inline-grid with grid-template-columns: min-content to constrain width to image width
- Dynamic Positioning: Navigation arrows use JavaScript to calculate positions based on actual modal width (prevents overlap with images)
- Responsive Scrolling: Overlay handles scrolling on both mobile and desktop, with fixed-position controls
- Caption Truncation: JavaScript-based line measurement and truncation for consistent behavior across browsers
- Touch Event Handling: Desktop swipe gestures use touch events with proper passive/active event handling
- Mobile Detection: Custom
isMobile() function based on screen width and touch capabilities
Implementation Details
Features Implemented
Core Features
- Photo Grid Display: Responsive grid layout showing photos with captions and dates
- Lightbox Modal: Click any photo to view larger version with full caption and metadata
- URL Hash Support: Direct links to specific photos (e.g.,
#photo-123) for sharing
- Share Link Button: Copy link to current photo to clipboard (desktop modal)
- Pagination: “Load More” button to fetch older photos
- Multi-Photo Posts: Handles posts with multiple photos, showing position indicators (1 of 3, 2 of 3, etc.)
- UTF-8 Encoding Fix: Properly decodes HTML entities and fixes mis-encoded characters (apostrophes, quotes, etc.)
Desktop-Specific Features
- Keyboard Navigation: Arrow keys (←/→) to navigate between photos, Escape to close modal
- Visual Navigation Arrows: Left/right arrow buttons that dynamically position based on modal width (never overlap images)
- Scroll from Anywhere: Can scroll long captions from anywhere on the page, not just the photo area
- Fixed Control Buttons: Close and share buttons stay fixed above the photo while content scrolls
- Caption Truncation: Long captions truncated to 5 lines with inline ”[+] More” link (opens modal)
- Grid Layout: Modal uses CSS Grid to constrain width to image width, preventing caption overflow
Mobile-Specific Features
- Uncropped Photos: Photos display in normal aspect ratio in main view (no need to tap to see full image)
- Inline Caption Expansion: Captions truncated to 3 lines with ”[+] More” link that expands in place
- Inline Permalink: Small permalink button next to date in main view for quick link copying
- Modal for Direct Links: Modal primarily shown to users arriving via permalink (use X to exit to filmstrip)
- No Modal Navigation: Modal doesn’t include navigation arrows or swipe gestures (vertical scrolling is primary)
- Tap to Open: Tapping photo or date in main view opens modal for larger view
Mobile vs Desktop Behavior
The app provides optimized experiences for different screen sizes:
Mobile (< 769px):
- Photos display uncropped in main view
- Captions truncated to 3 lines with inline expansion
- Inline permalink button for quick link copying
- Modal primarily for direct link visitors
- Vertical scrolling through filmstrip is primary navigation
- No navigation arrows or swipe gestures in modal
Desktop (≥ 769px):
- Photos in grid with hover effects
- Keyboard and visual arrow navigation
- Modal scrolls from anywhere on page
- Long captions scroll while controls stay fixed
- Caption truncation with modal-opening link
- Swipe gestures supported on touch-enabled desktops
Error Handling
The app includes comprehensive error handling:
-
API Error Handling:
- Catches network errors, HTTP errors (404, 500, etc.), and JSON parsing errors
- Displays user-friendly error messages in a visible error box
- Logs essential error information to browser console for debugging
-
How to Test Error Handling:
- Invalid Token: Temporarily change the
ACCESS_TOKEN to an invalid value - you’ll see an authentication error
- Network Error: Disconnect from internet and reload page - you’ll see a network/connection error
- Invalid Endpoint: Temporarily change
INSTANCE_URL to an invalid domain - you’ll see a fetch error
- Browser Console: Open DevTools (F12) → Console tab to see error logs
-
Error Display:
- Errors appear in a red-bordered box at the top of the page
- Error messages include helpful context (e.g., “Failed to load photos: [error message]”)
- Loading indicator is hidden when errors occur
Open Questions
What’s the exact API endpoint structure for Pixelfed? (RESOLVED: Uses Mastodon-compatible API endpoints)
Does it require authentication or is public feed accessible? (RESOLVED: Requires authentication)
What format are photos returned in (URLs, base64, etc.)? (RESOLVED: URLs in media_attachments array)
- Rate limits or request restrictions? (Not tested yet)
Will CORS allow direct browser calls, or do we need a proxy? (RESOLVED: Direct authenticated calls work without CORS proxy)
Account Info
- Profile: http://pixelfed.social/@alabut
- Personal access token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyMzAxMiIsImp0aSI6IjM3NjM1ZmMyZTVlNWU2ZGZkZGNjYzA1ZTFlYTlmMTc5Mzk1OTY0ODhiMmE5OGQzZjljZjRhOTI1NjFjYjY0ZGVkOTEwYjZiNzNjMTliNjBmIiwiaWF0IjoxNzYyMDg4MzYyLjY2MzIwNiwibmJmIjoxNzYyMDg4MzYyLjY2MzIwOCwiZXhwIjoxNzkzNjI0MzYyLjY2MTc1Nywic3ViIjoiNzc0MjkiLCJzY29wZXMiOlsicmVhZCJdfQ.O4gYYVbAs70Znz-mkCkvAHHLKryP2wCCtqt2FW5vCKUZqLCPM-ufHbabj78HU3PSKUNeYu-QRbZodeXQSvRMWWgdnM0MXzrmpxJGYyW3jXO33UZrKGEX8MV5XFfK5CntzcvZ11E7UPNz2P88WkvLAfhdMj4RhC9P3R4zMVrAzcGGb4dsOMpYzjE_AxWFC4cLrjz6KdkS8Ukn6vzHsvLrDqvjdxrw9IJbo_eUjl_D6WD3sy_k9I5sxCVVI7XC_ylE_ExNJBV0Ep6Y_fUOIq2IzKS6aN6SU-iY6IvVJVoEsoV46dCYBLnKEwYxflQKQAX6QGZv54ayNExd_THEO_CXrULP0aKFaTXFK6eQGcgj9KvtD8xb7PtVc6okUmvvfTpB2DeQJn_m0IDkdAGIF5oqqS6Q_XSLmDrxiNdDmU4apKtIJGerBQwJWeINaCsGJVmcCsHekdCSc23EKg6nHzY29sYLW4Io4Ug2hKlNrvKvB9xvlN91HnpuyYWeUCNN1CYpDUomfjW_2lDpThyjRr4GoN2notMM3m7X7g9l8sHq49UqN-U_vC24shn58fGMFMLlfx5FAEmJihsDkaTFxR2Lijqyq8ggqXiKJTSmI7hsoK4vtvddK0jD2-aGIL-aqgoDYrR_bHsw3JE4UtxA9gKf37zeWfsUCWTM5S43P07VqIg