diff --git a/README.md b/README.md index 1c81f88..1f7a872 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ A Flask-based webcomic website with server-side rendering using Jinja2 templates - An archive page where readers can browse all your comics - RSS feed so readers can subscribe to updates - Mobile-friendly design that works on phones and tablets -- No database required - just upload images and edit a simple text file +- No database required - just upload images and edit simple YAML files **Perfect for:** - Independent comic artists starting their first webcomic @@ -81,7 +81,7 @@ A Flask-based webcomic website with server-side rendering using Jinja2 templates - Anyone looking for a lightweight, customizable comic platform **How it works:** -You add your comics by uploading image files and adding basic information (title, date, description) to a configuration file. The website handles everything else - displaying comics with navigation, creating an archive, generating an RSS feed, and making your comics shareable on social media. +You add your comics by uploading image files and creating individual YAML files with basic information (title, date, description). The website handles everything else - displaying comics with navigation, creating an archive, generating an RSS feed, and making your comics shareable on social media. No coding knowledge required for basic use - just follow the instructions below to add comics and customize your site's appearance. @@ -91,7 +91,7 @@ No coding knowledge required for basic use - just follow the instructions below **Sunday Comics:** - Server-side application (Flask/Python) that runs on a web server -- Comics stored in a Python file - edit text to add comics +- Comics stored as individual YAML files - easy version control and management - Includes an RSS feed generator and helper scripts - API endpoints for programmatic access - Markdown support for rich-formatted content @@ -100,7 +100,7 @@ No coding knowledge required for basic use - just follow the instructions below **Rarebit:** - Purely static HTML/CSS/JavaScript files -- Comics are inferred from static images upload - edit a JS to customize +- Comics are inferred from static images upload - edit a JS file to customize - Can be hosted for free on GitHub Pages, Neocities, etc. - No server or programming language required - Simpler deployment - just upload files @@ -233,13 +233,11 @@ To test keyboard navigation on your site: When adding comics to your site, follow these guidelines to maintain accessibility: 1. **Always provide alt text** - ```python - { - 'number': 1, - 'filename': 'comic-001.png', - 'alt_text': 'A descriptive summary of what happens in the comic', # Required! - # ... - } + ```yaml + number: 1 + filename: comic-001.png + alt_text: A descriptive summary of what happens in the comic # Required! + date: '2025-01-01' ``` 2. **Write meaningful alt text** @@ -632,16 +630,27 @@ Resources: ``` sunday/ ├── app.py # Main Flask application -├── comics_data.py # Comic data and configuration +├── comics_data.py # Global configuration (not comic data) +├── data_loader.py # YAML comic loader with caching ├── requirements.txt # Python dependencies ├── Dockerfile # Production Docker image ├── docker-compose.yml # Docker Compose configuration ├── .dockerignore # Docker build exclusions +├── data/ # Comic data directory +│ └── comics/ # Individual comic YAML files +│ ├── 001.yaml # Comic #1 +│ ├── 002.yaml # Comic #2 +│ ├── TEMPLATE.yaml # Template for new comics +│ └── .comics_cache.pkl # Auto-generated cache file ├── scripts/ # Utility scripts -│ ├── add_comic.py # Script to add new comic entries -│ └── generate_rss.py # Script to generate RSS feed +│ ├── add_comic.py # Create new comic YAML files +│ ├── generate_rss.py # Generate RSS feed +│ ├── generate_sitemap.py # Generate sitemap.xml +│ ├── rebuild_cache.py # Force rebuild comics cache +│ └── publish_comic.py # Rebuild cache + RSS + sitemap ├── content/ # Markdown content │ ├── about.md # About page content +│ ├── terms.md # Terms of Service │ └── author_notes/ # Author notes for comics (by date) ├── templates/ # Jinja2 templates │ ├── base.html # Base template with navigation @@ -659,7 +668,8 @@ sunday/ │ ├── comics/ # Comic images │ ├── thumbs/ # Thumbnail images for archive │ └── icons/ # Navigation and social icons (optional) - └── feed.rss # RSS feed (generated) + ├── feed.rss # RSS feed (generated) + └── sitemap.xml # Sitemap (generated) ``` ## Setup @@ -683,6 +693,7 @@ The app can be configured via environment variables: - `SECRET_KEY` - Flask secret key (defaults to 'your-secret-key') - `PORT` - Port to run on (defaults to 3000) - `DEBUG` - Enable debug mode (defaults to False) +- `DISABLE_COMIC_CACHE` - Set to 'true' to disable comic caching (useful for debugging) **Development:** ```bash @@ -697,9 +708,15 @@ export PORT=3000 python app.py ``` +**Disable caching (debugging):** +```bash +export DISABLE_COMIC_CACHE=true +python app.py +``` + ## Configuration -The `comics_data.py` file contains both comic data and global configuration options: +The `comics_data.py` file contains global configuration options for your comic site. Comic data itself is stored in individual YAML files in the `data/comics/` directory. ### Global Settings @@ -716,10 +733,14 @@ FOOTER_IMAGE = None # Optional footer image path BANNER_IMAGE = 'banner.jpg' # Shareable banner for "Link to Me" section COMPACT_FOOTER = False # Display footer in compact mode ARCHIVE_FULL_WIDTH = True # Full-width archive with 4 columns +SECTIONS_ENABLED = True # Enable section headers on archive page USE_COMIC_NAV_ICONS = True # Use icons for comic navigation buttons USE_HEADER_NAV_ICONS = True # Show icons in main header navigation USE_FOOTER_SOCIAL_ICONS = True # Use icons for social links +USE_SHARE_ICONS = True # Use icons in share buttons (permalink/embed) NEWSLETTER_ENABLED = False # Show newsletter section in footer +EMBED_ENABLED = True # Enable comic embed functionality +PERMALINK_ENABLED = True # Enable permalink copy button SOCIAL_INSTAGRAM = None # Instagram URL (or None) SOCIAL_YOUTUBE = None # YouTube URL (or None) SOCIAL_EMAIL = None # Email mailto link (or None) @@ -728,78 +749,107 @@ API_SPEC_LINK = None # API documentation link (or None) ## Adding Comics -Comics are stored in the `COMICS` list in `comics_data.py`. Each comic entry: +Comics are stored as individual YAML files in the `data/comics/` directory. Each comic file contains: -```python -{ - 'number': 1, # Comic number (required, sequential) - 'filename': 'comic-001.png', # Image filename (required) OR list for multi-image - 'mobile_filename': 'comic-001-mobile.png', # Optional mobile version (single-image only) - 'date': '2025-01-01', # Publication date (required) - 'alt_text': 'Alt text for comic', # Accessibility text (required) OR list for multi-image - 'title': 'Comic Title', # Title (optional, shows #X if absent) - 'author_note': 'Optional note', # Author note (optional, plain text) - 'author_note_md': '2025-01-01.md', # Optional markdown file for author note - 'full_width': True, # Optional: override FULL_WIDTH_DEFAULT - 'plain': True # Optional: override PLAIN_DEFAULT -} +```yaml +number: 1 # Comic number (required, sequential) +filename: comic-001.png # Image filename (required) OR list for multi-image +date: '2025-01-01' # Publication date (required) +alt_text: Alt text for comic # Accessibility text (required) OR list for multi-image +title: Comic Title # Title (optional, shows #X if absent) +author_note: Optional note # Author note (optional, plain text) +author_note_md: 2025-01-01.md # Optional markdown file for author note +full_width: true # Optional: override FULL_WIDTH_DEFAULT +plain: true # Optional: override PLAIN_DEFAULT ``` **For multi-image comics (webtoon style):** -```python -{ - 'number': 2, - 'filename': ['page1.png', 'page2.png', 'page3.png'], # List of images - 'alt_text': ['Panel 1 description', 'Panel 2', 'Panel 3'], # Individual alt texts - 'date': '2025-01-08', - 'full_width': True # Recommended for webtoons -} +```yaml +number: 2 +filename: + - page1.png + - page2.png + - page3.png +alt_text: + - Panel 1 description + - Panel 2 description + - Panel 3 description +date: '2025-01-08' +full_width: true # Recommended for webtoons ``` ### Adding a New Comic **Option 1: Use the script (recommended)** ```bash -# Add comic entry only +# Add comic YAML file with defaults python scripts/add_comic.py -# Add comic entry AND create markdown file for author notes +# Add comic YAML file AND create markdown file for author notes python scripts/add_comic.py -m ``` -This will automatically add a new entry with defaults. The `-m` flag creates a markdown file in `content/author_notes/{date}.md` with a template and adds the `author_note_md` field to the comic entry. Then edit `comics_data.py` to customize. +This will create a new YAML file in `data/comics/` with the next comic number and reasonable defaults. The `-m` flag also creates a markdown file in `content/author_notes/{date}.md` with a template. Then: +1. Edit the generated YAML file to customize title, alt_text, author_note, etc. +2. Upload your comic image to `static/images/comics/` +3. Optionally create a thumbnail in `static/images/thumbs/` with the same filename **Option 2: Manual** -1. Save your comic image in `static/images/comics/` (e.g., `comic-001.png`) -2. Optionally, create a thumbnail in `static/images/thumbs/` with the same filename -3. Add the comic entry to the `COMICS` list in `comics_data.py` +1. Copy `data/comics/TEMPLATE.yaml` and rename it (e.g., `003.yaml`) +2. Edit the YAML file to set comic properties +3. Save your comic image in `static/images/comics/` +4. Optionally, create a thumbnail in `static/images/thumbs/` with the same filename -### Generating RSS Feed +### Publishing Comics -After adding comics, regenerate the RSS feed: +After adding or updating comics, use the publish script to update all generated files: ```bash -python scripts/generate_rss.py +python scripts/publish_comic.py ``` -This creates/updates `static/feed.rss` +This convenience script rebuilds the cache and regenerates both RSS feed and sitemap in one command. + +### Comic Caching System + +Sunday Comics uses an automatic caching system to speed up comic loading: + +**How it works:** +- **First load**: Parses all YAML files, saves to `data/comics/.comics_cache.pkl` +- **Subsequent loads**: Reads from cache (~100x faster) +- **Auto-invalidation**: Cache rebuilds automatically when any YAML file is modified + +**Performance (1000 comics):** +- Initial load: ~2-3 seconds (builds cache) +- Subsequent loads: ~0.01 seconds (uses cache) +- Scripts share the same cache file on disk + +**Manual cache management:** +```bash +# Force rebuild the cache (normally not needed) +python scripts/rebuild_cache.py + +# Disable caching (for debugging) +export DISABLE_COMIC_CACHE=true +python app.py +``` + +The cache file is automatically excluded from git (listed in `.gitignore`). ### Markdown Support **Author Notes:** -Add the `author_note_md` field to your comic entry in `comics_data.py` to use markdown-formatted author notes. The field can be: -- Just a filename (e.g., `"2025-01-01.md"`) - looked up in `content/author_notes/` -- A path relative to `content/` (e.g., `"special/intro.md"`) +Add the `author_note_md` field to your comic YAML file to use markdown-formatted author notes. The field can be: +- Just a filename (e.g., `2025-01-01.md`) - looked up in `content/author_notes/` +- A path relative to `content/` (e.g., `special/intro.md`) Markdown author notes take precedence over the plain text `author_note` field and render as HTML. Example: -```python -# In comics_data.py -{ - 'number': 1, - 'filename': 'comic-001.png', - 'date': '2025-01-01', - 'alt_text': 'First comic', - 'author_note_md': '2025-01-01.md' # References content/author_notes/2025-01-01.md -} +```yaml +# In data/comics/001.yaml +number: 1 +filename: comic-001.png +date: '2025-01-01' +alt_text: First comic +author_note_md: 2025-01-01.md # References content/author_notes/2025-01-01.md ``` ```bash @@ -823,31 +873,34 @@ Sunday Comics supports vertical scrolling comics with multiple images stacked se - No click-through navigation on multi-image comics (use navigation buttons instead) **Basic Example:** -```python -# In comics_data.py -{ - 'number': 4, - 'title': 'Webtoon Episode 1', - 'filename': ['page1.jpg', 'page2.jpg', 'page3.jpg'], # List of images - 'alt_text': 'A three-part vertical story', # Single alt text for all images - 'date': '2025-01-22', -} +```yaml +# In data/comics/004.yaml +number: 4 +title: Webtoon Episode 1 +filename: + - page1.jpg + - page2.jpg + - page3.jpg +alt_text: A three-part vertical story # Single alt text for all images +date: '2025-01-22' ``` **Individual Alt Text (Recommended for Accessibility):** -```python -{ - 'number': 5, - 'title': 'Long Scroll Episode', - 'filename': ['scene1.png', 'scene2.png', 'scene3.png', 'scene4.png'], - 'alt_text': [ - 'Opening scene showing the city at dawn', - 'Character walking through the marketplace', - 'Close-up of the mysterious artifact', - 'Dramatic reveal of the antagonist' - ], # List must match number of images (or use single string for all) - 'date': '2025-01-29', -} +```yaml +# In data/comics/005.yaml +number: 5 +title: Long Scroll Episode +filename: + - scene1.png + - scene2.png + - scene3.png + - scene4.png +alt_text: + - Opening scene showing the city at dawn + - Character walking through the marketplace + - Close-up of the mysterious artifact + - Dramatic reveal of the antagonist +date: '2025-01-29' ``` **Important:** If you provide `alt_text` as a list, it should match the number of images in `filename`. If the counts don't match, you'll see a warning in the logs. To use the same alt text for all images, just provide a single string instead of a list. @@ -866,29 +919,26 @@ Sunday Comics supports vertical scrolling comics with multiple images stacked se 4. **Image optimization** - Compress images appropriately since readers will load multiple images per comic **Example with all options:** -```python -{ - 'number': 6, - 'title': 'Chapter 2: The Journey Begins', - 'filename': [ - 'ch2-001.png', - 'ch2-002.png', - 'ch2-003.png', - 'ch2-004.png', - 'ch2-005.png' - ], - 'alt_text': [ - 'Panel 1: Hero packs their bag at sunrise', - 'Panel 2: Saying goodbye to the village elder', - 'Panel 3: Walking along the forest path', - 'Panel 4: Encountering a mysterious stranger', - 'Panel 5: Accepting a map to the ancient ruins' - ], - 'date': '2025-02-05', - 'author_note': 'This was so much fun to draw! The journey arc begins.', - 'full_width': True, # Recommended for webtoon-style comics - 'section': 'Chapter 2', # Optional: mark the start of a new chapter -} +```yaml +# In data/comics/006.yaml +number: 6 +title: 'Chapter 2: The Journey Begins' +filename: + - ch2-001.png + - ch2-002.png + - ch2-003.png + - ch2-004.png + - ch2-005.png +alt_text: + - 'Panel 1: Hero packs their bag at sunrise' + - 'Panel 2: Saying goodbye to the village elder' + - 'Panel 3: Walking along the forest path' + - 'Panel 4: Encountering a mysterious stranger' + - 'Panel 5: Accepting a map to the ancient ruins' +date: '2025-02-05' +author_note: This was so much fun to draw! The journey arc begins. +full_width: true # Recommended for webtoon-style comics +section: Chapter 2 # Optional: mark the start of a new chapter ``` **Technical Details:**