348 lines
11 KiB
Markdown
348 lines
11 KiB
Markdown
# Sunday Comics - Webcomic Flask App
|
|
|
|
A Flask-based webcomic website with server-side rendering using Jinja2 templates.
|
|
|
|
## 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]
|