This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 76a4d223e6c003eca9a31d29e89621309d2d2e42
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Dec 10 15:18:03 2025 +0000

    Move announcement and ignore form scripts to separate files
---
 atr/get/announce.py                 | 129 +++---------------------------------
 atr/get/ignores.py                  |  33 +--------
 atr/static/js/announce-preview.js   | 118 +++++++++++++++++++++++++++++++++
 atr/static/js/ignore-form-change.js |  15 +++++
 4 files changed, 144 insertions(+), 151 deletions(-)

diff --git a/atr/get/announce.py b/atr/get/announce.py
index b8034c3..fddc8e5 100644
--- a/atr/get/announce.py
+++ b/atr/get/announce.py
@@ -82,6 +82,7 @@ async def selected(session: web.Committer, project_name: str, 
version_name: str)
         title=f"Announce and distribute {release.project.display_name} 
{release.version}",
         description=f"Announce and distribute {release.project.display_name} 
{release.version} as a release.",
         content=content,
+        javascripts=["announce-preview"],
     )
 
 
@@ -133,6 +134,10 @@ async def _render_page(
         "body": default_body,
     }
 
+    preview_url = util.as_url(
+        post.preview.announce_preview, project_name=release.project.name, 
version_name=release.version
+    )
+
     form.render_block(
         page,
         model_cls=shared.announce.AnnounceForm,
@@ -149,7 +154,9 @@ async def _render_page(
         wider_widgets=True,
     )
 
-    page.append(_render_javascript(release, download_path_description))
+    # TODO: Would be better if we could add data-preview-url to the form
+    page.append(htpy.div("#announce-config.d-none", 
data_preview_url=preview_url))
+
     return page.collect()
 
 
@@ -213,6 +220,7 @@ def _render_mailing_list_with_warning(choices: 
list[tuple[str, str]], default_va
 
 def _render_download_path_field(default_value: str, description: str) -> 
htm.Element:
     """Render the download path suffix field with custom help text."""
+    base_text = description.split(" plus this suffix")[0] if " plus this 
suffix" in description else description
     return htm.div[
         htpy.input(
             "#download_path_suffix.form-control",
@@ -220,122 +228,5 @@ def _render_download_path_field(default_value: str, 
description: str) -> htm.Ele
             name="download_path_suffix",
             value=default_value,
         ),
-        htm.div(".form-text.text-muted.mt-2")[description],
+        htpy.div(".form-text.text-muted.mt-2", 
data_base_text=base_text)[description],
     ]
-
-
-def _render_javascript(release: sql.Release, download_path_description: str) 
-> htm.Element:
-    """Render the JavaScript for email preview and path validation."""
-    preview_url = util.as_url(
-        post.preview.announce_preview, project_name=release.project.name, 
version_name=release.version
-    )
-    base_text = (
-        download_path_description.split(" plus this suffix")[0]
-        if " plus this suffix" in download_path_description
-        else download_path_description
-    )
-
-    js_code = f"""
-        document.addEventListener("DOMContentLoaded", () => {{
-            let debounceTimeout;
-            const debounceDelay = 500;
-
-            const bodyTextarea = document.getElementById("body");
-            const textPreviewContent = 
document.getElementById("announce-body-preview-content");
-            const announceForm = document.querySelector("form.atr-canary");
-
-            if (!bodyTextarea || !textPreviewContent || !announceForm) {{
-                console.error("Required elements for announce preview not 
found. Exiting.");
-                return;
-            }}
-
-            const previewUrl = "{preview_url}";
-            const csrfTokenInput = 
announceForm.querySelector('input[name="csrf_token"]');
-
-            if (!previewUrl || !csrfTokenInput) {{
-                console.error("Required data attributes or CSRF token not 
found for announce preview.");
-                return;
-            }}
-            const csrfToken = csrfTokenInput.value;
-
-            function fetchAndUpdateAnnouncePreview() {{
-                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 => {{
-                        textPreviewContent.textContent = previewText;
-                    }})
-                    .catch(error => {{
-                        console.error("Error fetching email preview:", error);
-                        textPreviewContent.textContent = `Error loading 
preview:\\n${{error.message}}`;
-                    }});
-            }}
-
-            bodyTextarea.addEventListener("input", () => {{
-                clearTimeout(debounceTimeout);
-                debounceTimeout = setTimeout(fetchAndUpdateAnnouncePreview, 
debounceDelay);
-            }});
-
-            fetchAndUpdateAnnouncePreview();
-
-            {render.copy_javascript()}
-
-            const pathInput = document.getElementById("download_path_suffix");
-            const pathHelpText = pathInput ? 
pathInput.parentElement.querySelector(".form-text") : null;
-
-            if (pathInput && pathHelpText) {{
-                const baseText = "{base_text}";
-                let pathDebounce;
-
-                function updatePathHelpText() {{
-                    let suffix = pathInput.value;
-                    if (suffix.includes("..") || suffix.includes("//")) {{
-                        pathHelpText.textContent = "Download path suffix must 
not contain .. or //";
-                        return;
-                    }}
-                    if (suffix.startsWith("./")) {{
-                        suffix = suffix.substring(1);
-                    }} else if (suffix === ".") {{
-                        suffix = "/";
-                    }}
-                    if (!suffix.startsWith("/")) {{
-                        suffix = "/" + suffix;
-                    }}
-                    if (!suffix.endsWith("/")) {{
-                        suffix = suffix + "/";
-                    }}
-                    if (suffix.includes("/.")) {{
-                        pathHelpText.textContent = "Download path suffix must 
not contain /.";
-                        return;
-                    }}
-                    pathHelpText.textContent = baseText + suffix;
-                }}
-
-                pathInput.addEventListener("input", () => {{
-                    clearTimeout(pathDebounce);
-                    pathDebounce = setTimeout(updatePathHelpText, 10);
-                }});
-                updatePathHelpText();
-            }}
-        }});
-    """
-
-    return htpy.script[markupsafe.Markup(js_code)]
diff --git a/atr/get/ignores.py b/atr/get/ignores.py
index 91c29bf..a6575e0 100644
--- a/atr/get/ignores.py
+++ b/atr/get/ignores.py
@@ -15,10 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-from typing import Final
-
-import markupsafe
-
 import atr.blueprints.get as get
 import atr.form as form
 import atr.htm as htm
@@ -30,23 +26,6 @@ import atr.template as template
 import atr.util as util
 import atr.web as web
 
-# TODO: Port to TypeScript and move to static files
-_UPDATE_IGNORE_FORM: Final[str] = """
-document.querySelectorAll("table.page-details 
input.form-control").forEach(function (input) {
-    var row = input.closest("tr");
-    var updateBtn = row.querySelector("button.btn-primary");
-    function check() {
-        if (input.value !== input.dataset.value) {
-            updateBtn.classList.remove("disabled");
-        } else {
-            updateBtn.classList.add("disabled");
-        }
-    }
-    input.addEventListener("input", check);
-    check();
-});
-"""
-
 
 @get.committer("/ignores/<committee_name>")
 async def ignores(session: web.Committer, committee_name: str) -> str | 
web.WerkzeugResponse:
@@ -59,10 +38,9 @@ async def ignores(session: web.Committer, committee_name: 
str) -> str | web.Werk
         htm.p[f"Manage ignored checks for committee {committee_name}."],
         _add_ignore(committee_name),
         _existing_ignores(ignores),
-        _script_dom_loaded(_UPDATE_IGNORE_FORM),
     ]
 
-    return await template.blank("Ignored checks", content)
+    return await template.blank("Ignored checks", content, 
javascripts=["ignore-form-change"])
 
 
 def _add_ignore(committee_name: str) -> htm.Element:
@@ -133,12 +111,3 @@ def _existing_ignores(ignores: 
list[sql.CheckResultIgnore]) -> htm.Element:
         htm.h2["Existing ignores"],
         [_check_result_ignore_card(cri) for cri in ignores] or htm.p["No 
ignores found."],
     ]
-
-
-def _script_dom_loaded(text: str) -> htm.Element:
-    script_text = markupsafe.Markup(f"""
-document.addEventListener("DOMContentLoaded", function () {{
-{text}
-}});
-""")
-    return htm.script[script_text]
diff --git a/atr/static/js/announce-preview.js 
b/atr/static/js/announce-preview.js
new file mode 100644
index 0000000..691ef95
--- /dev/null
+++ b/atr/static/js/announce-preview.js
@@ -0,0 +1,118 @@
+document.addEventListener("DOMContentLoaded", () => {
+    let debounceTimeout;
+    const debounceDelay = 500;
+
+    const bodyTextarea = document.getElementById("body");
+    const textPreviewContent = 
document.getElementById("announce-body-preview-content");
+    const announceForm = document.querySelector("form.atr-canary");
+    const configElement = document.getElementById("announce-config");
+
+    if (!bodyTextarea || !textPreviewContent || !announceForm) {
+        console.error("Required elements for announce preview not found. 
Exiting.");
+        return;
+    }
+
+    const previewUrl = configElement ? configElement.dataset.previewUrl : null;
+    const csrfTokenInput = 
announceForm.querySelector('input[name="csrf_token"]');
+
+    if (!previewUrl || !csrfTokenInput) {
+        console.error("Required data attributes or CSRF token not found for 
announce preview.");
+        return;
+    }
+    const csrfToken = csrfTokenInput.value;
+
+    function fetchAndUpdateAnnouncePreview() {
+        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 => {
+                textPreviewContent.textContent = previewText;
+            })
+            .catch(error => {
+                console.error("Error fetching email preview:", error);
+                textPreviewContent.textContent = `Error loading 
preview:\n${error.message}`;
+            });
+    }
+
+    bodyTextarea.addEventListener("input", () => {
+        clearTimeout(debounceTimeout);
+        debounceTimeout = setTimeout(fetchAndUpdateAnnouncePreview, 
debounceDelay);
+    });
+
+    fetchAndUpdateAnnouncePreview();
+
+    // Copy variable button functionality
+    document.querySelectorAll(".copy-var-btn").forEach(btn => {
+        btn.addEventListener("click", () => {
+            const variable = btn.dataset.variable;
+            navigator.clipboard.writeText(variable).then(() => {
+                const originalText = btn.textContent;
+                btn.textContent = "Copied!";
+                btn.classList.remove("btn-outline-secondary");
+                btn.classList.add("btn-success");
+                setTimeout(() => {
+                    btn.textContent = originalText;
+                    btn.classList.remove("btn-success");
+                    btn.classList.add("btn-outline-secondary");
+                }, 1500);
+            });
+        });
+    });
+
+    // Download path suffix validation
+    const pathInput = document.getElementById("download_path_suffix");
+    const pathHelpText = pathInput ? 
pathInput.parentElement.querySelector(".form-text") : null;
+
+    if (pathInput && pathHelpText) {
+        const baseText = pathHelpText.dataset.baseText || "";
+        let pathDebounce;
+
+        function updatePathHelpText() {
+            let suffix = pathInput.value;
+            if (suffix.includes("..") || suffix.includes("//")) {
+                pathHelpText.textContent = "Download path suffix must not 
contain .. or //";
+                return;
+            }
+            if (suffix.startsWith("./")) {
+                suffix = suffix.substring(1);
+            } else if (suffix === ".") {
+                suffix = "/";
+            }
+            if (!suffix.startsWith("/")) {
+                suffix = "/" + suffix;
+            }
+            if (!suffix.endsWith("/")) {
+                suffix = suffix + "/";
+            }
+            if (suffix.includes("/.")) {
+                pathHelpText.textContent = "Download path suffix must not 
contain /.";
+                return;
+            }
+            pathHelpText.textContent = baseText + suffix;
+        }
+
+        pathInput.addEventListener("input", () => {
+            clearTimeout(pathDebounce);
+            pathDebounce = setTimeout(updatePathHelpText, 10);
+        });
+        updatePathHelpText();
+    }
+});
diff --git a/atr/static/js/ignore-form-change.js 
b/atr/static/js/ignore-form-change.js
new file mode 100644
index 0000000..a852346
--- /dev/null
+++ b/atr/static/js/ignore-form-change.js
@@ -0,0 +1,15 @@
+document.addEventListener("DOMContentLoaded", function () {
+    document.querySelectorAll("table.page-details 
input.form-control").forEach(function (input) {
+        var row = input.closest("tr");
+        var updateBtn = row.querySelector("button.btn-primary");
+        function check() {
+            if (input.value !== input.dataset.value) {
+                updateBtn.classList.remove("disabled");
+            } else {
+                updateBtn.classList.add("disabled");
+            }
+        }
+        input.addEventListener("input", check);
+        check();
+    });
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to