Social composer: send now and rename messy account names (#6)
## Summary Closes #5. - Composer can **Send now** without a schedule time (Schedule and Save draft still available) - Account names wrap and show the platform id so a bad OAuth label is still readable - Social accounts page can rename the display name; re-auth keeps a custom name instead of overwriting it with a placeholder - Worker polls `dispatch_due` every 15s so scheduled campaigns and social posts fire without a manual command; local `docker compose up` now starts the worker ## Test plan - [ ] Open Social → Compose: confirm **Send now**, **Schedule**, and **Save draft** buttons (no combined Save / publish) - [ ] Send now with a connected account and caption → post queues immediately, no datetime required - [ ] Schedule with a datetime → post is scheduled; Schedule with a blank time → validation error - [ ] Composer account list shows full name plus external id, wrapping if the name is long - [ ] Social accounts → rename a messy label → new name shows in composer without reconnecting - [ ] Re-authorize an account that was renamed → custom name is kept - [ ] Rebuild and start compose (`docker compose up --build`) so the **worker** service is running - [ ] Schedule a social post ~2 minutes ahead → after due time, worker logs `Enqueued N due item(s).` and the post leaves Scheduled (published or failed) - [ ] `docker compose logs -f worker` shows `Polling due scheduled work every 15s.` Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
@@ -137,8 +137,18 @@
|
||||
{% for account in accounts %}
|
||||
<tr {% if not account.is_active %}class="row-warn"{% endif %}>
|
||||
<td>
|
||||
<strong>{{ account.label }}</strong><br>
|
||||
<span class="muted">{{ account.external_id }}</span>
|
||||
<form method="post" class="account-rename">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="rename">
|
||||
<input type="hidden" name="account_id" value="{{ account.pk }}">
|
||||
<input id="id_label_{{ account.pk }}" name="label" type="text" maxlength="120"
|
||||
value="{{ account.label }}" required
|
||||
aria-label="Display name for {{ account.get_platform_display }}">
|
||||
<button class="btn btn-ghost btn-sm" type="submit">Rename</button>
|
||||
</form>
|
||||
{% if account.external_id %}
|
||||
<span class="muted" style="display:block;margin-top:6px;overflow-wrap:anywhere">{{ account.external_id }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="platform-pill {% if account.platform == 'facebook' %}meta{% elif account.platform == 'instagram' %}ig{% else %}li{% endif %}">
|
||||
|
||||
@@ -116,34 +116,35 @@
|
||||
<div class="field">
|
||||
<label>Accounts</label>
|
||||
{% for account in accounts %}
|
||||
<label class="check-row">
|
||||
<label class="check-row account-pick">
|
||||
<input type="checkbox" name="account_ids" value="{{ account.pk }}"
|
||||
class="account-check"
|
||||
data-platform="{{ account.platform }}"
|
||||
{% if account.pk|stringformat:"s" in form.account_ids %}checked{% endif %}>
|
||||
{{ account.get_platform_display }} · {{ account.label }}
|
||||
<span class="account-pick-copy">
|
||||
<strong>{{ account.display_name }}</strong>
|
||||
<span class="muted">{{ account.get_platform_display }}{% if account.external_id %} · {{ account.external_id }}{% endif %}</span>
|
||||
</span>
|
||||
</label>
|
||||
{% empty %}
|
||||
<p class="muted">No active accounts.</p>
|
||||
{% endfor %}
|
||||
{% if accounts %}
|
||||
<div class="hint">Name look wrong? <a href="{% url 'social:account_list' %}">Rename it on Social accounts</a>.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-grid cols-2">
|
||||
<div class="field">
|
||||
<label for="id_publish_mode">Publish</label>
|
||||
<select id="id_publish_mode" name="publish_mode">
|
||||
<option value="schedule"{% if form.publish_mode == "schedule" %} selected{% endif %}>Schedule</option>
|
||||
<option value="now"{% if form.publish_mode == "now" %} selected{% endif %}>Publish now</option>
|
||||
<option value="draft"{% if form.publish_mode == "draft" %} selected{% endif %}>Save draft only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_scheduled_for">When</label>
|
||||
<label for="id_scheduled_for">Schedule for <span class="muted">(optional)</span></label>
|
||||
<input id="id_scheduled_for" name="scheduled_for" type="datetime-local" step="60"
|
||||
value="{{ form.scheduled_for }}">
|
||||
<div class="hint">Needed only if you click Schedule. Leave blank to Send now.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" type="submit" name="action" value="publish">Save / publish</button>
|
||||
<button class="btn btn-primary" type="submit" name="action" value="send_now">Send now</button>
|
||||
<button class="btn btn-ghost" type="submit" name="action" value="schedule">Schedule</button>
|
||||
<button class="btn btn-ghost" type="submit" name="action" value="save">Save draft</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -199,8 +200,8 @@
|
||||
var previewBody = document.getElementById('preview-body');
|
||||
var content = document.getElementById('preview-content');
|
||||
var previewMedia = document.getElementById('preview-media');
|
||||
var mode = document.getElementById('id_publish_mode');
|
||||
var when = document.getElementById('id_scheduled_for');
|
||||
var composeForm = document.getElementById('social-compose');
|
||||
var mediaInput = document.getElementById('id_media');
|
||||
var mediaJson = document.getElementById('id_media_json');
|
||||
var thumbs = document.getElementById('media-thumbs');
|
||||
@@ -296,9 +297,14 @@
|
||||
previewBody.hidden = !hasContent;
|
||||
}
|
||||
|
||||
function syncWhen() {
|
||||
if (!mode || !when) return;
|
||||
when.disabled = mode.value !== 'schedule';
|
||||
function requireScheduleTime(event) {
|
||||
var submitter = event.submitter;
|
||||
var action = submitter && submitter.name === 'action' ? submitter.value : '';
|
||||
if (action !== 'schedule') return;
|
||||
if (when && when.value) return;
|
||||
event.preventDefault();
|
||||
if (when) when.focus();
|
||||
window.alert('Pick a date and time to schedule, or choose Send now.');
|
||||
}
|
||||
|
||||
function validateBeforeUpload(file) {
|
||||
@@ -363,7 +369,7 @@
|
||||
bodyEl.addEventListener('keyup', syncPreview);
|
||||
bodyEl.addEventListener('change', syncPreview);
|
||||
}
|
||||
if (mode) mode.addEventListener('change', syncWhen);
|
||||
if (composeForm) composeForm.addEventListener('submit', requireScheduleTime);
|
||||
|
||||
var generateForm = document.querySelector('form input[name=action][value=generate]')?.form;
|
||||
if (generateForm) {
|
||||
@@ -378,7 +384,6 @@
|
||||
syncMediaField();
|
||||
renderThumbs();
|
||||
syncPreview();
|
||||
syncWhen();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -38,7 +38,12 @@
|
||||
<tbody>
|
||||
{% for target in post.targets.all %}
|
||||
<tr>
|
||||
<td>{{ target.account.label }}</td>
|
||||
<td>
|
||||
{{ target.account.display_name }}
|
||||
{% if target.account.external_id %}
|
||||
<br><span class="muted" style="overflow-wrap:anywhere">{{ target.account.external_id }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ target.get_platform_display }}</td>
|
||||
<td><span class="badge badge-{{ target.status }}">{{ target.get_status_display }}</span></td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user