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 %}