atr/templates/announce-selected.html (186 lines of code) (raw):
{% extends "layouts/base.html" %}
{% block title %}
Announce and distribute {{ release.project.display_name }} {{ release.version }} ~ ATR
{% endblock title %}
{% block description %}
Announce and distribute {{ release.project.display_name }} {{ release.version }} as a release.
{% endblock description %}
{% block stylesheets %}
{{ super() }}
<style>
.page-preview-meta-item::after {
content: "•";
margin-left: 1rem;
color: #ccc;
}
.page-preview-meta-item:last-child::after {
content: none;
}
</style>
{% endblock stylesheets %}
{% block content %}
<p class="d-flex justify-content-between align-items-center">
<a href="{{ as_url(routes.finish.selected, project_name=release.project.name, version_name=release.version) }}"
class="atr-back-link">← Back to Finish {{ release.short_display_name }}</a>
<span>
<span class="atr-phase-symbol-other">①</span>
<span class="atr-phase-arrow">→</span>
<strong class="atr-phase-symbol-other">②</strong>
<span class="atr-phase-arrow">→</span>
<strong class="atr-phase-three atr-phase-symbol">③</strong>
<span class="atr-phase-three atr-phase-label">FINISH</span>
</span>
</p>
<h1>
Announce <strong>{{ release.project.short_display_name }}</strong> <em>{{ release.version }}</em>
</h1>
<div id="{{ release.name }}" class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<h3 class="card-title mb-0">About this release preview</h3>
</div>
<div class="card-body">
<div class="d-flex flex-wrap gap-3 pb-1 text-secondary fs-6">
<span class="page-preview-meta-item">Revision: {{ release.revision }}</span>
<span class="page-preview-meta-item">Created: {{ release.created.strftime("%Y-%m-%d %H:%M:%S UTC") }}</span>
</div>
<!--
<div>
<a title="Show files for {{ release.name }}" href="{{ as_url(routes.preview.view, project_name=release.project.name, version_name=release.version) }}" class="btn btn-sm btn-secondary">
<i class="bi bi-archive"></i>
Show files
</a>
</div>
-->
</div>
</div>
<h2>Announce this release</h2>
<p>This form will send an announcement to the ASF user-tests@tooling.apache.org mailing list.</p>
<form method="post"
id="announce-release-form"
action="{{ as_url(routes.announce.selected_post, project_name=release.project.name, version_name=release.version) }}"
class="atr-canary py-4 px-5 mb-4 border rounded">
{{ announce_form.hidden_tag() }}
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-3 text-md-end fw-medium">{{ announce_form.mailing_list.label }}</div>
<div class="col-md-9">
<div class="d-flex gap-4 mb-3">
{% for subfield in announce_form.mailing_list %}
<div class="form-check">
{{ subfield(class_='form-check-input') }}
{{ subfield.label(class_='form-check-label') }}
</div>
{% endfor %}
</div>
{% if announce_form.mailing_list.errors %}
<div class="invalid-feedback d-block">
{% for error in announce_form.mailing_list.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
<div class="card bg-warning-subtle mb-3">
<span class="card-body p-3">
<i class="bi bi-exclamation-triangle me-1"></i>
<strong>TODO:</strong> The limited options above are provided for testing purposes. In the finished version of ATR, you will be able to send to your own specified mailing lists.
</span>
</div>
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<label for="{{ announce_form.subject.id }}"
class="col-md-3 col-form-label text-md-end">{{ announce_form.subject.label.text }}:</label>
<div class="col-md-9">
{{ announce_form.subject(class_='form-control') }}
{% if announce_form.subject.errors %}
<div class="invalid-feedback d-block">
{% for error in announce_form.subject.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<label for="{{ announce_form.body.id }}"
class="col-md-3 col-form-label text-md-end">{{ announce_form.body.label.text }}:</label>
<div class="col-md-9">
{{ announce_form.body(class_='form-control font-monospace', rows='12') }}
{% if announce_form.body.errors %}
<div class="invalid-feedback d-block">
{% for error in announce_form.body.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-9 offset-md-3">
<details>
<summary class="text-muted">Show live preview</summary>
<pre id="announce-email-body-preview"
data-preview-url="{{ as_url(routes.preview.announce_preview, project_name=release.project.name, version_name=release.version) }}"
data-asf-uid="{{ current_user.uid }}"
class="mt-2 p-3 bg-white border rounded font-monospace overflow-auto"></pre>
</details>
</div>
</div>
<div class="row mb-3">
<div class="col-md-9 offset-md-3">
<div class="form-check">
{{ announce_form.confirm_announce(class_="form-check-input") }}
{{ announce_form.confirm_announce.label(class_="form-check-label") }}
</div>
{% if announce_form.confirm_announce.errors %}
<div class="text-danger small mt-1">{{ announce_form.confirm_announce.errors[0] }}</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-9 offset-md-3">{{ announce_form.submit(class_='btn btn-primary') }}</div>
</div>
</form>
{% endblock content %}
{% block javascripts %}
{{ super() }}
<script>
document.addEventListener("DOMContentLoaded", () => {
let debounceTimeout;
const debounceDelay = 500;
const bodyTextarea = document.getElementById("body");
const previewPre = document.getElementById("announce-email-body-preview");
const announceForm = document.getElementById("announce-release-form");
if (!bodyTextarea || !previewPre || !announceForm) {
console.error("Required elements for preview not found.");
return;
}
const previewUrl = previewPre.dataset.previewUrl;
const csrfTokenInput = announceForm.querySelector('input[name="csrf_token"]');
if (!previewUrl || !csrfTokenInput) {
console.error("Required data attributes or CSRF token not found.");
return
}
const csrfToken = csrfTokenInput.value;
function fetchAndUpdatePreview() {
const bodyContent = bodyTextarea.value;
fetch(previewUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRFToken": csrfToken
},
body: new URLSearchParams({
"body": bodyContent,
"csrf_token": csrfToken
})
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error(`HTTP error ${response.status}: ${text}`)
});
}
return response.text();
})
.then(previewText => {
previewPre.textContent = previewText;
})
.catch(error => {
console.error("Error fetching email preview:", error);
previewPre.textContent = `Error loading preview:\\n${error.message}`;
});
}
bodyTextarea.addEventListener("input", () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(fetchAndUpdatePreview, debounceDelay);
});
fetchAndUpdatePreview();
});
</script>
{% endblock javascripts %}