Files
sunday/app.py
2025-11-13 10:38:26 +10:00

173 lines
5.3 KiB
Python

import os
from datetime import datetime
from flask import Flask, render_template, abort, jsonify, request
from comics_data import COMICS, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE, COMPACT_FOOTER, USE_ICON_NAV
import markdown
app = Flask(__name__)
# Configuration
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-secret-key')
@app.context_processor
def inject_global_settings():
"""Make global settings available to all templates"""
return {
'header_image': HEADER_IMAGE,
'footer_image': FOOTER_IMAGE,
'compact_footer': COMPACT_FOOTER,
'use_icon_nav': USE_ICON_NAV
}
def is_full_width(comic):
"""Determine if a comic should be full width based on global and per-comic settings"""
# If comic explicitly sets full_width, use that value
if 'full_width' in comic:
return comic['full_width']
# Otherwise use the global default
return FULL_WIDTH_DEFAULT
def is_plain(comic):
"""Determine if a comic should be plain mode based on global and per-comic settings"""
# If comic explicitly sets plain, use that value
if 'plain' in comic:
return comic['plain']
# Otherwise use the global default
return PLAIN_DEFAULT
def format_comic_date(date_str):
"""Format date string (YYYY-MM-DD) to 'Day name, Month name day, year'"""
try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
# Use %d and strip leading zero for cross-platform compatibility
day = date_obj.strftime('%d').lstrip('0')
formatted = date_obj.strftime(f'%A, %B {day}, %Y')
return formatted
except:
return date_str
def get_author_note(date_str):
"""Load author note from markdown file if it exists, using date as filename"""
note_path = os.path.join(os.path.dirname(__file__), 'content', 'author_notes', f'{date_str}.md')
try:
with open(note_path, 'r', encoding='utf-8') as f:
content = f.read()
return markdown.markdown(content)
except FileNotFoundError:
return None
def enrich_comic(comic):
"""Add computed properties to comic data"""
if comic is None:
return None
enriched = comic.copy()
enriched['full_width'] = is_full_width(comic)
enriched['plain'] = is_plain(comic)
enriched['formatted_date'] = format_comic_date(comic['date'])
# Check for markdown author note, fall back to data field if not found
markdown_note = get_author_note(comic['date'])
if markdown_note:
enriched['author_note'] = markdown_note
enriched['author_note_is_html'] = True
else:
# No markdown file, use plain text from comic data if it exists
enriched['author_note_is_html'] = False
return enriched
def get_comic_by_number(number):
"""Get a comic by its number"""
for comic in COMICS:
if comic['number'] == number:
return enrich_comic(comic)
return None
def get_latest_comic():
"""Get the most recent comic"""
if COMICS:
return enrich_comic(COMICS[-1])
return None
@app.route('/')
def index():
"""Home page - shows latest comic"""
comic = get_latest_comic()
if not comic:
return render_template('index.html', title='Latest Comic',
comic=None, total_comics=0)
return render_template('index.html', title='Latest Comic',
comic=comic, total_comics=len(COMICS))
@app.route('/comic/<int:comic_id>')
def comic(comic_id):
"""View a specific comic"""
comic = get_comic_by_number(comic_id)
if not comic:
abort(404)
return render_template('comic.html', title=f"Comic #{comic_id}",
comic=comic, total_comics=len(COMICS))
@app.route('/archive')
def archive():
"""Archive page showing all comics"""
# Reverse order to show newest first
comics = [enrich_comic(comic) for comic in reversed(COMICS)]
return render_template('archive.html', title='Archive',
comics=comics)
@app.route('/about')
def about():
"""About page"""
# Read and render the markdown file
about_path = os.path.join(os.path.dirname(__file__), 'content', 'about.md')
try:
with open(about_path, 'r', encoding='utf-8') as f:
content = f.read()
html_content = markdown.markdown(content)
except FileNotFoundError:
html_content = '<p>About content not found.</p>'
return render_template('page.html', title='About', content=html_content)
@app.route('/api/comics')
def api_comics():
"""API endpoint - returns all comics as JSON"""
return jsonify([enrich_comic(comic) for comic in COMICS])
@app.route('/api/comics/<int:comic_id>')
def api_comic(comic_id):
"""API endpoint - returns a specific comic as JSON"""
comic = get_comic_by_number(comic_id)
if not comic:
return jsonify({'error': 'Comic not found'}), 404
return jsonify(comic)
@app.errorhandler(404)
def page_not_found(e):
"""404 error handler"""
# Return JSON for API requests
if request.path.startswith('/api/'):
return jsonify({'error': 'Not found'}), 404
# Return HTML for regular pages
return render_template('404.html', title='Page Not Found'), 404
if __name__ == '__main__':
port = int(os.environ.get('PORT', 3000))
debug = os.environ.get('DEBUG', 'False').lower() in ('true', '1', 't')
app.run(debug=debug, port=port)