whtwnd-to-leaflet#
A browser-based tool for converting WhiteWind blog entries into the Leaflet publication format. Migrate content, metadata, and themes from WhiteWind to Leaflet on the AT Protocol with automatic fetching and publishing capabilities.
⚠️ Now with Auto-Publishing! - Log in to automatically fetch your WhiteWind entries and publish directly to Leaflet.
⚠️ IMPORTANT: Untested Implementation#
The auto-publishing feature now uses com.atproto.repo.applyWrites for batch operations, which is currently UNTESTED.
- The implementation has been refactored to use batch writes (up to 10 records per API call)
- This should dramatically reduce publishing time for large blogs
- However, this has not been tested in production
- Please test with a small number of posts first (1-5 entries)
- Consider using Manual Mode and pdsls.dev for critical migrations
- Report any issues on the GitHub repository
If you need the stable version using individual createRecord calls, check out the previous commit.
✨ Features#
Core Conversion#
- Remark-based Markdown Parsing - Uses the robust
unified/remarkecosystem for accurate parsing - Proper ATProto Blob Handling - Converts blob URLs to correct Leaflet image block format with
{ $type: 'blob', ref: { $link: 'cid' } } - Full Lexicon Compliance - Strictly follows Leaflet lexicons for all block types
- Image Blocks - Standalone images converted to
pub.leaflet.blocks.imageblocks with proper aspectRatio - List Support - Proper
pub.leaflet.blocks.unorderedListwith nested list items - Rich Text Facets - Preserves bold, italic, code, and links with proper UTF-8 byte offsets
New Auto-Publishing Features#
- AT Protocol Authentication - Secure login with app passwords
- Auto-Fetch WhiteWind Entries - Automatically retrieves all your WhiteWind blog posts
- Direct Publishing - Publish converted entries directly to Leaflet without manual file uploads
- Progress Tracking - Real-time progress bar showing publication status
- Manual Mode - Still supports manual JSON paste for offline conversion
Other Features#
- Add to Existing Publications - Can add converted posts to an existing Leaflet publication
- Metadata Cleanup - Only preserves Leaflet-compatible metadata, strips WhiteWind-specific fields
- ZIP Export - Download all converted files as a ZIP archive
How It Works#
The app is a single-page web tool (built with SvelteKit + TypeScript + Tailwind on Svelte 5) that runs entirely in the browser. It produces Leaflet-compatible publication record plus one or more document records from your WhiteWind entries.
Two Modes of Operation#
Auto Mode (Recommended) 🚀#
- Login - Authenticate with your AT Protocol handle and app password
- Fetch - Automatically retrieve all your WhiteWind entries
- Configure - Set up your publication details and theme
- Convert - Transform entries to Leaflet format
- Publish - Directly publish to your PDS with one click
Manual Mode 📝#
- Publication Setup - Choose to create new or add to existing publication
- Theme Configuration - Customize colors (if creating new publication)
- Entry Conversion - Paste WhiteWind JSON and convert
- Download - Export as ZIP or copy JSON
Usage#
Auto Mode (With Login)#
-
Log in with your AT Protocol credentials:
- Enter your handle (e.g.,
alice.bsky.social) or DID - Use an app password (never your main password!)
- Create app passwords in your AT Protocol client settings
- Enter your handle (e.g.,
-
Select Auto Mode and click Fetch My WhiteWind Entries
- Your entries will be automatically retrieved from your PDS
-
Configure your publication:
- Create new or add to existing publication
- Set publication name, description, and preferences
- Choose theme colors
-
Convert your entries to Leaflet format
-
Publish directly to AT Protocol with one click
- Progress bar shows real-time publishing status
- All records created automatically
Manual Mode (Without Login)#
- Choose whether to create a new publication or add to an existing one
- Fill out the required fields (Publication Setup or Existing Rkey)
- If creating new, configure the theme
- Paste your WhiteWind JSON entries. You can fetch them from your PDS with:
https://[pds domain]/xrpc/com.atproto.repo.listRecords?repo=[did]&collection=com.whtwnd.blog.entry
- Enter your Author DID and PDS URL
- Click Convert to Leaflet
- Download the ZIP export or manually upload using pdsls.dev
Importing into a PDS (Manual Mode)#
When manually importing the converted records:
For new publications:
- Use pdsls.dev and the ZIP export
- Upload
00.jsonfirst (the publication record) with your chosen rkey - Then upload each document record (
1.json,2.json, etc.) individually - Don't change the autogenerated rkeys for documents
For existing publications:
- The ZIP will only contain document records (
0.json,1.json, etc.) - Upload each document individually to your existing publication
- Don't change the autogenerated rkeys
Technical Details#
Markdown Parsing#
The converter uses the unified/remark ecosystem for robust Markdown parsing:
- unified - Core parser framework
- remark-parse - Markdown parser
- remark-gfm - GitHub Flavored Markdown support
- mdast-util-to-string - AST utilities
This provides:
- Better edge case handling
- Support for GFM features
- Proper nested element parsing
- Extensible with plugins
Leaflet Block Support#
Fully compliant with Leaflet lexicons:
- ✅ pub.leaflet.blocks.text - Paragraphs with rich text facets
- ✅ pub.leaflet.blocks.header - Headings (levels 1-6) with facets
- ✅ pub.leaflet.blocks.image - Images with required aspectRatio
- ✅ pub.leaflet.blocks.code - Code blocks with language
- ✅ pub.leaflet.blocks.blockquote - Block quotes
- ✅ pub.leaflet.blocks.horizontalRule - Horizontal rules
- ✅ pub.leaflet.blocks.unorderedList - Lists with nested items
Rich Text Facets#
Inline formatting preserved with proper UTF-8 byte offsets:
- Bold (
**text**) →pub.leaflet.richtext.facet#bold - Italic (
*text*) →pub.leaflet.richtext.facet#italic - Code (
`text`) →pub.leaflet.richtext.facet#code - Links (
[text](url)) →pub.leaflet.richtext.facet#link
Metadata Handling#
The converter only preserves Leaflet-compatible metadata:
Preserved:
title- Post titledescription- From WhiteWind's subtitle fieldpublishedAt- Original creation timestamptheme- Visual theme
Discarded (WhiteWind-specific):
rkey- WhiteWind record key (replaced with new TID)cid- Content identifiervalue- Wrapper objecturi- Original WhiteWind URIvisibility- Not in Leaflet document schema- Any other fields not in Leaflet's lexicon
Blob URL Conversion#
Handles two types of blob references:
- XRPC getBlob URLs:
xrpc/com.atproto.sync.getBlob?did=X&cid=Y - Direct CID references: URLs containing
bafk...orbafyb...
For standalone images, creates proper image blocks:
{
"block": {
"$type": "pub.leaflet.blocks.image",
"image": {
"$type": "blob",
"ref": {
"$link": "bafkreiabc123..."
}
},
"aspectRatio": {
"width": 1920,
"height": 1080
},
"alt": "Image description"
}
}
For inline images and blob links, converts to AT-URI format: at://did/com.atproto.blob/cid
Image Dimensions#
The converter attempts to fetch actual image dimensions for proper aspectRatio:
- Checks blob metadata if available
- Fetches and measures actual image if PDS URL provided
- Falls back to 512x512 if dimensions unavailable
This ensures compliance with Leaflet's required aspectRatio field.
AT Protocol Authentication & Publishing#
Uses @atproto/api for secure authentication:
- Resolves handles/DIDs using Slingshot identity resolver
- Supports app passwords (2FA-compatible)
- Session management with localStorage
- Automatic PDS URL resolution
Publishing:
- Uses
com.atproto.repo.applyWritesfor efficient batch operations - Processes up to 10 records per API call (maximum allowed)
- Dramatically reduces API calls compared to individual
createRecordoperations - Note: This batch publishing implementation is currently untested
Local Development#
Install dependencies:
npm install
Run development server:
npm run dev
Open the URL from Vite (usually http://localhost:5173).
Build & preview:
npm run build
npm run preview
Files of Interest#
src/routes/+page.svelte- Main UI with auth and publishingsrc/lib/convert.ts- Core conversion logic with remark integrationsrc/lib/auth.ts- AT Protocol authentication and publishingsrc/lib/styles.css,src/lib/variables.css- Styles and theme variablespackage.json- Dependencies including unified/remark and @atproto/api
Migration Tips#
For migrating ~86 posts from WhiteWind:
Using Auto Mode (Recommended)#
- Log in with your AT Protocol credentials
- Fetch all entries automatically
- Configure publication settings
- Convert and publish in one go
- Monitor progress with the real-time progress bar
Using Manual Mode#
- Start with existing publication mode - Use your existing Leaflet publication rkey
- Process in batches - Convert and upload 10-15 posts at a time
- Review after conversion - Check the first few converted posts for formatting issues
- Iterate if needed - The remark parser handles most cases, but custom formatting may need adjustment
- Preserve timestamps - Original publish dates are maintained automatically
Common Issues#
Image dimensions missing:
- Make sure to provide your PDS URL
- The converter will attempt to fetch actual dimensions
- Falls back to 512x512 if unavailable
List formatting:
- WhiteWind lists are converted to proper Leaflet unorderedList blocks
- Nested lists are supported with proper structure
Rich text facets:
- All inline formatting (bold, italic, code, links) is preserved
- Byte offsets calculated automatically for UTF-8 compliance
Security Notes#
- Never use your main password - Always create an app password
- App passwords support 2FA and can be revoked individually
- Sessions stored locally - Clear browser data to logout completely
- No server-side storage - All processing happens in your browser
License#
Licensed under GPL 3.0. See /LICENSE for details.
Credits#
Project: ewanc26/whtwnd-to-leaflet
Built with 🍃 by Ewan
Not affiliated with WhiteWind or Leaflet.
Changelog#
v2.0.0 - Auto-Publishing Update#
New Features:
- AT Protocol authentication with app passwords
- Auto-fetch WhiteWind entries from your PDS
- Direct publishing to Leaflet with progress tracking
- Two modes: Auto (with login) and Manual (without login)
Improvements:
- Full Leaflet lexicon compliance
- Proper
pub.leaflet.blocks.unorderedListsupport - Improved image block handling with aspectRatio
- Better error messages and user feedback
- Real-time publishing progress
Bug Fixes:
- Removed invalid
visibilityfield from documents - Fixed list conversion to use proper Leaflet structure
- Improved blob URL detection and conversion
- Better handling of missing image dimensions
v1.0.0 - Initial Release#
- Remark-based Markdown parsing
- Proper ATProto blob handling
- Image and text block conversion
- ZIP export functionality
- Add to existing publications