mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-24 09:58:43 -04:00
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:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
84
app/assets/javascripts/index.js
Normal file
84
app/assets/javascripts/index.js
Normal 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';
|
||||
@@ -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);
|
||||
40
app/assets/javascripts/moduleRegistry.js
Normal file
40
app/assets/javascripts/moduleRegistry.js
Normal 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;
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user