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
The following commit(s) were added to refs/heads/main by this push:
new a88f971 Group JS files by origin
a88f971 is described below
commit a88f971d0684a91453717dc6f3fba44aba367525
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Dec 10 19:38:12 2025 +0000
Group JS files by origin
---
.pre-commit-config.yaml | 2 +
.pre-commit-heavy.yaml | 6 +-
Makefile | 2 +-
atr/admin/templates/update-keys.html | 2 +-
atr/admin/templates/update-projects.html | 2 +-
atr/get/finish.py | 2 +-
atr/get/tokens.py | 2 +-
atr/static/js/admin-update-form.js | 55 -------
atr/static/js/announce-preview.js | 100 ------------
atr/static/js/clipboard-copy.js | 32 ----
atr/static/js/committee-directory.js | 146 -----------------
atr/static/js/copy-variable.js | 18 ---
atr/static/js/create-a-jwt.js | 33 ----
atr/static/js/create-a-jwt.js.map | 1 -
atr/static/js/finish-selected-move.js.map | 1 -
atr/static/js/ignore-form-change.js | 15 --
atr/static/js/keys-add-toggle.js | 22 ---
atr/static/js/{ => min}/bootstrap.bundle.min.js | 0
.../js/{ => min}/bootstrap.bundle.min.js.map | 0
atr/static/js/ongoing-tasks-poll.js | 106 -------------
atr/static/js/projects-add-form.js | 14 --
atr/static/js/projects-directory.js | 72 ---------
atr/static/js/report-results.js | 100 ------------
atr/static/js/src/admin-update-form.js | 59 +++++++
atr/static/js/src/announce-preview.js | 107 +++++++++++++
atr/static/js/src/clipboard-copy.js | 34 ++++
atr/static/js/src/committee-directory.js | 166 +++++++++++++++++++
atr/static/js/src/copy-variable.js | 18 +++
atr/static/js/src/ignore-form-change.js | 17 ++
atr/static/js/src/keys-add-toggle.js | 26 +++
atr/static/js/src/ongoing-tasks-poll.js | 119 ++++++++++++++
atr/static/js/src/projects-add-form.js | 20 +++
atr/static/js/src/projects-directory.js | 74 +++++++++
atr/static/js/src/report-results.js | 105 ++++++++++++
atr/static/js/src/vote-preview.js | 74 +++++++++
atr/static/js/ts/create-a-jwt.js | 23 +++
atr/static/js/ts/create-a-jwt.js.map | 1 +
atr/static/js/{ => ts}/finish-selected-move.js | 176 ++++++++++-----------
atr/static/js/ts/finish-selected-move.js.map | 1 +
atr/static/js/vote-preview.js | 70 --------
atr/template.py | 6 +-
atr/templates/check-selected.html | 2 +-
atr/templates/committee-directory.html | 2 +-
atr/templates/layouts/base.html | 2 +-
atr/templates/projects.html | 2 +-
atr/templates/report-selected-path.html | 2 +-
bootstrap/make.sh | 4 +-
tsconfig.json | 6 +-
48 files changed, 948 insertions(+), 901 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fc00d59..4b3ddcc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,10 +20,12 @@ repos:
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
+ exclude: ^atr/static/js/(ts|min)/
- id: fix-byte-order-marker
- id: forbid-submodules
- id: mixed-line-ending
- id: trailing-whitespace
+ exclude: ^atr/static/js/(ts|min)/
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
diff --git a/.pre-commit-heavy.yaml b/.pre-commit-heavy.yaml
index 5d0ad8b..5cf9eac 100644
--- a/.pre-commit-heavy.yaml
+++ b/.pre-commit-heavy.yaml
@@ -7,16 +7,14 @@ repos:
entry: biome check --write --files-ignore-unknown=true
--no-errors-on-unmatched --colors=off
language: system
types: [javascript]
- files: ^atr/static/js/.*\.js$
- exclude: (.*\.min\.js$|.*\.ts\.js$)
+ files: ^atr/static/js/src/.*\.js$
- repo: https://github.com/oxc-project/mirrors-oxlint
rev: v1.32.0
hooks:
- id: oxlint
name: oxlint JS linter
types: [javascript]
- files: "atr/static/js/.*\\.js$"
- exclude: (.*\.min\.js$|.*\.ts\.js$)
+ files: ^atr/static/js/src/.*\.js$
args:
- --deny=correctness
- --deny=perf
diff --git a/Makefile b/Makefile
index 3895f78..b57090b 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ build-playwright:
docker build -t atr-playwright -f tests/Dockerfile.playwright playwright
build-ts:
- tsc -p tsconfig.json
+ tsgo --project ./tsconfig.json
build-ubuntu:
scripts/build Dockerfile.ubuntu $(IMAGE)
diff --git a/atr/admin/templates/update-keys.html
b/atr/admin/templates/update-keys.html
index 8319ba7..78f3c5f 100644
--- a/atr/admin/templates/update-keys.html
+++ b/atr/admin/templates/update-keys.html
@@ -91,5 +91,5 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/admin-update-form.js') }}"></script>
+ <script src="{{ static_url('js/src/admin-update-form.js') }}"></script>
{% endblock javascripts %}
diff --git a/atr/admin/templates/update-projects.html
b/atr/admin/templates/update-projects.html
index f582e9c..033e4d6 100644
--- a/atr/admin/templates/update-projects.html
+++ b/atr/admin/templates/update-projects.html
@@ -91,5 +91,5 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/admin-update-form.js') }}"></script>
+ <script src="{{ static_url('js/src/admin-update-form.js') }}"></script>
{% endblock javascripts %}
diff --git a/atr/get/finish.py b/atr/get/finish.py
index 63f0940..63731a8 100644
--- a/atr/get/finish.py
+++ b/atr/get/finish.py
@@ -330,7 +330,7 @@ async def _render_page(
page.append(
htpy.script(
id="main-script-data",
- src=util.static_url("js/finish-selected-move.js"),
+ src=util.static_url("js/ts/finish-selected-move.js"),
**{"data-csrf-token": csrf_token},
)[""]
)
diff --git a/atr/get/tokens.py b/atr/get/tokens.py
index f9a11af..0f64732 100644
--- a/atr/get/tokens.py
+++ b/atr/get/tokens.py
@@ -82,7 +82,7 @@ async def tokens(session: web.Committer) -> str:
title="Tokens",
description="Manage your PATs and JWTs.",
content=page.collect(),
- javascripts=["create-a-jwt"],
+ typescripts=["create-a-jwt"],
)
diff --git a/atr/static/js/admin-update-form.js
b/atr/static/js/admin-update-form.js
deleted file mode 100644
index f73ff88..0000000
--- a/atr/static/js/admin-update-form.js
+++ /dev/null
@@ -1,55 +0,0 @@
-document.addEventListener("DOMContentLoaded", () => {
- const form = document.querySelector("form");
- const button = form.querySelector("button[type='submit']");
-
- form.addEventListener("submit", async (e) => {
- e.preventDefault();
-
- button.disabled = true;
- document.body.style.cursor = "wait";
-
- const statusElement = document.getElementById("status");
- while (statusElement.firstChild) {
- statusElement.firstChild.remove();
- }
-
- const csrfToken =
document.querySelector("input[name='csrf_token']").value;
-
- try {
- const response = await fetch(window.location.href, {
- method: "POST",
- headers: {
- "X-CSRFToken": csrfToken
- }
- });
-
- if (!response.ok) {
- addStatusMessage(statusElement, "Could not make network
request", "error");
- return;
- }
-
- const data = await response.json();
- addStatusMessage(statusElement, data.message, data.category);
- } catch (error) {
- addStatusMessage(statusElement, error, "error");
- } finally {
- button.disabled = false;
- document.body.style.cursor = "default";
- }
- });
-});
-
-function addStatusMessage(parentElement, message, category) {
- const divElement = document.createElement("div");
- divElement.classList.add("page-status-message");
- divElement.classList.add(category);
- if (category === "error") {
- const prefixElement = document.createElement("strong");
- const textElement = document.createTextNode("Error: ");
- prefixElement.appendChild(textElement);
- divElement.appendChild(prefixElement);
- }
- const textNode = document.createTextNode(message);
- divElement.appendChild(textNode);
- parentElement.appendChild(divElement);
-}
diff --git a/atr/static/js/announce-preview.js
b/atr/static/js/announce-preview.js
deleted file mode 100644
index 1833864..0000000
--- a/atr/static/js/announce-preview.js
+++ /dev/null
@@ -1,100 +0,0 @@
-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();
-
- // 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/clipboard-copy.js b/atr/static/js/clipboard-copy.js
deleted file mode 100644
index b15eccc..0000000
--- a/atr/static/js/clipboard-copy.js
+++ /dev/null
@@ -1,32 +0,0 @@
-document.addEventListener("DOMContentLoaded", function () {
- const copyButtons = document.querySelectorAll(".atr-copy-btn");
-
- copyButtons.forEach(button => {
- button.addEventListener("click", function () {
- const targetId = this.getAttribute("data-clipboard-target");
- const targetElement = document.querySelector(targetId);
-
- if (targetElement) {
- const textToCopy = targetElement.textContent;
-
- navigator.clipboard.writeText(textToCopy)
- .then(() => {
- const originalText = this.innerHTML;
- this.innerHTML = '<i class="bi bi-check"></i> Copied!';
-
- setTimeout(() => {
- this.innerHTML = originalText;
- }, 2000);
- })
- .catch(err => {
- console.error("Failed to copy: ", err);
- this.innerHTML = '<i class="bi
bi-exclamation-triangle"></i> Failed!';
-
- setTimeout(() => {
- this.innerHTML = '<i class="bi bi-clipboard"></i>
Copy';
- }, 2000);
- });
- }
- });
- });
-});
diff --git a/atr/static/js/committee-directory.js
b/atr/static/js/committee-directory.js
deleted file mode 100644
index 3e02fbf..0000000
--- a/atr/static/js/committee-directory.js
+++ /dev/null
@@ -1,146 +0,0 @@
-let allCommitteeCards = [];
-
-function filterCommitteesByText() {
- const projectFilter = document.getElementById("project-filter").value;
- const cards = allCommitteeCards;
- let visibleCount = 0;
-
- if (participantButton && participantButton.dataset.showing ===
"participant") {
- participantButton.dataset.showing = "all";
- participantButton.textContent = "Show my committees";
- participantButton.setAttribute("aria-pressed", "false");
- }
-
- for (let card of cards) {
- const nameElement = card.querySelector(".card-title");
- const name = nameElement.textContent.trim();
- if (!projectFilter) {
- card.parentElement.hidden = false;
- visibleCount++;
- } else {
- let regex;
- try {
- regex = new RegExp(projectFilter, "i");
- } catch (e) {
- const escapedFilter =
projectFilter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- regex = new RegExp(escapedFilter, "i");
- }
- card.parentElement.hidden = !name.match(regex);
- if (!card.parentElement.hidden) {
- visibleCount++;
- }
- }
- }
- document.getElementById("committee-count").textContent = visibleCount;
-}
-
-document.getElementById("filter-button").addEventListener("click",
filterCommitteesByText);
-document.getElementById("project-filter").addEventListener("keydown",
function(event) {
- if (event.key === "Enter") {
- filterCommitteesByText();
- event.preventDefault();
- }
-});
-
-const participantButton = document.getElementById("filter-participant-button");
-if (participantButton) {
- participantButton.addEventListener("click", function() {
- const showing = this.dataset.showing;
- const cards = allCommitteeCards;
- let visibleCount = 0;
-
- if (showing === "all") {
- cards.forEach(card => {
- const isParticipant = card.dataset.isParticipant === "true";
- card.parentElement.hidden = !isParticipant;
- if (!card.parentElement.hidden) {
- visibleCount++;
- }
- });
- this.textContent = "Show all committees";
- this.dataset.showing = "participant";
- this.setAttribute("aria-pressed", "true");
- } else {
- cards.forEach(card => {
- card.parentElement.hidden = false;
- visibleCount++;
- });
- this.textContent = "Show my committees";
- this.dataset.showing = "all";
- this.setAttribute("aria-pressed", "false");
- }
- document.getElementById("project-filter").value = "";
- document.getElementById("committee-count").textContent = visibleCount;
- });
-}
-
-document.addEventListener("DOMContentLoaded", function() {
- // Hide images that fail to load
- document.querySelectorAll(".page-logo").forEach(function(img) {
- img.addEventListener("error", function() {
- this.style.display = "none";
- });
- });
-
- allCommitteeCards =
Array.from(document.querySelectorAll(".page-project-card"));
- const cards = allCommitteeCards;
- const committeeCountSpan = document.getElementById("committee-count");
- let initialVisibleCount = 0;
- const initialShowingMode = participantButton ?
participantButton.dataset.showing : "all";
-
- if (participantButton) {
- if (initialShowingMode === "participant") {
- participantButton.setAttribute("aria-pressed", "true");
- } else {
- participantButton.setAttribute("aria-pressed", "false");
- }
- }
-
- if (initialShowingMode === "participant") {
- cards.forEach(card => {
- const isParticipant = card.dataset.isParticipant === "true";
- card.parentElement.hidden = !isParticipant;
- if (!card.parentElement.hidden) {
- initialVisibleCount++;
- }
- });
- } else {
- cards.forEach(card => {
- card.parentElement.hidden = false;
- initialVisibleCount++;
- });
- }
- committeeCountSpan.textContent = initialVisibleCount;
-
- // Add a click listener to project subcards to handle navigation
- // TODO: Improve accessibility
-
document.querySelectorAll(".page-project-subcard").forEach(function(subcard) {
- subcard.addEventListener("click", function(event) {
- if (this.dataset.projectUrl) {
- window.location.href = this.dataset.projectUrl;
- }
- });
- });
-
- // Add a click listener for toggling project visibility within each
committee
-
document.querySelectorAll(".page-toggle-committee-projects").forEach(function(button)
{
- button.addEventListener("click", function() {
- const projectListContainer =
this.closest(".page-project-list-container");
- if (projectListContainer) {
- const extraProjects =
projectListContainer.querySelectorAll(".page-project-extra");
- extraProjects.forEach(function(proj) {
- proj.classList.toggle("d-none");
- });
-
- const isExpanded = this.getAttribute("aria-expanded") ===
"true";
- if (isExpanded) {
- this.textContent = this.dataset.textShow;
- this.setAttribute("aria-expanded", "false");
- } else {
- this.textContent = this.dataset.textHide;
- this.setAttribute("aria-expanded", "true");
- }
- }
- });
- });
-});
diff --git a/atr/static/js/copy-variable.js b/atr/static/js/copy-variable.js
deleted file mode 100644
index 2b81d51..0000000
--- a/atr/static/js/copy-variable.js
+++ /dev/null
@@ -1,18 +0,0 @@
-document.addEventListener("DOMContentLoaded", function () {
- 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);
- });
- });
- });
-});
diff --git a/atr/static/js/create-a-jwt.js b/atr/static/js/create-a-jwt.js
deleted file mode 100644
index e9a56e0..0000000
--- a/atr/static/js/create-a-jwt.js
+++ /dev/null
@@ -1,33 +0,0 @@
-"use strict";
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P,
generator) {
- function adopt(value) { return value instanceof P ? value : new P(function
(resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch
(e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); }
catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) :
adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-document.addEventListener("DOMContentLoaded", () => {
- const form = document.getElementById("issue-jwt-form");
- const output = document.getElementById("jwt-output");
- if (!form || !output) {
- return;
- }
- form.addEventListener("submit", (e) => __awaiter(void 0, void 0, void 0,
function* () {
- e.preventDefault();
- const resp = yield fetch(form.action, {
- method: "POST",
- body: new FormData(form),
- });
- if (resp.ok) {
- const token = yield resp.text();
- output.classList.remove("d-none");
- output.textContent = token;
- }
- else {
- alert("Failed to fetch JWT");
- }
- }));
-});
-//# sourceMappingURL=create-a-jwt.js.map
diff --git a/atr/static/js/create-a-jwt.js.map
b/atr/static/js/create-a-jwt.js.map
deleted file mode 100644
index 5288535..0000000
--- a/atr/static/js/create-a-jwt.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"create-a-jwt.js","sourceRoot":"","sources":["../ts/create-a-jwt.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAS,EAAE;IACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAA2B,CAAC;IACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAErD,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAO,CAAQ,EAAiB,EAAE;QAChE,CAAC,CAAC,cAAc,EAAE,CAAC;QAEnB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAA
[...]
diff --git a/atr/static/js/finish-selected-move.js.map
b/atr/static/js/finish-selected-move.js.map
deleted file mode 100644
index 4c7c9c4..0000000
--- a/atr/static/js/finish-selected-move.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"finish-selected-move.js","sourceRoot":"","sources":["../ts/finish-selected-move.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;AAEb,IAAK,QAGJ;AAHD,WAAK,QAAQ;IACT,yBAAa,CAAA;IACb,uBAAW,CAAA;AACf,CAAC,EAHI,QAAQ,KAAR,QAAQ,QAGZ;AAED,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACrB,iBAAiB,EAAE,qBAAqB;IACxC,wBAAwB,EAAE,6BAA6B;IACvD,OAAO,EAAE,UAAU;IACnB,cAAc,EAAE,kBAAkB;IAClC,eAAe,EAAE,oBAAoB;IACrC,gBAAgB,EAAE,qBAAqB;IACvC,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,WAAW;IACrB,UAAU,EAAE,
[...]
diff --git a/atr/static/js/ignore-form-change.js
b/atr/static/js/ignore-form-change.js
deleted file mode 100644
index a852346..0000000
--- a/atr/static/js/ignore-form-change.js
+++ /dev/null
@@ -1,15 +0,0 @@
-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();
- });
-});
diff --git a/atr/static/js/keys-add-toggle.js b/atr/static/js/keys-add-toggle.js
deleted file mode 100644
index 8ac378d..0000000
--- a/atr/static/js/keys-add-toggle.js
+++ /dev/null
@@ -1,22 +0,0 @@
-document.addEventListener("DOMContentLoaded", function() {
- const checkboxes =
document.querySelectorAll("input[name='selected_committees']");
- if (checkboxes.length === 0) return;
-
- const firstCheckbox = checkboxes[0];
- const container = firstCheckbox.closest(".col-sm-8");
- if (!container) return;
-
- const button = document.createElement("button");
- button.id = "toggleCommitteesBtn";
- button.type = "button";
- button.className = "btn btn-outline-secondary btn-sm mt-2";
- button.textContent = "Select all committees";
-
- button.addEventListener("click", function() {
- const allChecked = Array.from(checkboxes).every(cb => cb.checked);
- checkboxes.forEach(cb => cb.checked = !allChecked);
- button.textContent = allChecked ? "Select all committees" : "Deselect
all committees";
- });
-
- container.appendChild(button);
-});
diff --git a/atr/static/js/bootstrap.bundle.min.js
b/atr/static/js/min/bootstrap.bundle.min.js
similarity index 100%
rename from atr/static/js/bootstrap.bundle.min.js
rename to atr/static/js/min/bootstrap.bundle.min.js
diff --git a/atr/static/js/bootstrap.bundle.min.js.map
b/atr/static/js/min/bootstrap.bundle.min.js.map
similarity index 100%
rename from atr/static/js/bootstrap.bundle.min.js.map
rename to atr/static/js/min/bootstrap.bundle.min.js.map
diff --git a/atr/static/js/ongoing-tasks-poll.js
b/atr/static/js/ongoing-tasks-poll.js
deleted file mode 100644
index 33c08dc..0000000
--- a/atr/static/js/ongoing-tasks-poll.js
+++ /dev/null
@@ -1,106 +0,0 @@
-(function() {
- // Handle More and Less toggle buttons for collapse sections
-
document.querySelectorAll(".page-collapse-toggle").forEach(function(button) {
- button.addEventListener("click", function() {
- this.textContent = this.textContent.trim() === "More" ? "Less" :
"More";
- });
- });
-
- const banner = document.getElementById("ongoing-tasks-banner");
- if (!banner) return;
-
- const apiUrl = banner.dataset.apiUrl;
- if (!apiUrl) return;
-
- const countSpan = document.getElementById("ongoing-tasks-count");
- const textSpan = document.getElementById("ongoing-tasks-text");
- const voteButton = document.getElementById("start-vote-button");
- const progress = document.getElementById("poll-progress");
- const pollInterval = 3000;
-
- let currentCount = parseInt(countSpan?.textContent || "0", 10);
- if (currentCount === 0) return;
-
- function restartProgress() {
- if (!progress) return;
- progress.style.animation = "none";
- progress.offsetHeight;
- progress.style.animation = `poll-grow ${pollInterval}ms linear
forwards`;
- }
-
- function updateBanner(count) {
- if (!countSpan || !textSpan) return;
-
- currentCount = count;
- countSpan.textContent = count;
-
- const taskWord = count === 1 ? "task" : "tasks";
- const isAre = count === 1 ? "is" : "are";
- // TODO: Migrate away from setting innerHTML
- textSpan.innerHTML = `There ${isAre} currently <strong
id="ongoing-tasks-count">${count}</strong> background verification ${taskWord}
running for the latest revision. Results shown below may be incomplete or
outdated until the tasks finish.`;
-
- if (count === 0) {
- // Banner always exists, but we hide it
- banner.classList.add("d-none");
- enableVoteButton();
- }
- }
-
- function enableVoteButton() {
- if (!voteButton) return;
- if (!voteButton.classList.contains("disabled")) return;
-
- const voteHref = voteButton.dataset.voteHref ||
voteButton.getAttribute("href");
- if (!voteHref || voteHref === "#") return;
-
- voteButton.classList.remove("disabled");
- voteButton.removeAttribute("aria-disabled");
- voteButton.removeAttribute("tabindex");
- voteButton.removeAttribute("role");
- voteButton.setAttribute("href", voteHref);
- voteButton.setAttribute("title", "Start a vote on this draft");
- }
-
- function pollOngoingTasks() {
- if (currentCount === 0) return;
-
- if (progress) {
- progress.style.animation = "none";
- progress.style.width = "100%";
- progress.classList.remove("bg-warning");
- progress.classList.add("bg-info", "progress-bar-striped",
"progress-bar-animated");
- }
- fetch(apiUrl)
- .then(response => {
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- })
- .then(data => {
- if (progress) {
- progress.classList.remove("bg-info",
"progress-bar-striped", "progress-bar-animated");
- progress.classList.add("bg-warning");
- }
- const newCount = data.ongoing || 0;
- if (newCount !== currentCount) {
- updateBanner(newCount);
- }
- if (newCount > 0) {
- restartProgress();
- setTimeout(pollOngoingTasks, pollInterval);
- }
- })
- .catch(error => {
- console.error("Error polling ongoing tasks:", error);
- if (progress) {
- progress.classList.remove("bg-info",
"progress-bar-striped", "progress-bar-animated");
- progress.classList.add("bg-warning");
- }
- restartProgress();
- // Double the interval when there's an error
- setTimeout(pollOngoingTasks, pollInterval * 2);
- });
- }
-
- restartProgress();
- setTimeout(pollOngoingTasks, pollInterval);
-})();
diff --git a/atr/static/js/projects-add-form.js
b/atr/static/js/projects-add-form.js
deleted file mode 100644
index aff9e77..0000000
--- a/atr/static/js/projects-add-form.js
+++ /dev/null
@@ -1,14 +0,0 @@
-document.addEventListener("DOMContentLoaded", function() {
- const configElement = document.getElementById("projects-add-config");
- if (!configElement) return;
-
- const committeeDisplayName = configElement.dataset.committeeDisplayName;
- const committeeName = configElement.dataset.committeeName;
- if (!committeeDisplayName || !committeeName) return;
-
- const formTexts = document.querySelectorAll(".form-text, .text-muted");
- formTexts.forEach(function(element) {
- element.textContent = element.textContent.replace(/Example/g,
committeeDisplayName);
- element.textContent = element.textContent.replace(/example/g,
committeeName.toLowerCase());
- });
-});
diff --git a/atr/static/js/projects-directory.js
b/atr/static/js/projects-directory.js
deleted file mode 100644
index 705cfcd..0000000
--- a/atr/static/js/projects-directory.js
+++ /dev/null
@@ -1,72 +0,0 @@
-function filter() {
- const projectFilter = document.getElementById("project-filter").value;
- const cards = document.querySelectorAll(".page-project-card");
- let visibleCount = 0;
- for (let card of cards) {
- const nameElement = card.querySelector(".card-title");
- const name = nameElement.innerHTML;
- if (!projectFilter) {
- card.parentElement.hidden = false;
- visibleCount++;
- } else {
- card.parentElement.hidden = !name.match(new RegExp(projectFilter,
"i"));
- if (!card.parentElement.hidden) {
- visibleCount++;
- }
- }
- }
- document.getElementById("project-count").textContent = visibleCount;
-}
-
-// Add event listeners
-document.getElementById("filter-button").addEventListener("click", filter);
-document.getElementById("project-filter").addEventListener("keydown",
function(event) {
- if (event.key === "Enter") {
- filter();
- event.preventDefault();
- }
-});
-
-// Add click handlers for project cards
-document.querySelectorAll(".page-project-card").forEach(function(card) {
- card.addEventListener("click", function(event) {
- // Prevent card navigation if click is inside a form
- if (event.target.closest("form")) {
- return;
- }
- window.location.href = this.getAttribute("data-project-url");
- });
-});
-
-// Participant filter logic
-const participantButton = document.getElementById("filter-participant-button");
-participantButton.addEventListener("click", function() {
- const showing = this.dataset.showing;
- const cards = document.querySelectorAll(".page-project-card");
- let visibleCount = 0;
-
- if (showing === "all") {
- // Switch to showing only participant projects
- cards.forEach(card => {
- const isParticipant = card.dataset.isParticipant === "true";
- card.parentElement.hidden = !isParticipant;
- if (!card.parentElement.hidden) {
- visibleCount++;
- }
- });
- this.textContent = "Show all projects";
- this.dataset.showing = "participant";
- } else {
- // Switch to showing all projects
- cards.forEach(card => {
- card.parentElement.hidden = false;
- visibleCount++;
- });
- this.textContent = "Show my projects";
- this.dataset.showing = "all";
- }
- // Reset text filter when toggling participant view
- document.getElementById("project-filter").value = "";
- // Update count
- document.getElementById("project-count").textContent = visibleCount;
-});
diff --git a/atr/static/js/report-results.js b/atr/static/js/report-results.js
deleted file mode 100644
index 34dc44e..0000000
--- a/atr/static/js/report-results.js
+++ /dev/null
@@ -1,100 +0,0 @@
-function toggleAllDetails() {
- const details = document.querySelectorAll("details");
- // Check if any are closed
- const anyClosed = Array.from(details).some(detail => !detail.open);
- // If any are closed, open all
- // Otherwise, close all
- details.forEach(detail => detail.open = anyClosed);
-}
-
-function toggleStatusVisibility(type, status) {
- const btn = document.getElementById(`btn-toggle-${type}-${status}`);
- const targets =
document.querySelectorAll(`.atr-result-${type}.atr-result-status-${status}`);
- if (!targets.length) return;
- let elementsCurrentlyHidden = targets[0].classList.contains("atr-hide");
- targets.forEach(el => {
- if (elementsCurrentlyHidden) {
- el.classList.remove("atr-hide");
- } else {
- el.classList.add("atr-hide");
- }
- });
- const bsSt = (status === "failure" || status === "exception") ? "danger" :
status;
- const cntMatch = btn.textContent.match(/\((\d+)\)/);
- if (!cntMatch) {
- console.error("Button text regex mismatch for:", btn.textContent);
- return;
- }
- const cnt = cntMatch[0];
- const newButtonAction = elementsCurrentlyHidden ? "Hide" : "Show";
- btn.querySelector("span").textContent = newButtonAction;
- if (newButtonAction === "Hide") {
- btn.classList.remove(`btn-outline-${bsSt}`);
- btn.classList.add(`btn-${bsSt}`);
- } else {
- btn.classList.remove(`btn-${bsSt}`);
- btn.classList.add(`btn-outline-${bsSt}`);
- }
- if (type === "member") {
- updateMemberStriping();
- } else if (type === "primary") {
- updatePrimaryStriping();
- }
-}
-
-function restripeVisibleRows(rowSelector, stripeClass) {
- let visibleIdx = 0;
- document.querySelectorAll(rowSelector).forEach(row => {
- row.classList.remove(stripeClass);
- const hidden = row.classList.contains("atr-hide") ||
row.classList.contains("page-member-path-hide");
- if (!hidden) {
- if (visibleIdx % 2 === 0) row.classList.add(stripeClass);
- visibleIdx++;
- }
- });
-}
-
-function updatePrimaryStriping() {
- restripeVisibleRows(".atr-result-primary", "page-member-visible-odd");
-}
-
-function updateMemberStriping() {
- restripeVisibleRows(".atr-result-member", "page-member-visible-odd");
-}
-
-// Toggle status visibility buttons
-document.querySelectorAll(".page-toggle-status").forEach(function(btn) {
- btn.addEventListener("click", function() {
- const type = this.dataset.type;
- const status = this.dataset.status;
- toggleStatusVisibility(type, status);
- });
-});
-
-// Toggle all details button
-const toggleAllBtn = document.getElementById("btn-toggle-all-details");
-if (toggleAllBtn) {
- toggleAllBtn.addEventListener("click", toggleAllDetails);
-}
-
-// Member path filter
-const mpfInput = document.getElementById("member-path-filter");
-if (mpfInput) {
- mpfInput.addEventListener("input", function() {
- const filterText = this.value.toLowerCase();
- document.querySelectorAll(".atr-result-member").forEach(row => {
- const pathCell = row.cells[0];
- let hide = false;
- if (filterText) {
- if (!pathCell.textContent.toLowerCase().includes(filterText)) {
- hide = true;
- }
- }
- row.classList.toggle("page-member-path-hide", hide);
- });
- updateMemberStriping();
- });
-}
-
-updatePrimaryStriping();
-updateMemberStriping();
diff --git a/atr/static/js/src/admin-update-form.js
b/atr/static/js/src/admin-update-form.js
new file mode 100644
index 0000000..e7a9b7c
--- /dev/null
+++ b/atr/static/js/src/admin-update-form.js
@@ -0,0 +1,59 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const form = document.querySelector("form");
+ const button = form.querySelector("button[type='submit']");
+
+ form.addEventListener("submit", async (e) => {
+ e.preventDefault();
+
+ button.disabled = true;
+ document.body.style.cursor = "wait";
+
+ const statusElement = document.getElementById("status");
+ while (statusElement.firstChild) {
+ statusElement.firstChild.remove();
+ }
+
+ const csrfToken =
document.querySelector("input[name='csrf_token']").value;
+
+ try {
+ const response = await fetch(window.location.href, {
+ method: "POST",
+ headers: {
+ "X-CSRFToken": csrfToken,
+ },
+ });
+
+ if (!response.ok) {
+ addStatusMessage(
+ statusElement,
+ "Could not make network request",
+ "error",
+ );
+ return;
+ }
+
+ const data = await response.json();
+ addStatusMessage(statusElement, data.message,
data.category);
+ } catch (error) {
+ addStatusMessage(statusElement, error, "error");
+ } finally {
+ button.disabled = false;
+ document.body.style.cursor = "default";
+ }
+ });
+});
+
+function addStatusMessage(parentElement, message, category) {
+ const divElement = document.createElement("div");
+ divElement.classList.add("page-status-message");
+ divElement.classList.add(category);
+ if (category === "error") {
+ const prefixElement = document.createElement("strong");
+ const textElement = document.createTextNode("Error: ");
+ prefixElement.appendChild(textElement);
+ divElement.appendChild(prefixElement);
+ }
+ const textNode = document.createTextNode(message);
+ divElement.appendChild(textNode);
+ parentElement.appendChild(divElement);
+}
diff --git a/atr/static/js/src/announce-preview.js
b/atr/static/js/src/announce-preview.js
new file mode 100644
index 0000000..ea387cb
--- /dev/null
+++ b/atr/static/js/src/announce-preview.js
@@ -0,0 +1,107 @@
+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();
+
+ // 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/src/clipboard-copy.js
b/atr/static/js/src/clipboard-copy.js
new file mode 100644
index 0000000..4e5c038
--- /dev/null
+++ b/atr/static/js/src/clipboard-copy.js
@@ -0,0 +1,34 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const copyButtons = document.querySelectorAll(".atr-copy-btn");
+
+ copyButtons.forEach((button) => {
+ button.addEventListener("click", function () {
+ const targetId =
this.getAttribute("data-clipboard-target");
+ const targetElement = document.querySelector(targetId);
+
+ if (targetElement) {
+ const textToCopy = targetElement.textContent;
+
+ navigator.clipboard
+ .writeText(textToCopy)
+ .then(() => {
+ const originalText =
this.innerHTML;
+ this.innerHTML = '<i class="bi
bi-check"></i> Copied!';
+
+ setTimeout(() => {
+ this.innerHTML =
originalText;
+ }, 2000);
+ })
+ .catch((err) => {
+ console.error("Failed to copy:
", err);
+ this.innerHTML =
+ '<i class="bi
bi-exclamation-triangle"></i> Failed!';
+
+ setTimeout(() => {
+ this.innerHTML = '<i
class="bi bi-clipboard"></i> Copy';
+ }, 2000);
+ });
+ }
+ });
+ });
+});
diff --git a/atr/static/js/src/committee-directory.js
b/atr/static/js/src/committee-directory.js
new file mode 100644
index 0000000..a8bb9cb
--- /dev/null
+++ b/atr/static/js/src/committee-directory.js
@@ -0,0 +1,166 @@
+let allCommitteeCards = [];
+
+function filterCommitteesByText() {
+ const projectFilter = document.getElementById("project-filter").value;
+ const cards = allCommitteeCards;
+ let visibleCount = 0;
+
+ if (
+ participantButton &&
+ participantButton.dataset.showing === "participant"
+ ) {
+ participantButton.dataset.showing = "all";
+ participantButton.textContent = "Show my committees";
+ participantButton.setAttribute("aria-pressed", "false");
+ }
+
+ for (const card of cards) {
+ const nameElement = card.querySelector(".card-title");
+ const name = nameElement.textContent.trim();
+ if (!projectFilter) {
+ card.parentElement.hidden = false;
+ visibleCount++;
+ } else {
+ let regex;
+ try {
+ regex = new RegExp(projectFilter, "i");
+ } catch (e) {
+ const escapedFilter = projectFilter.replace(
+ /[.*+?^${}()|[\]\\]/g,
+ "\\$&",
+ );
+ regex = new RegExp(escapedFilter, "i");
+ }
+ card.parentElement.hidden = !name.match(regex);
+ if (!card.parentElement.hidden) {
+ visibleCount++;
+ }
+ }
+ }
+ document.getElementById("committee-count").textContent = visibleCount;
+}
+
+document
+ .getElementById("filter-button")
+ .addEventListener("click", filterCommitteesByText);
+document
+ .getElementById("project-filter")
+ .addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ filterCommitteesByText();
+ event.preventDefault();
+ }
+ });
+
+const participantButton = document.getElementById("filter-participant-button");
+if (participantButton) {
+ participantButton.addEventListener("click", function () {
+ const showing = this.dataset.showing;
+ const cards = allCommitteeCards;
+ let visibleCount = 0;
+
+ if (showing === "all") {
+ cards.forEach((card) => {
+ const isParticipant =
card.dataset.isParticipant === "true";
+ card.parentElement.hidden = !isParticipant;
+ if (!card.parentElement.hidden) {
+ visibleCount++;
+ }
+ });
+ this.textContent = "Show all committees";
+ this.dataset.showing = "participant";
+ this.setAttribute("aria-pressed", "true");
+ } else {
+ cards.forEach((card) => {
+ card.parentElement.hidden = false;
+ visibleCount++;
+ });
+ this.textContent = "Show my committees";
+ this.dataset.showing = "all";
+ this.setAttribute("aria-pressed", "false");
+ }
+ document.getElementById("project-filter").value = "";
+ document.getElementById("committee-count").textContent =
visibleCount;
+ });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ // Hide images that fail to load
+ document.querySelectorAll(".page-logo").forEach((img) => {
+ img.addEventListener("error", function () {
+ this.style.display = "none";
+ });
+ });
+
+ allCommitteeCards = Array.from(
+ document.querySelectorAll(".page-project-card"),
+ );
+ const cards = allCommitteeCards;
+ const committeeCountSpan = document.getElementById("committee-count");
+ let initialVisibleCount = 0;
+ const initialShowingMode = participantButton
+ ? participantButton.dataset.showing
+ : "all";
+
+ if (participantButton) {
+ if (initialShowingMode === "participant") {
+ participantButton.setAttribute("aria-pressed", "true");
+ } else {
+ participantButton.setAttribute("aria-pressed", "false");
+ }
+ }
+
+ if (initialShowingMode === "participant") {
+ cards.forEach((card) => {
+ const isParticipant = card.dataset.isParticipant ===
"true";
+ card.parentElement.hidden = !isParticipant;
+ if (!card.parentElement.hidden) {
+ initialVisibleCount++;
+ }
+ });
+ } else {
+ cards.forEach((card) => {
+ card.parentElement.hidden = false;
+ initialVisibleCount++;
+ });
+ }
+ committeeCountSpan.textContent = initialVisibleCount;
+
+ // Add a click listener to project subcards to handle navigation
+ // TODO: Improve accessibility
+ document.querySelectorAll(".page-project-subcard").forEach((subcard) =>
{
+ subcard.addEventListener("click", function (event) {
+ if (this.dataset.projectUrl) {
+ window.location.href = this.dataset.projectUrl;
+ }
+ });
+ });
+
+ // Add a click listener for toggling project visibility within each
committee
+ document
+ .querySelectorAll(".page-toggle-committee-projects")
+ .forEach((button) => {
+ button.addEventListener("click", function () {
+ const projectListContainer = this.closest(
+ ".page-project-list-container",
+ );
+ if (projectListContainer) {
+ const extraProjects =
projectListContainer.querySelectorAll(
+ ".page-project-extra",
+ );
+ extraProjects.forEach((proj) => {
+ proj.classList.toggle("d-none");
+ });
+
+ const isExpanded =
this.getAttribute("aria-expanded") === "true";
+ if (isExpanded) {
+ this.textContent =
this.dataset.textShow;
+
this.setAttribute("aria-expanded", "false");
+ } else {
+ this.textContent =
this.dataset.textHide;
+
this.setAttribute("aria-expanded", "true");
+ }
+ }
+ });
+ });
+});
diff --git a/atr/static/js/src/copy-variable.js
b/atr/static/js/src/copy-variable.js
new file mode 100644
index 0000000..ed2ab68
--- /dev/null
+++ b/atr/static/js/src/copy-variable.js
@@ -0,0 +1,18 @@
+document.addEventListener("DOMContentLoaded", () => {
+ 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);
+ });
+ });
+ });
+});
diff --git a/atr/static/js/src/ignore-form-change.js
b/atr/static/js/src/ignore-form-change.js
new file mode 100644
index 0000000..a61efe7
--- /dev/null
+++ b/atr/static/js/src/ignore-form-change.js
@@ -0,0 +1,17 @@
+document.addEventListener("DOMContentLoaded", () => {
+ document
+ .querySelectorAll("table.page-details input.form-control")
+ .forEach((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();
+ });
+});
diff --git a/atr/static/js/src/keys-add-toggle.js
b/atr/static/js/src/keys-add-toggle.js
new file mode 100644
index 0000000..a0cf505
--- /dev/null
+++ b/atr/static/js/src/keys-add-toggle.js
@@ -0,0 +1,26 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const checkboxes = document.querySelectorAll(
+ "input[name='selected_committees']",
+ );
+ if (checkboxes.length === 0) return;
+
+ const firstCheckbox = checkboxes[0];
+ const container = firstCheckbox.closest(".col-sm-8");
+ if (!container) return;
+
+ const button = document.createElement("button");
+ button.id = "toggleCommitteesBtn";
+ button.type = "button";
+ button.className = "btn btn-outline-secondary btn-sm mt-2";
+ button.textContent = "Select all committees";
+
+ button.addEventListener("click", () => {
+ const allChecked = Array.from(checkboxes).every((cb) =>
cb.checked);
+ checkboxes.forEach((cb) => (cb.checked = !allChecked));
+ button.textContent = allChecked
+ ? "Select all committees"
+ : "Deselect all committees";
+ });
+
+ container.appendChild(button);
+});
diff --git a/atr/static/js/src/ongoing-tasks-poll.js
b/atr/static/js/src/ongoing-tasks-poll.js
new file mode 100644
index 0000000..6cec4ab
--- /dev/null
+++ b/atr/static/js/src/ongoing-tasks-poll.js
@@ -0,0 +1,119 @@
+(() => {
+ // Handle More and Less toggle buttons for collapse sections
+ document.querySelectorAll(".page-collapse-toggle").forEach((button) => {
+ button.addEventListener("click", function () {
+ this.textContent = this.textContent.trim() === "More" ?
"Less" : "More";
+ });
+ });
+
+ const banner = document.getElementById("ongoing-tasks-banner");
+ if (!banner) return;
+
+ const apiUrl = banner.dataset.apiUrl;
+ if (!apiUrl) return;
+
+ const countSpan = document.getElementById("ongoing-tasks-count");
+ const textSpan = document.getElementById("ongoing-tasks-text");
+ const voteButton = document.getElementById("start-vote-button");
+ const progress = document.getElementById("poll-progress");
+ const pollInterval = 3000;
+
+ let currentCount = parseInt(countSpan?.textContent || "0", 10);
+ if (currentCount === 0) return;
+
+ function restartProgress() {
+ if (!progress) return;
+ progress.style.animation = "none";
+ progress.offsetHeight;
+ progress.style.animation = `poll-grow ${pollInterval}ms linear
forwards`;
+ }
+
+ function updateBanner(count) {
+ if (!countSpan || !textSpan) return;
+
+ currentCount = count;
+ countSpan.textContent = count;
+
+ const taskWord = count === 1 ? "task" : "tasks";
+ const isAre = count === 1 ? "is" : "are";
+ // TODO: Migrate away from setting innerHTML
+ textSpan.innerHTML = `There ${isAre} currently <strong
id="ongoing-tasks-count">${count}</strong> background verification ${taskWord}
running for the latest revision. Results shown below may be incomplete or
outdated until the tasks finish.`;
+
+ if (count === 0) {
+ // Banner always exists, but we hide it
+ banner.classList.add("d-none");
+ enableVoteButton();
+ }
+ }
+
+ function enableVoteButton() {
+ if (!voteButton) return;
+ if (!voteButton.classList.contains("disabled")) return;
+
+ const voteHref =
+ voteButton.dataset.voteHref ||
voteButton.getAttribute("href");
+ if (!voteHref || voteHref === "#") return;
+
+ voteButton.classList.remove("disabled");
+ voteButton.removeAttribute("aria-disabled");
+ voteButton.removeAttribute("tabindex");
+ voteButton.removeAttribute("role");
+ voteButton.setAttribute("href", voteHref);
+ voteButton.setAttribute("title", "Start a vote on this draft");
+ }
+
+ function pollOngoingTasks() {
+ if (currentCount === 0) return;
+
+ if (progress) {
+ progress.style.animation = "none";
+ progress.style.width = "100%";
+ progress.classList.remove("bg-warning");
+ progress.classList.add(
+ "bg-info",
+ "progress-bar-striped",
+ "progress-bar-animated",
+ );
+ }
+ fetch(apiUrl)
+ .then((response) => {
+ if (!response.ok) throw new Error(`HTTP
${response.status}`);
+ return response.json();
+ })
+ .then((data) => {
+ if (progress) {
+ progress.classList.remove(
+ "bg-info",
+ "progress-bar-striped",
+ "progress-bar-animated",
+ );
+ progress.classList.add("bg-warning");
+ }
+ const newCount = data.ongoing || 0;
+ if (newCount !== currentCount) {
+ updateBanner(newCount);
+ }
+ if (newCount > 0) {
+ restartProgress();
+ setTimeout(pollOngoingTasks,
pollInterval);
+ }
+ })
+ .catch((error) => {
+ console.error("Error polling ongoing tasks:",
error);
+ if (progress) {
+ progress.classList.remove(
+ "bg-info",
+ "progress-bar-striped",
+ "progress-bar-animated",
+ );
+ progress.classList.add("bg-warning");
+ }
+ restartProgress();
+ // Double the interval when there's an error
+ setTimeout(pollOngoingTasks, pollInterval * 2);
+ });
+ }
+
+ restartProgress();
+ setTimeout(pollOngoingTasks, pollInterval);
+})();
diff --git a/atr/static/js/src/projects-add-form.js
b/atr/static/js/src/projects-add-form.js
new file mode 100644
index 0000000..b77518e
--- /dev/null
+++ b/atr/static/js/src/projects-add-form.js
@@ -0,0 +1,20 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const configElement = document.getElementById("projects-add-config");
+ if (!configElement) return;
+
+ const committeeDisplayName = configElement.dataset.committeeDisplayName;
+ const committeeName = configElement.dataset.committeeName;
+ if (!committeeDisplayName || !committeeName) return;
+
+ const formTexts = document.querySelectorAll(".form-text, .text-muted");
+ formTexts.forEach((element) => {
+ element.textContent = element.textContent.replace(
+ /Example/g,
+ committeeDisplayName,
+ );
+ element.textContent = element.textContent.replace(
+ /example/g,
+ committeeName.toLowerCase(),
+ );
+ });
+});
diff --git a/atr/static/js/src/projects-directory.js
b/atr/static/js/src/projects-directory.js
new file mode 100644
index 0000000..46135ac
--- /dev/null
+++ b/atr/static/js/src/projects-directory.js
@@ -0,0 +1,74 @@
+function filter() {
+ const projectFilter = document.getElementById("project-filter").value;
+ const cards = document.querySelectorAll(".page-project-card");
+ let visibleCount = 0;
+ for (const card of cards) {
+ const nameElement = card.querySelector(".card-title");
+ const name = nameElement.innerHTML;
+ if (!projectFilter) {
+ card.parentElement.hidden = false;
+ visibleCount++;
+ } else {
+ card.parentElement.hidden = !name.match(new
RegExp(projectFilter, "i"));
+ if (!card.parentElement.hidden) {
+ visibleCount++;
+ }
+ }
+ }
+ document.getElementById("project-count").textContent = visibleCount;
+}
+
+// Add event listeners
+document.getElementById("filter-button").addEventListener("click", filter);
+document
+ .getElementById("project-filter")
+ .addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ filter();
+ event.preventDefault();
+ }
+ });
+
+// Add click handlers for project cards
+document.querySelectorAll(".page-project-card").forEach((card) => {
+ card.addEventListener("click", function (event) {
+ // Prevent card navigation if click is inside a form
+ if (event.target.closest("form")) {
+ return;
+ }
+ window.location.href = this.getAttribute("data-project-url");
+ });
+});
+
+// Participant filter logic
+const participantButton = document.getElementById("filter-participant-button");
+participantButton.addEventListener("click", function () {
+ const showing = this.dataset.showing;
+ const cards = document.querySelectorAll(".page-project-card");
+ let visibleCount = 0;
+
+ if (showing === "all") {
+ // Switch to showing only participant projects
+ cards.forEach((card) => {
+ const isParticipant = card.dataset.isParticipant ===
"true";
+ card.parentElement.hidden = !isParticipant;
+ if (!card.parentElement.hidden) {
+ visibleCount++;
+ }
+ });
+ this.textContent = "Show all projects";
+ this.dataset.showing = "participant";
+ } else {
+ // Switch to showing all projects
+ cards.forEach((card) => {
+ card.parentElement.hidden = false;
+ visibleCount++;
+ });
+ this.textContent = "Show my projects";
+ this.dataset.showing = "all";
+ }
+ // Reset text filter when toggling participant view
+ document.getElementById("project-filter").value = "";
+ // Update count
+ document.getElementById("project-count").textContent = visibleCount;
+});
diff --git a/atr/static/js/src/report-results.js
b/atr/static/js/src/report-results.js
new file mode 100644
index 0000000..ba97dac
--- /dev/null
+++ b/atr/static/js/src/report-results.js
@@ -0,0 +1,105 @@
+function toggleAllDetails() {
+ const details = document.querySelectorAll("details");
+ // Check if any are closed
+ const anyClosed = Array.from(details).some((detail) => !detail.open);
+ // If any are closed, open all
+ // Otherwise, close all
+ details.forEach((detail) => (detail.open = anyClosed));
+}
+
+function toggleStatusVisibility(type, status) {
+ const btn = document.getElementById(`btn-toggle-${type}-${status}`);
+ const targets = document.querySelectorAll(
+ `.atr-result-${type}.atr-result-status-${status}`,
+ );
+ if (!targets.length) return;
+ const elementsCurrentlyHidden =
targets[0].classList.contains("atr-hide");
+ targets.forEach((el) => {
+ if (elementsCurrentlyHidden) {
+ el.classList.remove("atr-hide");
+ } else {
+ el.classList.add("atr-hide");
+ }
+ });
+ const bsSt =
+ status === "failure" || status === "exception" ? "danger" :
status;
+ const cntMatch = btn.textContent.match(/\((\d+)\)/);
+ if (!cntMatch) {
+ console.error("Button text regex mismatch for:",
btn.textContent);
+ return;
+ }
+ const cnt = cntMatch[0];
+ const newButtonAction = elementsCurrentlyHidden ? "Hide" : "Show";
+ btn.querySelector("span").textContent = newButtonAction;
+ if (newButtonAction === "Hide") {
+ btn.classList.remove(`btn-outline-${bsSt}`);
+ btn.classList.add(`btn-${bsSt}`);
+ } else {
+ btn.classList.remove(`btn-${bsSt}`);
+ btn.classList.add(`btn-outline-${bsSt}`);
+ }
+ if (type === "member") {
+ updateMemberStriping();
+ } else if (type === "primary") {
+ updatePrimaryStriping();
+ }
+}
+
+function restripeVisibleRows(rowSelector, stripeClass) {
+ let visibleIdx = 0;
+ document.querySelectorAll(rowSelector).forEach((row) => {
+ row.classList.remove(stripeClass);
+ const hidden =
+ row.classList.contains("atr-hide") ||
+ row.classList.contains("page-member-path-hide");
+ if (!hidden) {
+ if (visibleIdx % 2 === 0)
row.classList.add(stripeClass);
+ visibleIdx++;
+ }
+ });
+}
+
+function updatePrimaryStriping() {
+ restripeVisibleRows(".atr-result-primary", "page-member-visible-odd");
+}
+
+function updateMemberStriping() {
+ restripeVisibleRows(".atr-result-member", "page-member-visible-odd");
+}
+
+// Toggle status visibility buttons
+document.querySelectorAll(".page-toggle-status").forEach((btn) => {
+ btn.addEventListener("click", function () {
+ const type = this.dataset.type;
+ const status = this.dataset.status;
+ toggleStatusVisibility(type, status);
+ });
+});
+
+// Toggle all details button
+const toggleAllBtn = document.getElementById("btn-toggle-all-details");
+if (toggleAllBtn) {
+ toggleAllBtn.addEventListener("click", toggleAllDetails);
+}
+
+// Member path filter
+const mpfInput = document.getElementById("member-path-filter");
+if (mpfInput) {
+ mpfInput.addEventListener("input", function () {
+ const filterText = this.value.toLowerCase();
+ document.querySelectorAll(".atr-result-member").forEach((row)
=> {
+ const pathCell = row.cells[0];
+ let hide = false;
+ if (filterText) {
+ if
(!pathCell.textContent.toLowerCase().includes(filterText)) {
+ hide = true;
+ }
+ }
+ row.classList.toggle("page-member-path-hide", hide);
+ });
+ updateMemberStriping();
+ });
+}
+
+updatePrimaryStriping();
+updateMemberStriping();
diff --git a/atr/static/js/src/vote-preview.js
b/atr/static/js/src/vote-preview.js
new file mode 100644
index 0000000..cc0cfba
--- /dev/null
+++ b/atr/static/js/src/vote-preview.js
@@ -0,0 +1,74 @@
+document.addEventListener("DOMContentLoaded", () => {
+ let debounceTimeout;
+ const debounceDelay = 500;
+
+ const bodyTextarea = document.getElementById("body");
+ const voteDurationInput = document.getElementById("vote_duration");
+ const textPreviewContent = document.getElementById(
+ "vote-body-preview-content",
+ );
+ const voteForm = document.querySelector("form.atr-canary");
+ const configElement = document.getElementById("vote-config");
+
+ if (!bodyTextarea || !voteDurationInput || !textPreviewContent ||
!voteForm) {
+ console.error("Required elements for vote preview not found.
Exiting.");
+ return;
+ }
+
+ const previewUrl = configElement ? configElement.dataset.previewUrl :
null;
+ const minHours = configElement ? configElement.dataset.minHours : "72";
+ const csrfTokenInput =
voteForm.querySelector('input[name="csrf_token"]');
+
+ if (!previewUrl || !csrfTokenInput) {
+ console.error(
+ "Required data attributes or CSRF token not found for
vote preview.",
+ );
+ return;
+ }
+ const csrfToken = csrfTokenInput.value;
+
+ function fetchAndUpdateVotePreview() {
+ const bodyContent = bodyTextarea.value;
+ const voteDuration = voteDurationInput.value || minHours;
+
+ fetch(previewUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type":
"application/x-www-form-urlencoded",
+ "X-CSRFToken": csrfToken,
+ },
+ body: new URLSearchParams({
+ body: bodyContent,
+ duration: voteDuration,
+ 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(fetchAndUpdateVotePreview,
debounceDelay);
+ });
+
+ voteDurationInput.addEventListener("input", () => {
+ clearTimeout(debounceTimeout);
+ debounceTimeout = setTimeout(fetchAndUpdateVotePreview,
debounceDelay);
+ });
+
+ fetchAndUpdateVotePreview();
+});
diff --git a/atr/static/js/ts/create-a-jwt.js b/atr/static/js/ts/create-a-jwt.js
new file mode 100644
index 0000000..2dd2df5
--- /dev/null
+++ b/atr/static/js/ts/create-a-jwt.js
@@ -0,0 +1,23 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const form = document.getElementById("issue-jwt-form");
+ const output = document.getElementById("jwt-output");
+ if (!form || !output) {
+ return;
+ }
+ form.addEventListener("submit", async (e) => {
+ e.preventDefault();
+ const resp = await fetch(form.action, {
+ method: "POST",
+ body: new FormData(form),
+ });
+ if (resp.ok) {
+ const token = await resp.text();
+ output.classList.remove("d-none");
+ output.textContent = token;
+ }
+ else {
+ alert("Failed to fetch JWT");
+ }
+ });
+});
+//# sourceMappingURL=create-a-jwt.js.map
\ No newline at end of file
diff --git a/atr/static/js/ts/create-a-jwt.js.map
b/atr/static/js/ts/create-a-jwt.js.map
new file mode 100644
index 0000000..f6bd847
--- /dev/null
+++ b/atr/static/js/ts/create-a-jwt.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"create-a-jwt.js","sourceRoot":"","sources":["../../ts/create-a-jwt.ts"],"names":[],"mappings":"AAAA,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAS,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAA2B,CAAC;IACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAErD,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAQ,EAAiB,EAAE,CAAC;QACjE,CAAC,CAAC,cAAc,EAAE,CAAC;QAEnB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,
[...]
\ No newline at end of file
diff --git a/atr/static/js/finish-selected-move.js
b/atr/static/js/ts/finish-selected-move.js
similarity index 74%
rename from atr/static/js/finish-selected-move.js
rename to atr/static/js/ts/finish-selected-move.js
index 9bfb1da..2a659f4 100644
--- a/atr/static/js/finish-selected-move.js
+++ b/atr/static/js/ts/finish-selected-move.js
@@ -1,13 +1,4 @@
"use strict";
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P,
generator) {
- function adopt(value) { return value instanceof P ? value : new P(function
(resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch
(e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); }
catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) :
adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
var ItemType;
(function (ItemType) {
ItemType["File"] = "file";
@@ -276,19 +267,19 @@ function handleDirRadio(radio) {
}
}
function setState(partial) {
- uiState = Object.assign(Object.assign({}, uiState), partial);
+ uiState = { ...uiState, ...partial };
renderAllLists();
}
function onFileFilterInput(event) {
const target = event.target;
if (target instanceof HTMLInputElement) {
- setState({ filters: Object.assign(Object.assign({}, uiState.filters),
{ file: target.value }) });
+ setState({ filters: { ...uiState.filters, file: target.value } });
}
}
function onDirFilterInput(event) {
const target = event.target;
if (target instanceof HTMLInputElement) {
- setState({ filters: Object.assign(Object.assign({}, uiState.filters),
{ dir: target.value }) });
+ setState({ filters: { ...uiState.filters, dir: target.value } });
}
}
function onMaxFilesChange(event) {
@@ -306,60 +297,58 @@ function isErrorResponse(data) {
(('message' in data && typeof data.message === 'string') ||
('error' in data && typeof data.error === 'string'));
}
-function moveFiles(files, dest, csrfToken, signal) {
- return __awaiter(this, void 0, void 0, function* () {
- const formData = new FormData();
- formData.append("csrf_token", csrfToken);
- formData.append("variant", "MOVE_FILE");
- for (const file of files) {
- formData.append("source_files", file);
+async function moveFiles(files, dest, csrfToken, signal) {
+ const formData = new FormData();
+ formData.append("csrf_token", csrfToken);
+ formData.append("variant", "MOVE_FILE");
+ for (const file of files) {
+ formData.append("source_files", file);
+ }
+ formData.append("target_directory", dest);
+ try {
+ const response = await fetch(window.location.pathname, {
+ method: "POST",
+ body: formData,
+ credentials: "same-origin",
+ headers: {
+ "Accept": "application/json",
+ },
+ signal,
+ });
+ if (response.ok) {
+ return { ok: true };
}
- formData.append("target_directory", dest);
- try {
- const response = yield fetch(window.location.pathname, {
- method: "POST",
- body: formData,
- credentials: "same-origin",
- headers: {
- "Accept": "application/json",
- },
- signal,
- });
- if (response.ok) {
- return { ok: true };
+ else {
+ let errorMsg = `An error occurred while moving the file (Status:
${response.status})`;
+ if (response.status === 403)
+ errorMsg = "Permission denied to move the file.";
+ if (response.status === 400)
+ errorMsg = "Invalid request to move the file.";
+ if (signal?.aborted) {
+ errorMsg = "Move operation aborted.";
}
- else {
- let errorMsg = `An error occurred while moving the file
(Status: ${response.status})`;
- if (response.status === 403)
- errorMsg = "Permission denied to move the file.";
- if (response.status === 400)
- errorMsg = "Invalid request to move the file.";
- if (signal === null || signal === void 0 ? void 0 :
signal.aborted) {
- errorMsg = "Move operation aborted.";
- }
- try {
- const errorData = yield response.json();
- if (isErrorResponse(errorData)) {
- if (errorData.message) {
- errorMsg = errorData.message;
- }
- else if (errorData.error) {
- errorMsg = errorData.error;
- }
+ try {
+ const errorData = await response.json();
+ if (isErrorResponse(errorData)) {
+ if (errorData.message) {
+ errorMsg = errorData.message;
+ }
+ else if (errorData.error) {
+ errorMsg = errorData.error;
}
}
- catch ( /* Do nothing */_a) { /* Do nothing */ }
- return { ok: false, message: errorMsg };
}
+ catch { /* Do nothing */ }
+ return { ok: false, message: errorMsg };
}
- catch (error) {
- // console.error("Network or fetch error:", error);
- if (error instanceof Error && error.name === 'AbortError') {
- return { ok: false, message: "Move operation aborted." };
- }
- return { ok: false, message: "A network error occurred. Please
check your connection and try again." };
+ }
+ catch (error) {
+ // console.error("Network or fetch error:", error);
+ if (error instanceof Error && error.name === 'AbortError') {
+ return { ok: false, message: "Move operation aborted." };
}
- });
+ return { ok: false, message: "A network error occurred. Please check
your connection and try again." };
+ }
}
function splitMoveCandidates(selected, dest) {
const toMoveMutable = [];
@@ -374,42 +363,39 @@ function splitMoveCandidates(selected, dest) {
}
return { toMove: toMoveMutable, alreadyThere: alreadyThereMutable };
}
-function onConfirmMoveClick() {
- return __awaiter(this, void 0, void 0, function* () {
- errorAlert.classList.add("d-none");
- errorAlert.textContent = "";
- const controller = new AbortController();
- window.addEventListener("beforeunload", () => controller.abort());
- if (uiState.currentlySelectedPaths.size > 0 &&
uiState.currentlyChosenDirectoryPath && uiState.csrfToken) {
- const { toMove, alreadyThere: itemsAlreadyInDest } =
splitMoveCandidates(uiState.currentlySelectedPaths,
uiState.currentlyChosenDirectoryPath);
- if (toMove.length === 0 && itemsAlreadyInDest.length > 0 &&
uiState.currentlySelectedPaths.size > 0) {
- errorAlert.classList.remove("d-none");
- errorAlert.textContent = `All selected items
(${itemsAlreadyInDest.join(", ")}) are already in the target directory. No
items were moved.`;
- confirmMoveButton.disabled = false;
- return;
- }
- if (itemsAlreadyInDest.length > 0) {
- const alreadyInDestMsg = `Note: ${itemsAlreadyInDest.join(",
")} ${itemsAlreadyInDest.length === 1 ? "is" : "are"} already in the target
directory and will not be moved.`;
- const existingError = errorAlert.textContent;
- errorAlert.textContent = existingError ? `${existingError}
${alreadyInDestMsg}` : alreadyInDestMsg;
- }
- const result = yield moveFiles(toMove,
uiState.currentlyChosenDirectoryPath, uiState.csrfToken, controller.signal);
- if (result.ok) {
- window.location.reload();
- }
- else {
- errorAlert.classList.remove("d-none");
- errorAlert.textContent = result.message;
- }
+async function onConfirmMoveClick() {
+ errorAlert.classList.add("d-none");
+ errorAlert.textContent = "";
+ const controller = new AbortController();
+ window.addEventListener("beforeunload", () => controller.abort());
+ if (uiState.currentlySelectedPaths.size > 0 &&
uiState.currentlyChosenDirectoryPath && uiState.csrfToken) {
+ const { toMove, alreadyThere: itemsAlreadyInDest } =
splitMoveCandidates(uiState.currentlySelectedPaths,
uiState.currentlyChosenDirectoryPath);
+ if (toMove.length === 0 && itemsAlreadyInDest.length > 0 &&
uiState.currentlySelectedPaths.size > 0) {
+ errorAlert.classList.remove("d-none");
+ errorAlert.textContent = `All selected items
(${itemsAlreadyInDest.join(", ")}) are already in the target directory. No
items were moved.`;
+ confirmMoveButton.disabled = false;
+ return;
+ }
+ if (itemsAlreadyInDest.length > 0) {
+ const alreadyInDestMsg = `Note: ${itemsAlreadyInDest.join(", ")}
${itemsAlreadyInDest.length === 1 ? "is" : "are"} already in the target
directory and will not be moved.`;
+ const existingError = errorAlert.textContent;
+ errorAlert.textContent = existingError ? `${existingError}
${alreadyInDestMsg}` : alreadyInDestMsg;
+ }
+ const result = await moveFiles(toMove,
uiState.currentlyChosenDirectoryPath, uiState.csrfToken, controller.signal);
+ if (result.ok) {
+ window.location.reload();
}
else {
errorAlert.classList.remove("d-none");
- errorAlert.textContent = "Please select item(s) and a destination
directory.";
+ errorAlert.textContent = result.message;
}
- });
+ }
+ else {
+ errorAlert.classList.remove("d-none");
+ errorAlert.textContent = "Please select item(s) and a destination
directory.";
+ }
}
document.addEventListener("DOMContentLoaded", () => {
- var _a, _b, _c, _d;
fileFilterInput = $(ID.fileFilter);
fileListTableBody = $(ID.fileListTableBody);
maxFilesInput = $(ID.maxFilesInput);
@@ -424,18 +410,18 @@ document.addEventListener("DOMContentLoaded", () => {
let initialFilePaths = [];
let initialTargetDirs = [];
try {
- const fileData = (_a = document.getElementById(ID.fileData)) === null
|| _a === void 0 ? void 0 : _a.textContent;
+ const fileData = document.getElementById(ID.fileData)?.textContent;
if (fileData)
initialFilePaths = JSON.parse(fileData);
- const dirData = (_b = document.getElementById(ID.dirData)) === null ||
_b === void 0 ? void 0 : _b.textContent;
+ const dirData = document.getElementById(ID.dirData)?.textContent;
if (dirData)
initialTargetDirs = JSON.parse(dirData);
}
- catch (_e) {
+ catch {
// console.error("Error parsing JSON data:");
}
- const csrfToken = (_d = (_c = document
- .querySelector(`#${ID.mainScriptData}`)) === null || _c === void 0 ?
void 0 : _c.dataset.csrfToken) !== null && _d !== void 0 ? _d : null;
+ const csrfToken = document
+ .querySelector(`#${ID.mainScriptData}`)?.dataset.csrfToken ?? null;
uiState = {
filters: {
file: fileFilterInput.value || "",
@@ -482,4 +468,4 @@ document.addEventListener("DOMContentLoaded", () => {
});
renderAllLists();
});
-//# sourceMappingURL=finish-selected-move.js.map
+//# sourceMappingURL=finish-selected-move.js.map
\ No newline at end of file
diff --git a/atr/static/js/ts/finish-selected-move.js.map
b/atr/static/js/ts/finish-selected-move.js.map
new file mode 100644
index 0000000..eddbb19
--- /dev/null
+++ b/atr/static/js/ts/finish-selected-move.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"finish-selected-move.js","sourceRoot":"","sources":["../../ts/finish-selected-move.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,IAAK,QAGJ;AAHD,WAAK,QAAQ;IACT,yBAAa,CAAA;IACb,uBAAW,CAAA;AAAC,CAChB,EAHK,QAAQ,KAAR,QAAQ,QAGZ;AAED,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACrB,iBAAiB,EAAE,qBAAqB;IACxC,wBAAwB,EAAE,6BAA6B;IACvD,OAAO,EAAE,UAAU;IACnB,cAAc,EAAE,kBAAkB;IAClC,eAAe,EAAE,oBAAoB;IACrC,gBAAgB,EAAE,qBAAqB;IACvC,UAAU,EAAE,kBAAkB;IAC9B,QAAQ,EAAE,WAAW;IACrB,UAAU,EAAE,aAAa;
[...]
\ No newline at end of file
diff --git a/atr/static/js/vote-preview.js b/atr/static/js/vote-preview.js
deleted file mode 100644
index 270fe71..0000000
--- a/atr/static/js/vote-preview.js
+++ /dev/null
@@ -1,70 +0,0 @@
-document.addEventListener("DOMContentLoaded", () => {
- let debounceTimeout;
- const debounceDelay = 500;
-
- const bodyTextarea = document.getElementById("body");
- const voteDurationInput = document.getElementById("vote_duration");
- const textPreviewContent =
document.getElementById("vote-body-preview-content");
- const voteForm = document.querySelector("form.atr-canary");
- const configElement = document.getElementById("vote-config");
-
- if (!bodyTextarea || !voteDurationInput || !textPreviewContent ||
!voteForm) {
- console.error("Required elements for vote preview not found.
Exiting.");
- return;
- }
-
- const previewUrl = configElement ? configElement.dataset.previewUrl : null;
- const minHours = configElement ? configElement.dataset.minHours : "72";
- const csrfTokenInput = voteForm.querySelector('input[name="csrf_token"]');
-
- if (!previewUrl || !csrfTokenInput) {
- console.error("Required data attributes or CSRF token not found for
vote preview.");
- return;
- }
- const csrfToken = csrfTokenInput.value;
-
- function fetchAndUpdateVotePreview() {
- const bodyContent = bodyTextarea.value;
- const voteDuration = voteDurationInput.value || minHours;
-
- fetch(previewUrl, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- "X-CSRFToken": csrfToken
- },
- body: new URLSearchParams({
- "body": bodyContent,
- "duration": voteDuration,
- "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(fetchAndUpdateVotePreview, debounceDelay);
- });
-
- voteDurationInput.addEventListener("input", () => {
- clearTimeout(debounceTimeout);
- debounceTimeout = setTimeout(fetchAndUpdateVotePreview, debounceDelay);
- });
-
- fetchAndUpdateVotePreview();
-});
diff --git a/atr/template.py b/atr/template.py
index 979c995..df194bf 100644
--- a/atr/template.py
+++ b/atr/template.py
@@ -34,14 +34,16 @@ async def blank(
content: str | htm.Element,
description: str | None = None,
javascripts: list[str] | None = None,
+ typescripts: list[str] | None = None,
) -> str:
- js_urls = [util.static_url(f"js/{name}.js") for name in javascripts or []]
+ js_urls = [util.static_url(f"js/src/{name}.js") for name in javascripts or
[]]
+ ts_urls = [util.static_url(f"js/ts/{name}.js") for name in typescripts or
[]]
return await render_sync(
"blank.html",
title=title,
description=description or title,
content=content,
- javascripts=js_urls,
+ javascripts=js_urls + ts_urls,
)
diff --git a/atr/templates/check-selected.html
b/atr/templates/check-selected.html
index aefc386..e74102e 100644
--- a/atr/templates/check-selected.html
+++ b/atr/templates/check-selected.html
@@ -195,5 +195,5 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/ongoing-tasks-poll.js') }}"></script>
+ <script src="{{ static_url('js/src/ongoing-tasks-poll.js') }}"></script>
{% endblock javascripts %}
diff --git a/atr/templates/committee-directory.html
b/atr/templates/committee-directory.html
index eb9442c..f0c7dfb 100644
--- a/atr/templates/committee-directory.html
+++ b/atr/templates/committee-directory.html
@@ -176,5 +176,5 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/committee-directory.js') }}"></script>
+ <script src="{{ static_url('js/src/committee-directory.js') }}"></script>
{% endblock javascripts %}
diff --git a/atr/templates/layouts/base.html b/atr/templates/layouts/base.html
index 01fe976..135ccba 100644
--- a/atr/templates/layouts/base.html
+++ b/atr/templates/layouts/base.html
@@ -64,7 +64,7 @@
</main>
{% block javascripts %}
- <script src="{{ static_url('js/bootstrap.bundle.min.js') }}"></script>
+ <script src="{{ static_url('js/min/bootstrap.bundle.min.js')
}}"></script>
{% endblock javascripts %}
</body>
</html>
diff --git a/atr/templates/projects.html b/atr/templates/projects.html
index 9878905..782a633 100644
--- a/atr/templates/projects.html
+++ b/atr/templates/projects.html
@@ -84,5 +84,5 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/projects-directory.js') }}"></script>
+ <script src="{{ static_url('js/src/projects-directory.js') }}"></script>
{% endblock javascripts %}
diff --git a/atr/templates/report-selected-path.html
b/atr/templates/report-selected-path.html
index 0503f4d..2845c85 100644
--- a/atr/templates/report-selected-path.html
+++ b/atr/templates/report-selected-path.html
@@ -339,7 +339,7 @@
{% block javascripts %}
{{ super() }}
- <script src="{{ static_url('js/report-results.js') }}"></script>
+ <script src="{{ static_url('js/src/report-results.js') }}"></script>
{% endblock javascripts %}
{% macro function_name_from_key(key) -%}
diff --git a/bootstrap/make.sh b/bootstrap/make.sh
index 597aa42..7420e48 100755
--- a/bootstrap/make.sh
+++ b/bootstrap/make.sh
@@ -22,5 +22,5 @@ cp ../reboot-shim.scss scss/reboot-shim.scss
sass -q scss/custom.scss css/custom.css
sed 's/custom.css.map/bootstrap.custom.css.map/g' css/custom.css >
../../atr/static/css/bootstrap.custom.css
sed 's/custom.css/bootstrap.custom.css/g' css/custom.css.map >
../../atr/static/css/bootstrap.custom.css.map
-cp node_modules/bootstrap/dist/js/bootstrap.bundle.min.js
../../atr/static/js/bootstrap.bundle.min.js
-cp node_modules/bootstrap/dist/js/bootstrap.bundle.min.js.map
../../atr/static/js/bootstrap.bundle.min.js.map
+cp node_modules/bootstrap/dist/js/bootstrap.bundle.min.js
../../atr/static/js/min/bootstrap.bundle.min.js
+cp node_modules/bootstrap/dist/js/bootstrap.bundle.min.js.map
../../atr/static/js/min/bootstrap.bundle.min.js.map
diff --git a/tsconfig.json b/tsconfig.json
index a1c45e2..68eb9ca 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,9 +1,9 @@
{
"compilerOptions": {
- "target": "ES6",
+ "target": "es2021",
"module": "commonjs",
"rootDir": "./atr/static/ts",
- "outDir": "./atr/static/js",
+ "outDir": "./atr/static/js/ts",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
@@ -11,5 +11,5 @@
"sourceMap": true
},
"include": ["./atr/static/ts/**/*.ts"],
- "exclude": ["./atr/static/js", "node_modules"]
+ "exclude": ["./atr/static/js/ts", "node_modules"]
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]