feat: publish UI rewrite — tabs, calendar widget, multi-week and single-article support

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-06 17:06:52 -04:00
parent 807ab8610d
commit 5ca7156723
3 changed files with 399 additions and 86 deletions

View File

@@ -64,6 +64,7 @@ def build_epub(
article_ids: list[int],
cover_path: str,
output_dir: str,
issue_type: str = "weekly",
) -> str:
os.makedirs(output_dir, exist_ok=True)
@@ -74,10 +75,20 @@ def build_epub(
.all()
)
title = (
f"Plymouth Independent \u2014 "
f"Week of {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}"
)
if issue_type == "single_article" and len(articles) == 1:
title = f"Plymouth Independent \u2014 {articles[0].title}"
elif issue_type == "multi_week":
w1 = week_start.isocalendar()[1]
w2 = week_end.isocalendar()[1]
title = (
f"Plymouth Independent \u2014 "
f"Weeks {w1}\u2013{w2}, {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}"
)
else:
title = (
f"Plymouth Independent \u2014 "
f"Week of {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}"
)
book = epub.EpubBook()
book.set_identifier(f"pi-{week_start.isoformat()}")

View File

@@ -1,6 +1,7 @@
import json
from datetime import date, timedelta
from flask import Blueprint, render_template, request, redirect, url_for, flash
from calendar import monthrange
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from app import db
from src.models import Article, Issue
@@ -11,19 +12,64 @@ import config
publish_bp = Blueprint("publish", __name__)
def _get_week_bounds(d: date) -> tuple[date, date]:
week_start = d - timedelta(days=d.weekday())
week_end = week_start + timedelta(days=6)
return week_start, week_end
def _calendar_data(year: int, month: int) -> list[dict]:
"""Build calendar grid data for a given month."""
first_day = date(year, month, 1)
_, days_in_month = monthrange(year, month)
weeks = []
current = first_day - timedelta(days=first_day.weekday())
while current.month <= month or (current + timedelta(days=6)).month <= month or current < first_day:
week_start = current
week_end = current + timedelta(days=6)
iso_week = week_start.isocalendar()[1]
article_count = Article.query.filter(
Article.pub_date >= str(week_start),
Article.pub_date < str(week_end + timedelta(days=1)),
).count()
days = []
for i in range(7):
d = current + timedelta(days=i)
days.append({
"day": d.day,
"date": d.isoformat(),
"in_month": d.month == month,
})
weeks.append({
"iso_week": iso_week,
"week_start": week_start.isoformat(),
"week_end": week_end.isoformat(),
"days": days,
"article_count": article_count,
})
current += timedelta(days=7)
if current.month > month and current.year >= year:
if not any(d["in_month"] for d in days):
weeks.pop()
break
return weeks
@publish_bp.route("/publish", methods=["GET"])
def index():
week_str = request.args.get("week")
if week_str:
try:
year, week_num = week_str.split("-W")
week_start = date.fromisocalendar(int(year), int(week_num), 1)
except (ValueError, TypeError):
week_start = date.today() - timedelta(days=date.today().weekday())
else:
week_start = date.today() - timedelta(days=date.today().weekday())
today = date.today()
cal_year = request.args.get("cal_year", today.year, type=int)
cal_month = request.args.get("cal_month", today.month, type=int)
week_end = week_start + timedelta(days=6)
week_start, week_end = _get_week_bounds(today)
calendar_weeks = _calendar_data(cal_year, cal_month)
articles = (
Article.query
@@ -35,20 +81,75 @@ def index():
.all()
)
all_articles = (
Article.query
.order_by(Article.pub_date.desc())
.all()
)
return render_template(
"publish.html",
articles=articles,
all_articles=all_articles,
week_start=week_start,
week_end=week_end,
week_str=f"{week_start.year}-W{week_start.isocalendar()[1]:02d}",
calendar_weeks=calendar_weeks,
cal_year=cal_year,
cal_month=cal_month,
cal_month_name=date(cal_year, cal_month, 1).strftime("%B %Y"),
)
@publish_bp.route("/publish/articles", methods=["GET"])
def articles_api():
start = request.args.get("start")
end = request.args.get("end")
if not start or not end:
return jsonify([])
try:
start_date = date.fromisoformat(start)
end_date = date.fromisoformat(end)
except ValueError:
return jsonify([])
articles = (
Article.query
.filter(
Article.pub_date >= str(start_date),
Article.pub_date < str(end_date + timedelta(days=1)),
)
.order_by(Article.pub_date.asc())
.all()
)
return jsonify([
{
"id": a.id,
"title": a.title,
"author": a.author,
"pub_date": a.pub_date.strftime("%b %d, %Y"),
"categories": json.loads(a.categories),
}
for a in articles
])
@publish_bp.route("/publish/calendar", methods=["GET"])
def calendar_api():
year = request.args.get("year", type=int)
month = request.args.get("month", type=int)
if not year or not month:
return jsonify([])
return jsonify(_calendar_data(year, month))
@publish_bp.route("/publish", methods=["POST"])
def create_issue():
week_start_str = request.form.get("week_start")
week_end_str = request.form.get("week_end")
cover_method = request.form.get("cover_method", "text")
issue_type = request.form.get("issue_type", "weekly")
included_ids = request.form.getlist("article_ids", type=int)
if not included_ids:
@@ -58,17 +159,6 @@ def create_issue():
week_start = date.fromisoformat(week_start_str)
week_end = date.fromisoformat(week_end_str)
all_week_articles = (
Article.query
.filter(
Article.pub_date >= str(week_start),
Article.pub_date < str(week_end + timedelta(days=1)),
)
.all()
)
all_ids = {a.id for a in all_week_articles}
excluded_ids = list(all_ids - set(included_ids))
headlines = [
a.title for a in Article.query.filter(Article.id.in_(included_ids))
.order_by(Article.pub_date.asc()).all()
@@ -84,12 +174,24 @@ def create_issue():
headlines, categories_list
)
epub_path = build_epub(
week_start, week_end, included_ids, cover_path, config.ISSUES_DIR
week_start, week_end, included_ids, cover_path,
config.ISSUES_DIR, issue_type=issue_type
)
except Exception as e:
flash(f"Error generating issue: {e}", "error")
return redirect(url_for("publish.index"))
all_week_articles = (
Article.query
.filter(
Article.pub_date >= str(week_start),
Article.pub_date < str(week_end + timedelta(days=1)),
)
.all()
)
all_ids = {a.id for a in all_week_articles}
excluded_ids = list(all_ids - set(included_ids))
issue = Issue(
week_start=week_start,
week_end=week_end,
@@ -99,6 +201,7 @@ def create_issue():
article_ids=json.dumps(included_ids),
excluded_article_ids=json.dumps(excluded_ids),
status="published",
issue_type=issue_type,
)
db.session.add(issue)
db.session.commit()