Files
pi-weekly-newspaper/templates/publish.html
2026-04-06 19:29:59 -04:00

275 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Publish{% endblock %}
{% block content %}
<h1>Publish Issue</h1>
<div class="tab-bar">
<button class="tab active" data-tab="weekly" onclick="switchTab('weekly')">Weekly Issue</button>
<button class="tab" data-tab="multi-week" onclick="switchTab('multi-week')">Multi-Week</button>
<button class="tab" data-tab="single-article" onclick="switchTab('single-article')">Single Article</button>
</div>
<!-- WEEKLY TAB -->
<div id="tab-weekly" class="tab-content active">
<div class="calendar-widget">
<div class="calendar-nav">
<button class="outline" onclick="changeMonth(-1)">&#9664;</button>
<strong id="cal-month-label">{{ cal_month_name }}</strong>
<button class="outline" onclick="changeMonth(1)">&#9654;</button>
</div>
<table class="calendar-grid">
<thead>
<tr>
<th>Wk</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th>
</tr>
</thead>
<tbody id="cal-body">
{% for week in calendar_weeks %}
<tr class="cal-week" data-start="{{ week.week_start }}" data-end="{{ week.week_end }}"
onclick="selectWeek(this)">
<td class="cal-wk">{{ week.iso_week }}{% if week.article_count %} <small>({{ week.article_count }})</small>{% endif %}</td>
{% for day in week.days %}
<td class="{% if not day.in_month %}cal-dim{% endif %}">{{ day.day }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="weekly-summary" class="publish-summary"></div>
<div id="weekly-articles"></div>
<form method="post" action="/publish" id="weekly-form">
<input type="hidden" name="week_start" id="weekly-start">
<input type="hidden" name="week_end" id="weekly-end">
<input type="hidden" name="issue_type" value="weekly">
<div id="weekly-checkboxes"></div>
<div class="publish-actions">
<select name="cover_method">
<option value="mosaic">Mosaic Cover</option>
<option value="text">Text Cover</option>
</select>
<button type="submit">Generate Issue</button>
</div>
</form>
</div>
<!-- MULTI-WEEK TAB -->
<div id="tab-multi-week" class="tab-content">
<div class="calendar-widget">
<div class="calendar-nav">
<button class="outline" onclick="changeMonthMulti(-1)">&#9664;</button>
<strong id="cal-month-label-multi">{{ cal_month_name }}</strong>
<button class="outline" onclick="changeMonthMulti(1)">&#9654;</button>
</div>
<table class="calendar-grid">
<thead>
<tr>
<th>Wk</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th>
</tr>
</thead>
<tbody id="cal-body-multi">
{% for week in calendar_weeks %}
<tr class="cal-week-multi" data-start="{{ week.week_start }}" data-end="{{ week.week_end }}"
onclick="selectWeekMulti(this)">
<td class="cal-wk">{{ week.iso_week }}{% if week.article_count %} <small>({{ week.article_count }})</small>{% endif %}</td>
{% for day in week.days %}
<td class="{% if not day.in_month %}cal-dim{% endif %}">{{ day.day }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="multi-summary" class="publish-summary"></div>
<form method="post" action="/publish" id="multi-form">
<input type="hidden" name="week_start" id="multi-start">
<input type="hidden" name="week_end" id="multi-end">
<input type="hidden" name="issue_type" value="multi_week">
<div id="multi-checkboxes"></div>
<div class="publish-actions">
<select name="cover_method">
<option value="mosaic">Mosaic Cover</option>
<option value="text">Text Cover</option>
</select>
<button type="submit">Generate Issue</button>
</div>
</form>
</div>
<!-- SINGLE ARTICLE TAB -->
<div id="tab-single-article" class="tab-content">
<input type="search" id="article-search" placeholder="Search articles by title, author, or category..."
oninput="filterArticles()">
<form method="post" action="/publish" id="single-form">
<input type="hidden" name="issue_type" value="single_article">
<input type="hidden" name="week_start" id="single-start">
<input type="hidden" name="week_end" id="single-end">
<div id="single-articles" class="article-radio-list">
{% for article in all_articles %}
<label class="article-radio-item" data-search="{{ article.title|lower }} {{ article.author|lower }} {{ article.categories|lower }}">
<input type="radio" name="article_ids" value="{{ article.id }}"
data-date="{{ article.pub_date.strftime('%Y-%m-%d') }}"
onchange="selectSingleArticle(this)">
<div>
<strong>{{ article.title }}</strong><br>
<small>{{ article.pub_date.strftime('%b %d, %Y') }} &middot; {{ article.author }} &middot; {{ article.categories | replace('[', '') | replace(']', '') | replace('"', '') }}</small>
</div>
</label>
{% endfor %}
</div>
<div class="publish-actions">
<select name="cover_method">
<option value="mosaic">Mosaic Cover</option>
<option value="text">Text Cover</option>
</select>
<button type="submit">Generate Single Issue</button>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
<script>
let calYear = {{ cal_year }};
let calMonth = {{ cal_month }};
let calYearMulti = {{ cal_year }};
let calMonthMulti = {{ cal_month }};
let multiStart = null;
let multiEnd = null;
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelector(`[data-tab="${tab}"]`).classList.add('active');
document.getElementById(`tab-${tab}`).classList.add('active');
}
async function loadArticles(start, end, targetId) {
const resp = await fetch(`/publish/articles?start=${start}&end=${end}`);
const articles = await resp.json();
const container = document.getElementById(targetId);
container.innerHTML = articles.map(a =>
`<label class="article-check-item">
<input type="checkbox" name="article_ids" value="${a.id}" checked>
<span><strong>${a.title}</strong> <small>${a.pub_date} &middot; ${a.author}</small></span>
</label>`
).join('');
return articles.length;
}
async function selectWeek(row) {
document.querySelectorAll('.cal-week').forEach(r => r.classList.remove('selected'));
row.classList.add('selected');
const start = row.dataset.start;
const end = row.dataset.end;
document.getElementById('weekly-start').value = start;
document.getElementById('weekly-end').value = end;
const count = await loadArticles(start, end, 'weekly-checkboxes');
const ws = new Date(start + 'T00:00:00');
const we = new Date(end + 'T00:00:00');
const opts = { month: 'short', day: 'numeric' };
const optsYear = { month: 'short', day: 'numeric', year: 'numeric' };
document.getElementById('weekly-summary').innerHTML =
`<strong>Week ${row.cells[0].textContent.trim().split(' ')[0]}:</strong> ${ws.toLocaleDateString('en-US', opts)} &ndash; ${we.toLocaleDateString('en-US', optsYear)} &middot; <strong>${count} articles</strong>`;
}
async function selectWeekMulti(row) {
const start = row.dataset.start;
const end = row.dataset.end;
if (!multiStart || (multiStart && multiEnd)) {
multiStart = { start, end, row };
multiEnd = null;
document.querySelectorAll('.cal-week-multi').forEach(r => r.classList.remove('selected', 'in-range'));
row.classList.add('selected');
} else {
multiEnd = { start, end, row };
if (multiEnd.start < multiStart.start) {
[multiStart, multiEnd] = [multiEnd, multiStart];
}
document.querySelectorAll('.cal-week-multi').forEach(r => {
r.classList.remove('selected', 'in-range');
if (r.dataset.start >= multiStart.start && r.dataset.end <= multiEnd.end) {
r.classList.add('in-range');
}
});
multiStart.row.classList.add('selected');
multiEnd.row.classList.add('selected');
}
const rangeStart = multiStart.start;
const rangeEnd = (multiEnd || multiStart).end;
document.getElementById('multi-start').value = rangeStart;
document.getElementById('multi-end').value = rangeEnd;
const count = await loadArticles(rangeStart, rangeEnd, 'multi-checkboxes');
const ws = new Date(rangeStart + 'T00:00:00');
const we = new Date(rangeEnd + 'T00:00:00');
const opts = { month: 'short', day: 'numeric' };
const optsYear = { month: 'short', day: 'numeric', year: 'numeric' };
document.getElementById('multi-summary').innerHTML =
`<strong>Range:</strong> ${ws.toLocaleDateString('en-US', opts)} &ndash; ${we.toLocaleDateString('en-US', optsYear)} &middot; <strong>${count} articles</strong>`;
}
async function changeMonth(delta) {
calMonth += delta;
if (calMonth > 12) { calMonth = 1; calYear++; }
if (calMonth < 1) { calMonth = 12; calYear--; }
const resp = await fetch(`/publish/calendar?year=${calYear}&month=${calMonth}`);
const weeks = await resp.json();
renderCalendar(weeks, 'cal-body', 'cal-week', 'selectWeek(this)');
document.getElementById('cal-month-label').textContent =
new Date(calYear, calMonth - 1).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
}
async function changeMonthMulti(delta) {
calMonthMulti += delta;
if (calMonthMulti > 12) { calMonthMulti = 1; calYearMulti++; }
if (calMonthMulti < 1) { calMonthMulti = 12; calYearMulti--; }
const resp = await fetch(`/publish/calendar?year=${calYearMulti}&month=${calMonthMulti}`);
const weeks = await resp.json();
renderCalendar(weeks, 'cal-body-multi', 'cal-week-multi', 'selectWeekMulti(this)');
document.getElementById('cal-month-label-multi').textContent =
new Date(calYearMulti, calMonthMulti - 1).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
multiStart = null;
multiEnd = null;
}
function renderCalendar(weeks, tbodyId, rowClass, onclickFn) {
const tbody = document.getElementById(tbodyId);
tbody.innerHTML = weeks.map(w => {
const days = w.days.map(d =>
`<td class="${d.in_month ? '' : 'cal-dim'}">${d.day}</td>`
).join('');
const count = w.article_count ? ` <small>(${w.article_count})</small>` : '';
return `<tr class="${rowClass}" data-start="${w.week_start}" data-end="${w.week_end}" onclick="${onclickFn}">
<td class="cal-wk">${w.iso_week}${count}</td>${days}
</tr>`;
}).join('');
}
function selectSingleArticle(radio) {
const pubDate = radio.dataset.date;
document.getElementById('single-start').value = pubDate;
document.getElementById('single-end').value = pubDate;
}
function filterArticles() {
const query = document.getElementById('article-search').value.toLowerCase();
document.querySelectorAll('.article-radio-item').forEach(item => {
const searchText = item.dataset.search;
item.style.display = searchText.includes(query) ? '' : 'none';
});
}
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function() {
const btn = this.querySelector('button[type="submit"]');
if (btn) {
btn.setAttribute('aria-busy', 'true');
btn.textContent = 'Generating...';
}
});
});
</script>
{% endblock %}