fade38e7a0
- GET /write now checks all groups are written/skipped before showing
the completion screen; incomplete sessions are redirected to the first
draft group
- POST /write/done now accepts form data (not JSON) and redirects to
/export; wired up from the completion screen via a <form> POST button
- phase5.html extra_scripts block wrapped in {% if group %} to prevent
Jinja errors when group is None on the completion screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
203 lines
7.7 KiB
HTML
203 lines
7.7 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="p-4 max-w-6xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h1 class="text-xl font-bold">Write</h1>
|
|
<span class="text-sm opacity-60">{{ done_count }} / {{ total }} done</span>
|
|
</div>
|
|
|
|
{% if not group %}
|
|
<div class="alert alert-success mb-4">All groups written or skipped.</div>
|
|
<form method="post" action="/write/done">
|
|
<input type="hidden" name="album_id" value="{{ album_id }}">
|
|
<button type="submit" class="btn btn-primary">Export →</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="flex gap-4">
|
|
|
|
<!-- Photos panel -->
|
|
<div class="group-photos w-64 flex-shrink-0 space-y-2 overflow-y-auto max-h-[80vh]">
|
|
{% for photo in photos %}
|
|
<img src="/proxy/thumb/{{ photo.id }}"
|
|
id="photo-{{ photo.id }}"
|
|
class="w-full rounded cursor-pointer border-4 border-transparent transition"
|
|
onclick="setHero('{{ photo.id }}')"
|
|
alt="">
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Form -->
|
|
<div class="flex-1 space-y-4">
|
|
<!-- Mode switch -->
|
|
<div class="tabs">
|
|
<button id="mode-journal" class="tab tab-bordered tab-active"
|
|
onclick="setMode('journal')">Journal</button>
|
|
<button id="mode-story" class="tab tab-bordered"
|
|
onclick="setMode('story')">Story</button>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label text-sm">Title</label>
|
|
<input id="title-field" type="text" class="input input-bordered"
|
|
oninput="scheduleAutosave()"
|
|
value="{{ group.title | e }}">
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label text-sm">Date</label>
|
|
<input id="date-field" type="text" class="input input-bordered input-sm"
|
|
oninput="scheduleAutosave()"
|
|
value="{{ group.date | e }}">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<div class="form-control">
|
|
<label class="label text-sm">City</label>
|
|
<input id="city-field" type="text" class="input input-bordered input-sm"
|
|
oninput="scheduleAutosave()"
|
|
value="{{ group.location_city | e }}">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label text-sm">Country</label>
|
|
<input id="country-field" type="text" class="input input-bordered input-sm"
|
|
oninput="scheduleAutosave()"
|
|
value="{{ group.location_country | e }}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control" id="mode-journal-fields">
|
|
<label class="label text-sm">Body</label>
|
|
<textarea id="body-field" class="textarea textarea-bordered h-40"
|
|
oninput="scheduleAutosave()">{{ group.body | e }}</textarea>
|
|
</div>
|
|
|
|
<!-- Story-only fields (hidden by default if mode is journal) -->
|
|
<div id="hero-picker" class="form-control" style="display:{% if group.entry_type == 'story' %}block{% else %}none{% endif %}">
|
|
<label class="label text-sm">Hero photo: <span id="hero-label">{{ group.hero_photo_id or 'none' }}</span></label>
|
|
<p class="text-xs opacity-60">Click a photo on the left to set it as the hero.</p>
|
|
</div>
|
|
|
|
<div id="shortcode-field-wrap" class="form-control" style="display:{% if group.entry_type == 'story' %}block{% else %}none{% endif %}">
|
|
<label class="label text-sm">Shortcode hints</label>
|
|
<input id="shortcode-field" type="text" class="input input-bordered input-sm"
|
|
oninput="scheduleAutosave()"
|
|
placeholder="e.g. gallery block, pull quote"
|
|
value="{{ group.shortcode_hints | e }}">
|
|
</div>
|
|
|
|
<div class="flex gap-2 mt-4">
|
|
{% if group_idx > 0 %}
|
|
<a href="/write?album_id={{ album_id }}&group_idx={{ group_idx - 1 }}" class="btn btn-ghost btn-sm">← Prev</a>
|
|
{% endif %}
|
|
<button id="skip-btn" class="btn btn-ghost btn-sm" onclick="skipGroup()">Skip for now</button>
|
|
<button class="btn btn-primary btn-sm ml-auto" onclick="saveAndNext()">Save & next</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inline notes -->
|
|
<div id="inline-notes" class="w-64 flex-shrink-0 bg-base-100 rounded p-3">
|
|
<h3 class="font-semibold text-sm mb-2">Your notes</h3>
|
|
<p class="text-xs opacity-70 whitespace-pre-wrap">{{ state.notes or 'No notes yet.' }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
{% if group %}
|
|
<script>
|
|
(function() {
|
|
var albumId = {{ album_id | tojson }};
|
|
var groupId = {{ group.id | tojson }};
|
|
var mode = {{ group.entry_type | tojson }};
|
|
var heroId = {{ group.hero_photo_id | tojson }};
|
|
var autosaveTimer = null;
|
|
|
|
window.setMode = function(m) {
|
|
mode = m;
|
|
var storyFields = ['hero-picker', 'shortcode-field-wrap'];
|
|
storyFields.forEach(function(id) {
|
|
var el = document.getElementById(id);
|
|
if (el) el.style.display = (m === 'story') ? 'block' : 'none';
|
|
});
|
|
document.getElementById('mode-journal').classList.toggle('tab-active', m === 'journal');
|
|
document.getElementById('mode-story').classList.toggle('tab-active', m === 'story');
|
|
scheduleAutosave();
|
|
};
|
|
|
|
window.setHero = function(id) {
|
|
heroId = id;
|
|
// Update border highlight
|
|
document.querySelectorAll('.group-photos img').forEach(function(img) {
|
|
img.classList.remove('border-primary');
|
|
img.classList.add('border-transparent');
|
|
});
|
|
var el = document.getElementById('photo-' + id);
|
|
if (el) { el.classList.remove('border-transparent'); el.classList.add('border-primary'); }
|
|
var label = document.getElementById('hero-label');
|
|
if (label) label.textContent = id;
|
|
scheduleAutosave();
|
|
};
|
|
|
|
window.scheduleAutosave = function() {
|
|
clearTimeout(autosaveTimer);
|
|
autosaveTimer = setTimeout(doAutosave, 500);
|
|
};
|
|
|
|
function getFormData() {
|
|
return {
|
|
album_id: albumId,
|
|
group_id: groupId,
|
|
entry_type: mode,
|
|
hero_photo_id: heroId,
|
|
title: document.getElementById('title-field') ? document.getElementById('title-field').value : '',
|
|
body: document.getElementById('body-field') ? document.getElementById('body-field').value : '',
|
|
location_city: document.getElementById('city-field') ? document.getElementById('city-field').value : '',
|
|
location_country: document.getElementById('country-field') ? document.getElementById('country-field').value : '',
|
|
date: document.getElementById('date-field') ? document.getElementById('date-field').value : '',
|
|
shortcode_hints: document.getElementById('shortcode-field') ? document.getElementById('shortcode-field').value : '',
|
|
};
|
|
}
|
|
|
|
function doAutosave() {
|
|
fetch('/write/autosave', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(getFormData()),
|
|
});
|
|
}
|
|
|
|
window.skipGroup = function() {
|
|
fetch('/write/skip', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({album_id: albumId, group_id: groupId}),
|
|
}).then(function() {
|
|
window.location.reload();
|
|
});
|
|
};
|
|
|
|
window.saveAndNext = function() {
|
|
fetch('/write/save', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(getFormData()),
|
|
}).then(function() {
|
|
var url = new URL(window.location.href);
|
|
var idx = parseInt(url.searchParams.get('group_idx') || '0');
|
|
url.searchParams.set('group_idx', idx + 1);
|
|
window.location.href = url.toString();
|
|
});
|
|
};
|
|
|
|
// Initialize mode display
|
|
if (mode === 'story') {
|
|
document.getElementById('mode-story') && document.getElementById('mode-story').classList.add('tab-active');
|
|
document.getElementById('mode-journal') && document.getElementById('mode-journal').classList.remove('tab-active');
|
|
}
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|