Feat/webpack (#2998)

* First commit

* Removed govuk for webpack. Modernized javascript importing. Removed dead js

* Fixed tests, a few styling bugs

* Fixed some table errors and regenerated backstop ref images

* Updated tests for coverage

* Changes from carlo suggestions
This commit is contained in:
Alex Janousek
2025-10-14 13:20:58 -04:00
committed by GitHub
parent 3b3191b337
commit 3a39d910b3
62 changed files with 1657 additions and 4861 deletions

View File

@@ -239,10 +239,10 @@
}
const data = await response.json();
labels = [];
deliveredData = [];
failedData = [];
pendingData = [];
let labels = [];
let deliveredData = [];
let failedData = [];
let pendingData = [];
let totalMessages = 0;
for (var dateString in data) {

View File

@@ -1,10 +1,7 @@
(function (window) {
"use strict";
export function initCurrentYear() {
const currentYearElement = document.getElementById("current-year");
// Show the current year
const currentYearElement = document.getElementById("current-year");
if (currentYearElement) {
currentYearElement.textContent = new Date().getFullYear();
}
})(window);
if (currentYearElement) {
currentYearElement.textContent = new Date().getFullYear();
}
}

View File

@@ -1,16 +1,7 @@
(function (window) {
"use strict";
export const ErrorBanner = {
hideBanner: () => $('.banner-dangerous').addClass('display-none'),
showBanner: () => $('.banner-dangerous').removeClass('display-none')
};
/*
This module is intended to be used to show and hide an error banner based on a javascript trigger. You should make
sure the banner has an appropriate aria-live attribute, and a tabindex of -1 so that screenreaders and keyboard users
are alerted to the change respectively.
This may behave in unexpected ways if you have more than one element with the `banner-dangerous` class on your page.
*/
window.NotifyModules.ErrorBanner = {
hideBanner: () => $('.banner-dangerous').addClass('display-none'),
showBanner: () => $('.banner-dangerous')
.removeClass('display-none')
};
})(window);
window.NotifyModules = window.NotifyModules || {};
window.NotifyModules.ErrorBanner = ErrorBanner;

View File

@@ -1,123 +0,0 @@
(function(window) {
"use strict";
window.NotifyModules['fullscreen-table'] = function() {
this.start = function(component) {
this.$component = $(component);
this.$table = this.$component.find('table');
this.nativeHeight = this.$component.innerHeight() + 20; // 20px to allow room for scrollbar
this.topOffset = this.$component.offset().top;
this.insertShims();
this.maintainWidth();
this.maintainHeight();
this.toggleShadows();
$(window)
.on('scroll resize', this.maintainHeight)
.on('resize', this.maintainWidth);
this.$scrollableTable
.on('scroll', this.toggleShadows)
.on('scroll', this.maintainHeight)
if (
window.NotifyModules.stickAtBottomWhenScrolling &&
window.NotifyModules.stickAtBottomWhenScrolling.recalculate
) {
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
}
this.maintainWidth();
};
this.insertShims = () => {
const attributesForFocus = 'role aria-labelledby';
let captionId = this.$table.find('caption').text().toLowerCase().replace(/[^A-Za-z]+/g, '');
this.$table.find('caption').attr('id', captionId);
this.$table.wrap(`<div class="fullscreen-scrollable-table" role="region" aria-labelledby="${captionId}"/>`);
this.$component
.append(
this.$component.find('.fullscreen-scrollable-table')
.clone()
.addClass('fullscreen-fixed-table')
.removeClass('fullscreen-scrollable-table')
.removeAttr(attributesForFocus)
.attr('aria-hidden', true)
.find('caption')
.removeAttr('id')
.closest('.fullscreen-fixed-table')
)
.append(
'<div class="fullscreen-right-shadow" />'
)
.after(
$("<div class='fullscreen-shim'/>").css({
'height': this.nativeHeight,
'top': this.topOffset
})
)
.css('position', 'absolute');
this.$scrollableTable = this.$component.find('.fullscreen-scrollable-table');
this.$fixedTable = this.$component.find('.fullscreen-fixed-table');
};
this.maintainHeight = () => {
let height = Math.min(
$(window).height() - this.topOffset + $(window).scrollTop(),
this.nativeHeight
);
this.$scrollableTable.outerHeight(height);
this.$fixedTable.outerHeight(height);
};
this.maintainWidth = () => {
let indexColumnWidth = this.$fixedTable.find('.table-field-index').outerWidth();
this.$scrollableTable
.css({
'width': this.$component.parent('main').width() - indexColumnWidth,
'margin-left': indexColumnWidth
});
this.$fixedTable
.width(indexColumnWidth + 4);
};
this.toggleShadows = () => {
this.$fixedTable
.toggleClass(
'fullscreen-scrolled-table',
this.$scrollableTable.scrollLeft() > 0
);
this.$component.find('.fullscreen-right-shadow')
.toggleClass(
'visible',
this.$scrollableTable.scrollLeft() < (this.$table.width() - this.$scrollableTable.width())
);
setTimeout(
() => this.$component.find('.fullscreen-right-shadow').addClass('with-transition'),
3000
);
};
};
})(window);

View File

@@ -0,0 +1,84 @@
// Webpack Entry Point
// Vendor libraries
import $ from 'jquery';
window.jQuery = window.$ = $;
import './jquery-expose.js';
import 'query-command-supported';
import 'textarea-caret';
import * as cbor from 'cbor-js';
// D3 - selective imports for tree shaking
import {
select,
scaleLinear,
scaleBand,
scaleSymlog,
scaleOrdinal,
axisBottom,
axisLeft,
stack,
stackOrderNone,
stackOffsetNone,
sum,
max,
format,
interpolate
} from 'd3';
window.CBOR = cbor;
window.d3 = {
select,
scaleLinear,
scaleBand,
scaleSymlog,
scaleOrdinal,
axisBottom,
axisLeft,
stack,
stackOrderNone,
stackOffsetNone,
sum,
max,
format,
interpolate
};
window.NotifyModules = window.NotifyModules || {};
// Core modules
import './modules/init.js';
import './modules/uswds-modules.js';
import './modules/show-hide-content.js';
import { registerModule, initModules } from './moduleRegistry.js';
// Local modules
import './radioSelect.js';
import './liveSearch.js';
import './preventDuplicateFormSubmissions.js';
import { ErrorBanner } from './errorBanner.js';
import './notifyModal.js';
import './timeoutPopup.js';
import { initCurrentYear } from './date.js';
import './sidenav.js';
import './validation.js';
import './scrollPosition.js';
initCurrentYear();
// NotifyModules
import './copyToClipboard.js';
import './enhancedTextbox.js';
import './fileUpload.js';
import './errorTracking.js';
import './templateFolderForm.js';
import './collapsibleCheckboxes.js';
import './radioSlider.js';
import './updateStatus.js';
import './main.js';
import './listEntry.js';
import './stick-to-window-when-scrolling.js';
import './totalMessagesChart.js';
import './activityChart.js';
import './job-polling.js';

View File

@@ -1,41 +0,0 @@
(function (window) {
// Set the target date (10 days before March 15th, 2024)
const targetDate = new Date("April 16, 2024 00:00:00").getTime();
// Set a const for the container to write a conditional statement and only run if it exists
const countdownContainer = document.getElementById("countdown-container");
// Function to update the countdown display
function updateCountdown() {
if (countdownContainer) {
const now = new Date().getTime();
const difference = targetDate - now;
// Time calculations for days only
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
// Visibility logic
if (days < 0 || days > 10) {
// Hide if more than 10 days away OR if already past the date
countdownContainer.style.display = "none";
} else {
// Show if 10 days or less remaining
countdownContainer.style.display = "block";
document.getElementById("countdown").innerHTML = days + " days ";
}
}
}
// Expose the updateCountdown function to the outside world
window.updateCountdown = updateCountdown;
// Initial display update
updateCountdown();
// Update the countdown every second (inside the IIFE)
setInterval(updateCountdown, 1000);
})(window);

View File

@@ -0,0 +1,40 @@
const modules = new Map();
export function registerModule(name, ModuleClass) {
modules.set(name, ModuleClass);
window.NotifyModules = window.NotifyModules || {};
window.NotifyModules[name] = ModuleClass;
}
export function initModules() {
const moduleElements = document.querySelectorAll('[data-module]');
moduleElements.forEach(element => {
const moduleName = element.getAttribute('data-module');
const moduleStarted = element.getAttribute('data-module-started');
if (moduleStarted) return;
const ModuleClass = modules.get(moduleName);
if (!ModuleClass) {
console.warn(`Module "${moduleName}" not found in registry`);
return;
}
try {
const moduleInstance = new ModuleClass();
if (moduleInstance.start && typeof moduleInstance.start === 'function') {
moduleInstance.start(element);
}
element.setAttribute('data-module-started', 'true');
} catch (error) {
console.error(`Error initializing module "${moduleName}":`, error);
}
});
}
window.NotifyModules = window.NotifyModules || {};
window.NotifyModules.start = initModules;

View File

@@ -12,6 +12,11 @@
this.$liveRegionCounter = this.$form.find('.selection-counter');
// Get single channel data from DOM (must happen after DOM is ready)
const addNewTemplateForm = document.querySelector('div[id=add_new_template_form]');
this.$singleNotificationChannel = addNewTemplateForm ? addNewTemplateForm.getAttribute("data-channel") : null;
this.$singleChannelService = addNewTemplateForm ? addNewTemplateForm.getAttribute("data-service") : null;
this.$liveRegionCounter.before(this.nothingSelectedButtons);
this.$liveRegionCounter.before(this.itemsSelectedButtons);
@@ -188,9 +193,6 @@
return changed;
};
this.$singleNotificationChannel = (document.querySelector('div[id=add_new_template_form]')).getAttribute("data-channel");
this.$singleChannelService = (document.querySelector('div[id=add_new_template_form]')).getAttribute("data-service");
this.actionButtonClicked = function(event) {
this.currentState = $(event.currentTarget).val();
@@ -215,6 +217,12 @@
}
}
} else {
// If state is not changing, this is a submit button - allow form submission
if (this.currentState === this._lastState) {
return true;
}
// Otherwise, show the form UI
event.preventDefault();
if (this.stateChanged()) {
this.render();

View File

@@ -12,7 +12,7 @@ window.NotifyModules.TimeoutPopup = window.NotifyModules.TimeoutPopup || {};
var difference = timeTillSessionEnd - now;
var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((difference % (1000 * 60)) / 1000);
document.getElementById("timeLeft").innerHTML = + minutes + "m " + seconds + "s";
document.getElementById("timeLeft").textContent = minutes + "m " + seconds + "s";
showTimer();
document.getElementById("logOutTimer").addEventListener("click", signoutUser);
document.getElementById("extendSessionTimer").addEventListener("click", extendSession);