371 lines
12 KiB
Markdown
371 lines
12 KiB
Markdown
# Sunday Comics - Webcomic Flask App
|
|
|
|
A Flask-based webcomic website with server-side rendering using Jinja2 templates.
|
|
|
|
## What is This?
|
|
|
|
**Sunday Comics** is a simple, ready-to-use website for publishing your webcomic online. If you're an artist or comic creator who wants to share your work on the web without dealing with complex platforms or databases, this is for you.
|
|
|
|
**What you get:**
|
|
- A clean, professional website to display your comics
|
|
- Easy navigation for readers (first, previous, next, latest buttons)
|
|
- 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
|
|
|
|
**Perfect for:**
|
|
- Independent comic artists starting their first webcomic
|
|
- Artists who want full control over their comic's presentation
|
|
- Creators who prefer simple file-based management over databases
|
|
- 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.
|
|
|
|
No coding knowledge required for basic use - just follow the instructions below to add comics and customize your site's appearance.
|
|
|
|
## Features
|
|
|
|
- Comic viewer with navigation (First, Previous, Next, Latest)
|
|
- Client-side navigation using JSON API (no page reloads)
|
|
- Archive page with thumbnail grid
|
|
- RSS feed support
|
|
- Markdown support for author notes and about page
|
|
- Optional icon-based navigation (comic navigation, header, and social links)
|
|
- Configurable logo and header/footer images
|
|
- Mobile-responsive with optional mobile-specific comic images
|
|
- Full-width and plain (headerless) display modes
|
|
- JSON API for programmatic access
|
|
- Open Graph and Twitter Card metadata for social sharing
|
|
- Server-side rendering with Jinja2
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
sunday/
|
|
├── app.py # Main Flask application
|
|
├── comics_data.py # Comic data and configuration
|
|
├── requirements.txt # Python dependencies
|
|
├── Dockerfile # Production Docker image
|
|
├── docker-compose.yml # Docker Compose configuration
|
|
├── .dockerignore # Docker build exclusions
|
|
├── scripts/ # Utility scripts
|
|
│ ├── add_comic.py # Script to add new comic entries
|
|
│ └── generate_rss.py # Script to generate RSS feed
|
|
├── content/ # Markdown content
|
|
│ ├── about.md # About page content
|
|
│ └── author_notes/ # Author notes for comics (by date)
|
|
├── templates/ # Jinja2 templates
|
|
│ ├── base.html # Base template with navigation
|
|
│ ├── index.html # Latest comic page
|
|
│ ├── comic.html # Individual comic viewer
|
|
│ ├── archive.html # Archive grid
|
|
│ ├── page.html # Generic markdown page template
|
|
│ └── 404.html # Error page
|
|
└── static/ # Static files
|
|
├── css/
|
|
│ └── style.css # Main stylesheet
|
|
├── js/
|
|
│ └── comic-nav.js # Client-side navigation
|
|
├── images/ # Image directory
|
|
│ ├── comics/ # Comic images
|
|
│ ├── thumbs/ # Thumbnail images for archive
|
|
│ └── icons/ # Navigation and social icons (optional)
|
|
└── feed.rss # RSS feed (generated)
|
|
```
|
|
|
|
## Setup
|
|
|
|
1. Install dependencies:
|
|
```bash
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
2. Run the application:
|
|
```bash
|
|
python app.py
|
|
```
|
|
|
|
3. Visit http://127.0.0.1:3000 in your browser
|
|
|
|
## Environment Variables
|
|
|
|
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)
|
|
|
|
**Development:**
|
|
```bash
|
|
export DEBUG=True
|
|
python app.py
|
|
```
|
|
|
|
**Production:**
|
|
```bash
|
|
export SECRET_KEY="your-secure-random-secret-key"
|
|
export PORT=3000
|
|
python app.py
|
|
```
|
|
|
|
## Configuration
|
|
|
|
The `comics_data.py` file contains both comic data and global configuration options:
|
|
|
|
### Global Settings
|
|
|
|
```python
|
|
COMIC_NAME = 'Sunday Comics' # Your comic/website name
|
|
SITE_URL = 'http://localhost:3000' # Your domain (update for production)
|
|
FULL_WIDTH_DEFAULT = False # Make all comics full-width by default
|
|
PLAIN_DEFAULT = False # Hide header/remove borders by default
|
|
LOGO_IMAGE = 'logo.png' # Path to logo (relative to static/images/)
|
|
LOGO_MODE = 'beside' # 'beside' or 'replace' title with logo
|
|
HEADER_IMAGE = None # Optional header image path
|
|
FOOTER_IMAGE = None # Optional footer image path
|
|
COMPACT_FOOTER = False # Display footer in compact mode
|
|
ARCHIVE_FULL_WIDTH = True # Full-width archive with 4 columns
|
|
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
|
|
SOCIAL_INSTAGRAM = None # Instagram URL (or None)
|
|
SOCIAL_YOUTUBE = None # YouTube URL (or None)
|
|
SOCIAL_EMAIL = None # Email mailto link (or None)
|
|
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:
|
|
|
|
```python
|
|
{
|
|
'number': 1, # Comic number (required, sequential)
|
|
'filename': 'comic-001.png', # Image filename (required)
|
|
'mobile_filename': 'comic-001-mobile.png', # Optional mobile version
|
|
'date': '2025-01-01', # Publication date (required)
|
|
'alt_text': 'Alt text for comic', # Accessibility text (required)
|
|
'title': 'Comic Title', # Title (optional, shows #X if absent)
|
|
'author_note': 'Optional note', # Author note (optional, plain text)
|
|
'full_width': True, # Optional: override FULL_WIDTH_DEFAULT
|
|
'plain': True # Optional: override PLAIN_DEFAULT
|
|
}
|
|
```
|
|
|
|
### Adding a New Comic
|
|
|
|
**Option 1: Use the script (recommended)**
|
|
```bash
|
|
# Add comic entry only
|
|
python scripts/add_comic.py
|
|
|
|
# Add comic entry 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. Then edit `comics_data.py` to customize.
|
|
|
|
**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`
|
|
|
|
### Generating RSS Feed
|
|
|
|
After adding comics, regenerate the RSS feed:
|
|
```bash
|
|
python scripts/generate_rss.py
|
|
```
|
|
This creates/updates `static/feed.rss`
|
|
|
|
### Markdown Support
|
|
|
|
**Author Notes:**
|
|
Create markdown files in `content/author_notes/{date}.md` (e.g., `2025-01-01.md`) for rich-formatted author notes. Markdown author notes take precedence over the `author_note` field in `comics_data.py` and render as HTML.
|
|
|
|
Example:
|
|
```bash
|
|
# Create author note for a specific comic
|
|
echo "# My First Comic\n\nThis is **bold** text!" > content/author_notes/2025-01-01.md
|
|
```
|
|
|
|
**About Page:**
|
|
The `/about` route renders `content/about.md` as HTML. Edit this file to customize your about page with markdown formatting.
|
|
|
|
**Note:** Client-side navigation displays author notes as plain text. Markdown author notes only render properly on initial page load (server-side rendering). For full markdown rendering, users need to refresh the page or navigate directly to the comic URL.
|
|
|
|
## Production Deployment
|
|
|
|
For production, you should **NOT** use Flask's built-in development server. Choose one of the following deployment methods:
|
|
|
|
### Option 1: Docker (Recommended)
|
|
|
|
**1. Generate a secure secret key:**
|
|
```bash
|
|
python -c "import secrets; print(secrets.token_hex(32))"
|
|
```
|
|
|
|
**2. Create a `.env` file:**
|
|
```bash
|
|
SECRET_KEY=your-generated-secret-key-here
|
|
```
|
|
|
|
**3. Build and run with Docker Compose:**
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
**Or build and run manually:**
|
|
```bash
|
|
# Build the image
|
|
docker build -t sunday-comics .
|
|
|
|
# Run the container
|
|
docker run -d \
|
|
-p 3000:3000 \
|
|
-e SECRET_KEY="your-secret-key" \
|
|
-v $(pwd)/comics_data.py:/app/comics_data.py:ro \
|
|
-v $(pwd)/static/images:/app/static/images:ro \
|
|
--name sunday-comics \
|
|
sunday-comics
|
|
```
|
|
|
|
**View logs:**
|
|
```bash
|
|
docker-compose logs -f
|
|
```
|
|
|
|
### Option 2: Manual Deployment with Gunicorn
|
|
|
|
**1. Generate a Secure Secret Key**
|
|
|
|
```bash
|
|
python -c "import secrets; print(secrets.token_hex(32))"
|
|
```
|
|
|
|
**2. Set Environment Variables**
|
|
|
|
```bash
|
|
export SECRET_KEY="generated-secret-key-from-above"
|
|
export DEBUG=False
|
|
export PORT=3000
|
|
```
|
|
|
|
**3. Use a Production WSGI Server**
|
|
|
|
**Install Gunicorn:**
|
|
```bash
|
|
pip install gunicorn
|
|
```
|
|
|
|
**Run with Gunicorn:**
|
|
```bash
|
|
gunicorn app:app --bind 0.0.0.0:3000 --workers 4
|
|
```
|
|
|
|
### Using a Reverse Proxy (Recommended)
|
|
|
|
Set up Nginx or another reverse proxy in front of your app for:
|
|
- HTTPS/SSL termination
|
|
- Static file serving
|
|
- Load balancing
|
|
- Better security
|
|
|
|
### Additional Production Considerations
|
|
|
|
- Use a process manager (systemd, supervisor) for non-Docker deployments
|
|
- Set appropriate file permissions
|
|
- Enable HTTPS with Let's Encrypt
|
|
- Consider using a CDN for static assets
|
|
- Monitor logs and performance
|
|
- Set up automated backups of `comics_data.py`
|
|
|
|
## Upgrading to a Database
|
|
|
|
For larger comic archives, consider replacing the `COMICS` list with a database:
|
|
|
|
- SQLite for simple setups
|
|
- PostgreSQL/MySQL for production
|
|
- Use Flask-SQLAlchemy for ORM support
|
|
|
|
## Customization
|
|
|
|
### Branding
|
|
- Update `COMIC_NAME` in `comics_data.py` to change your comic's name
|
|
- Update `SITE_URL` in `comics_data.py` for production deployment
|
|
- Customize logo by setting `LOGO_IMAGE` and `LOGO_MODE`
|
|
|
|
### Styling
|
|
- Modify `static/css/style.css` to change colors, fonts, and layout
|
|
- Current color scheme uses blue (#3498db) and dark blue-gray (#2c3e50)
|
|
|
|
### About Page
|
|
- Edit `content/about.md` to add your bio and comic information (supports markdown)
|
|
|
|
### Icon Navigation
|
|
To use icon navigation:
|
|
1. Set `USE_COMIC_NAV_ICONS = True` in `comics_data.py`
|
|
2. Add icons to `static/images/icons/`:
|
|
- Comic navigation: `first.png`, `previous.png`, `next.png`, `latest.png`
|
|
- Header navigation: `alert.png`, `archive.png`, `info.png`
|
|
- Social links: `instagram.png`, `youtube.png`, `mail.png`
|
|
|
|
### Social Links
|
|
Configure social media links in `comics_data.py`:
|
|
```python
|
|
SOCIAL_INSTAGRAM = 'https://instagram.com/yourhandle'
|
|
SOCIAL_YOUTUBE = 'https://youtube.com/@yourchannel'
|
|
SOCIAL_EMAIL = 'mailto:your@email.com'
|
|
```
|
|
|
|
## Pages
|
|
|
|
- `/` - Shows the latest comic
|
|
- `/comic/<id>` - View a specific comic by number
|
|
- `/archive` - Browse all comics in a grid
|
|
- `/about` - About the comic and author
|
|
- `/static/feed.rss` - RSS feed
|
|
|
|
## API Endpoints
|
|
|
|
The app exposes a JSON API for programmatic access:
|
|
|
|
- **GET `/api/comics`** - Returns all comics as a JSON array
|
|
```json
|
|
[
|
|
{
|
|
"number": 1,
|
|
"title": "First Comic",
|
|
"filename": "comic-001.png",
|
|
"date": "2025-01-01",
|
|
"alt_text": "The very first comic",
|
|
"author_note": "This is where your comic journey begins!"
|
|
}
|
|
]
|
|
```
|
|
|
|
- **GET `/api/comics/<id>`** - Returns a specific comic as JSON
|
|
```json
|
|
{
|
|
"number": 1,
|
|
"title": "First Comic",
|
|
"filename": "comic-001.png",
|
|
"date": "2025-01-01",
|
|
"alt_text": "The very first comic",
|
|
"author_note": "This is where your comic journey begins!"
|
|
}
|
|
```
|
|
|
|
Returns 404 with `{"error": "Comic not found"}` if the comic doesn't exist.
|
|
|
|
## Credits
|
|
|
|
- Inspired by [Rarebit](https://rarebit.neocities.org/)
|
|
- Favicon generated using [favicon.io](https://favicon.io)
|
|
- Example comics sourced from Boots and Her Buddies comic strip at [Comic Book Plus](https://comicbookplus.com/?cid=2561)
|
|
|
|
## License
|
|
|
|
[Add your license here]
|