From 7b04c6d7f8f6e07bf645d40d805f083fc0e1b227 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 2 Dec 2024 17:05:15 -0500 Subject: [PATCH 1/4] Fullcalendar upgrades --- fec/fec/static/js/modules/calendar-helpers.js | 30 ++- .../static/js/modules/calendar-list-view.js | 14 +- fec/fec/static/js/modules/election-search.js | 1 + .../static/js/modules/filters/filter-panel.js | 1 + fec/fec/tests/js/calendar-helpers.js | 134 ++++++++++++ fec/fec/tests/js/setup.js | 25 ++- package-lock.json | 193 ++++++++++++++++-- package.json | 6 +- 8 files changed, 373 insertions(+), 31 deletions(-) create mode 100644 fec/fec/tests/js/calendar-helpers.js diff --git a/fec/fec/static/js/modules/calendar-helpers.js b/fec/fec/static/js/modules/calendar-helpers.js index 0920fc6c36..34380eaebf 100644 --- a/fec/fec/static/js/modules/calendar-helpers.js +++ b/fec/fec/static/js/modules/calendar-helpers.js @@ -4,6 +4,14 @@ import moment from 'moment'; import { default as URI } from 'urijs'; +/** + * @param {Object} event - {start: '', end: '', title: '', summary: ''} + * @param {Moment} [event.start] + * @param {Moment} [event.end] + * @param {string} [event.title] + * @param {string} [event.summary] + * @returns {string} + */ export function getGoogleUrl(event) { let fmt, dates; if (event.end) { @@ -29,6 +37,11 @@ export function getGoogleUrl(event) { .toString(); } +/** + * @param {string} path + * @param {Object} params - key-value pairs on an Object + * @returns {string} a string like '/v1/path/?param1key=param1val¶m2key=param2val' + */ export function calendarDownload(path, params) { const url = URI(window.API_LOCATION) .path(Array.prototype.concat(window.API_VERSION, path || [], '').join('/')) @@ -57,6 +70,12 @@ export function getUrl(path, params, type) { .toString(); return URI.decode(url); } + +/** + * Returns a class name for multi-date events, i.e. those with valid but different start day and end day + * @param {Object} event - Event object, looking at start_date and end_date + * @returns {string} Returns a class name for multi-day events, else '' + */ export function className(event) { const start = event.start_date ? moment(event.start_date).format('M D') : null; const end = event.end_date ? moment(event.end_date).format('M D') : null; @@ -67,6 +86,11 @@ export function className(event) { } } +/** + * Does event.start_date have an hour (vs only yyyy-mm-dd or null) + * @param {Object} event + * @returns {boolean} true if event.start_date has a legitimate hour included, otherwise false + */ export function checkStartTime(event) { if (event.start_date) { return moment(event.start_date).hour() ? true : false; @@ -75,8 +99,12 @@ export function checkStartTime(event) { } } +/** + * Matches the category parameter from calendar date API + * @param {string} category + * @returns {string} String for the tooltip content for category + */ export function mapCategoryDescription(category) { - // matches the category parameter from calendar date API const tooltipContent = { 'Reporting Deadlines': 'Throughout the year, filers submit regularly scheduled reports about their campaign finance activity. These reporting requirements are outlined in Title 11 of the Code of Federal Regulations (CFR) and vary, depending on the type of filer.', diff --git a/fec/fec/static/js/modules/calendar-list-view.js b/fec/fec/static/js/modules/calendar-list-view.js index 56b33630ef..c0372cb9ea 100644 --- a/fec/fec/static/js/modules/calendar-list-view.js +++ b/fec/fec/static/js/modules/calendar-list-view.js @@ -1,13 +1,13 @@ /** * */ -import $ from 'jquery'; +import { Calendar } from '@fullcalendar/core'; +// import $ from 'jquery'; import { default as moment } from 'moment'; import { default as _chain } from 'underscore/modules/chain.js'; import { default as _each } from 'underscore/modules/each.js'; import { default as _pairs } from 'underscore/modules/pairs.js'; import { default as _reduce } from 'underscore/modules/reduce.js'; -import 'fullcalendar'; import Dropdown from './dropdowns.js'; import { default as eventTemplate } from '../templates/calendar/events.hbs'; @@ -20,6 +20,8 @@ const View = FC.View; // Then followed by a list of the types of events under that category // List items are the first token of the event category parameter from the API // example: 'ie' for 'IE Periods' + +/** @enum {string[]} */ const categories = { Elections: ['election'], 'Filing deadlines': ['reporting', 'pre'], @@ -67,6 +69,14 @@ const categoryGroups = function(events, start, end) { .value(); }; +/** + * Group the events by the formatted value of their start dates, + * otherwise events with a time on their date will be grouped separately from those that just have a date + * @param {Object[]} events - Array of Objects with event details + * @param {Moment} start + * @param {Moment} end + * @returns {Object[]} + */ const chronologicalGroups = function(events, start, end) { events = _chain(events) .filter(function(event) { diff --git a/fec/fec/static/js/modules/election-search.js b/fec/fec/static/js/modules/election-search.js index a81dac7970..0990f6e3db 100644 --- a/fec/fec/static/js/modules/election-search.js +++ b/fec/fec/static/js/modules/election-search.js @@ -564,6 +564,7 @@ ElectionSearch.prototype.formatName = function(result) { /** * Get the date of the general election for a two-year period * @param {Object} result + * @param {number} result.cycle * @returns {string} - In the `MMMM Do, YYYY` format * (i.e. Full month name + ordinal date + four-digit year e.g. August 8th 2008) */ diff --git a/fec/fec/static/js/modules/filters/filter-panel.js b/fec/fec/static/js/modules/filters/filter-panel.js index 2521f2cb90..cd0a405933 100644 --- a/fec/fec/static/js/modules/filters/filter-panel.js +++ b/fec/fec/static/js/modules/filters/filter-panel.js @@ -2,6 +2,7 @@ import { default as FilterSet } from './filter-set.js'; import { removeTabindex, restoreTabindex } from '../accessibility.js'; import { BREAKPOINTS, getWindowWidth, isLargeScreen } from '../helpers.js'; +/** @enum {string} */ const defaultOptions = { body: '.filters', content: '.filters__content', diff --git a/fec/fec/tests/js/calendar-helpers.js b/fec/fec/tests/js/calendar-helpers.js new file mode 100644 index 0000000000..82dbc95510 --- /dev/null +++ b/fec/fec/tests/js/calendar-helpers.js @@ -0,0 +1,134 @@ +// Common for all/most tests +import './setup.js'; +import * as sinonChai from 'sinon-chai'; +import { expect, use } from 'chai'; +use(sinonChai); +// (end common) + +import { + calendarDownload, + checkStartTime, + className, + mapCategoryDescription +} from '../../static/js/modules/calendar-helpers.js'; +import moment from 'moment'; +import ElectionSearch from '../../static/js/modules/election-search.js'; + +describe('calendarDownload', function () { + describe('should', function () { + let expectedUrl = `${window.API_LOCATION}/${window.API_VERSION}/test/path/`; + expectedUrl += `?api_key=${window.CALENDAR_DOWNLOAD_PUBLIC_API_KEY}&per_page=500`; + expectedUrl += `¶m1=val+1¶m+2=(val2)`; + + it('return expected results', function () { + expect( + calendarDownload('test/path', { param1: 'val 1', 'param 2': '(val2)' }) + ).to.equal(expectedUrl); + }); + it('handle errors as expected', function () { + // TODO + // TODO + // TODO + }); + }); +}); + +describe('checkStartTime', function () { + describe('should', function () { + it('return expected results', function () { + expect(checkStartTime({ start_date: '2000-01-01 12:34' })).to.be.true; + expect(checkStartTime({ start_date: '2000-01-01 01' })).to.be.true; + expect(checkStartTime({ start_date: '2000-01-01T23:59:59Z' })).to.be.true; + expect(checkStartTime({ start_date: '2000-01-01' })).to.be.false; + expect(checkStartTime({ start_date: new Date() })).to.be.true; + }); + it('handle errors as expected', function () { + expect(checkStartTime({ start_date: '2000-01-01 99:99' })).to.be.false; + expect(checkStartTime({ start_date: '2000-01-01 13:99' })).to.be.false; + expect(checkStartTime({ start_date: '2000-01-01 24' })).to.be.false; + expect(checkStartTime({ start_date: '2000-01-01 13:99' })).to.be.false; + }); + }); +}); + +describe('className', function () { + describe('should', function () { + it('return expected results', function () { + expect(className({ start_date: '2000-01-01', end_date: '2000-01-02' })).to.equal('fc-multi-day'); + expect(className({ start_date: '2000-01-01 00:00', end_date: '2000-01-01 01:00' })).to.equal(''); + }); + it('handle errors as expected', function () { + expect(className({})).to.be.equal(''); + expect(className({ start_date: '2000' })).to.equal(''); + expect(className({ end_date: '2000' })).to.equal('fc-multi-day'); + expect(className({ start_date: '2000', end_date: '2001' })).to.equal(''); + expect(className({ start_date: '2000-10', end_date: '2001-11' })).to.equal('fc-multi-day'); + expect(className({ start_date: '2000-01-05', end_date: '2000-01-03' })).to.equal('fc-multi-day'); + }); + }); +}); + +// getGoogleUrl is being tested in tests/js/calendar.js +// getUrl is being tested in tests/js/calendar.js + +describe('mapCategoryDescription', function () { + describe('should', function () { + it('return expected results', function () { + expect(mapCategoryDescription('Election Dates')).to.be.equal('Federal elections. These include primary, general and special elections as well as caucuses and conventions.'); + }); + it('handle errors as expected', function () { + expect(mapCategoryDescription()).to.be.undefined; + expect(mapCategoryDescription('gibberish')).to.be.undefined; + expect(mapCategoryDescription(false)).to.be.undefined; + }); + }); +}); + +// These functions are for things that used to include Moment +describe('ElectionSearch', function () { + // From modules/election-search.js + // let prev = moment(upcomingElections[0].election_date, 'YYYY-MM-DD'); + // let repl = new Date(upcomingElections[0].election_date) + // moment(upcomingElections[0].election_date, 'YYYY-MM-DD'); + describe('formatGenericElectionDate', function() { + it('replacement should go smoothly', function() { + const result = { cycle: 2020 }; + let date = moment() + .year(result.cycle) + .month('November') + .date(1); + while (date.format('E') !== '1') { + date = date.add(1, 'day'); + } + let prev = date.add(1, 'day').format('MMMM Do, YYYY'); + // ^^ THAT'S OUR GOAL ^^ + + let repl; + + const newElectionDate = new Date(result.cycle, 10, 1); + // Looking at the days of the week, we want the first Tuesday of the month. + // Problem: getDay() is local and Sunday isn't always the first day of the week, so we'll look at UTCDay + let i = 0; + while (newElectionDate.getUTCDay() != 2 && i < 10) { + newElectionDate.setDate(newElectionDate.getDate() + 1); + i++; + } + // repl.setFullYear(result.cycle); + repl = newElectionDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); + const newDate = newElectionDate.getDate(); + if (newDate == 1) + repl = repl.replace(',', 'st,'); + else if (newDate == 2) + repl = repl.replace(',', 'nd,'); + else if (newDate == 3) + repl = repl.replace(',', 'rd,'); + else + repl = repl.replace(',', 'th,'); + + expect(repl).to.equal(prev); + + }); + }); + + // expect(mapCategoryDescription()).to.be.undefined; +}); diff --git a/fec/fec/tests/js/setup.js b/fec/fec/tests/js/setup.js index aa07620da3..2a16461542 100644 --- a/fec/fec/tests/js/setup.js +++ b/fec/fec/tests/js/setup.js @@ -1,9 +1,9 @@ -// export default function() { - // Append jQuery to `window` for use by legacy libraries - window.$ = window.jQuery = $; +// Append jQuery to `window` for use by legacy libraries +if (!window.$) window.$ = window.jQuery = $; - // Add global variables - global = Object.assign(global, { // eslint-disable-line no-global-assign +// Add global variables +/* + // global = Object.assign(global, { // eslint-disable-line no-global-assign BASE_PATH: '/', API_LOCATION: '', API_VERSION: '/v1', @@ -11,3 +11,18 @@ API_KEY_PUBLIC_CALENDAR: '67890', DEFAULT_TIME_PERIOD: '2016' }); +*/ + +let globalVars = { + BASE_PATH: '/', + API_LOCATION: '', //http://localhost:5000', + API_VERSION: 'v1', + API_KEY_PUBLIC: '12345', + API_KEY_PUBLIC_CALENDAR: '67890', + CALENDAR_DOWNLOAD_PUBLIC_API_KEY: '54321', + DEFAULT_TIME_PERIOD: '2016' +}; + +for (let n in globalVars) { + window[n] = globalVars[n]; +} diff --git a/package-lock.json b/package-lock.json index f0f4d539c3..7eb5ca0346 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,8 +27,8 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^5.11.5", - "@fullcalendar/daygrid": "^5.11.5", + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/list": "^5.11.5", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", @@ -63,7 +63,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.0.0", - "fullcalendar": "^3.3.1", + "fullcalendar": "^4.0.0-alpha.4", "glossary-panel": "^1.0.0", "graceful-fs": "^4.2.10", "gulp": "^4.0.2", @@ -2210,26 +2210,23 @@ } }, "node_modules/@fullcalendar/core": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-5.11.5.tgz", - "integrity": "sha512-M/WQuq1+uUHxFDEIu2ib/aaPZ70VsRk2ITECo/WCLSLTVWcHPXwEg83reyP3G8JrMM4gRL4vScEHhX0U5aoNSw==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", + "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", "dev": true, "license": "MIT", "dependencies": { - "@fullcalendar/common": "~5.11.5", - "preact": "~10.12.1", - "tslib": "^2.1.0" + "preact": "~10.12.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-5.11.5.tgz", - "integrity": "sha512-hMpq0U3Nucys2jDD+crbkJCr+tVt3fDw04OE3fbpisuzqtrHxIzRmnUOdbWUjJQyToAAkt7UVUQ9E7hYdmvyGA==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", + "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", "dev": true, "license": "MIT", - "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" } }, "node_modules/@fullcalendar/list": { @@ -4441,6 +4438,13 @@ "node": ">= 0.10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -7320,6 +7324,19 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -7613,6 +7630,13 @@ "node": ">= 0.6" } }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -8719,6 +8743,16 @@ "node": ">=6" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -11429,6 +11463,33 @@ "dev": true, "license": "BSD" }, + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -11517,14 +11578,17 @@ } }, "node_modules/fullcalendar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.3.1.tgz", - "integrity": "sha512-YmJ7CrwJJKRPchZcIfuId+qKAzCEwvAPQJAhEUdtiTytlVcmx0NDnGgNmrC6ZYxrwV9Ulqq5dtftOJdXQjfsJw==", + "version": "4.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-4.0.0-alpha.4.tgz", + "integrity": "sha512-6uup/KTSSlybpj3ntiYvDlpbU82Z9deeW9D8zO/MJLcsKD/i+I4kWEnoJLp1egZdAHq9xGsJ9PLTBU/xz6QqGw==", "dev": true, "license": "MIT", "dependencies": { - "jquery": "2 - 3", - "moment": "^2.9.0" + "luxon": "^1.10.0", + "moment": "^2.23.0", + "moment-timezone": "^0.5.23", + "rrule": "^2.6.0", + "superagent": "^3.8.3" } }, "node_modules/function-bind": { @@ -16474,6 +16538,16 @@ "es5-ext": "~0.10.2" } }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -16821,6 +16895,16 @@ "dev": true, "license": "MIT" }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", @@ -17529,6 +17613,19 @@ "node": "*" } }, + "node_modules/moment-timezone": { + "version": "0.5.46", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz", + "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/moo": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", @@ -20773,6 +20870,16 @@ "inherits": "^2.0.1" } }, + "node_modules/rrule": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", + "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -22584,6 +22691,52 @@ "minimist": "^1.1.0" } }, + "node_modules/superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 251cd518b2..a713be77e5 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^5.11.5", - "@fullcalendar/daygrid": "^5.11.5", + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/list": "^5.11.5", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", @@ -78,7 +78,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.0.0", - "fullcalendar": "^3.3.1", + "fullcalendar": "^4.0.0-alpha.4", "glossary-panel": "^1.0.0", "graceful-fs": "^4.2.10", "gulp": "^4.0.2", From f08d4d2018a49016fafd7f2a6464bed1953b3ee4 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 3 Jan 2025 09:52:37 -0500 Subject: [PATCH 2/4] wip --- fec/fec/static/js/modules/calendar-helpers.js | 57 ++- .../static/js/modules/calendar-list-view.js | 167 ++++++- fec/fec/static/js/modules/calendar.js | 412 +++++++++++----- fec/fec/static/js/pages/calendar-page.js | 11 +- .../static/js/templates/calendar/event.hbs | 53 ++ .../static/js/templates/calendar/events.hbs | 3 +- fec/fec/static/scss/components/_calendar.scss | 186 ++++--- fec/fec/templates/base.html | 2 +- fec/fec/tests/js/calendar.js | 4 +- fec/gulpfile.js | 4 +- fec/webpack.config.cjs | 5 +- package-lock.json | 459 ++++++++++-------- package.json | 14 +- 13 files changed, 972 insertions(+), 405 deletions(-) create mode 100644 fec/fec/static/js/templates/calendar/event.hbs diff --git a/fec/fec/static/js/modules/calendar-helpers.js b/fec/fec/static/js/modules/calendar-helpers.js index 34380eaebf..96da737596 100644 --- a/fec/fec/static/js/modules/calendar-helpers.js +++ b/fec/fec/static/js/modules/calendar-helpers.js @@ -1,9 +1,31 @@ +/* eslint-disable */ /** * */ import moment from 'moment'; import { default as URI } from 'urijs'; +function dateString(dateToParse, format, adjustmentHours) { + const parsed = new Date(Date.parse(dateToParse)); + + if (adjustmentHours) parsed.setHours(parsed.getHours() + adjustmentHours); + + let YYYY = parsed.getUTCFullYear(); + let MM = (parsed.getUTCMonth() + 1).toString().padStart(2, '0'); // Months are 0-indexed + let DD = (parsed.getUTCDate()).toString().padStart(2, '0'); + let hh = (parsed.getUTCHours()).toString().padStart(2, '0'); + let mm = (parsed.getUTCMinutes()).toString().padStart(2, '0'); + let ss = (parsed.getUTCSeconds()).toString().padStart(2, '0'); + + if (format == 'YYYYMMDD') + return `${YYYY}${MM}${DD}`; + + else if (format == 'YYYYMMDD[T]HHmmss') + return `${YYYY}${MM}${DD}T${hh}${mm}${ss}`; + + return dateToParse; +} + /** * @param {Object} event - {start: '', end: '', title: '', summary: ''} * @param {Moment} [event.start] @@ -14,18 +36,31 @@ import { default as URI } from 'urijs'; */ export function getGoogleUrl(event) { let fmt, dates; - if (event.end) { - fmt = 'YYYYMMDD[T]HHmmss'; - dates = event.start.format(fmt) + '/' + event.end.format(fmt); + // TODO: remove this .format check after Moment has been removed + if (event.start.format) { + // If we're dealing with a Moment date (i.e. it has a .format() ) + if (event.end) { + fmt = 'YYYYMMDD[T]HHmmss'; + dates = event.start.format(fmt) + '/' + event.end.format(fmt); + } else { + fmt = 'YYYYMMDD'; + dates = + event.start.format(fmt) + + '/' + + event.start + .clone() + .add(1, 'day') + .format(fmt); + } } else { - fmt = 'YYYYMMDD'; - dates = - event.start.format(fmt) + - '/' + - event.start - .clone() - .add(1, 'day') - .format(fmt); + // Otherwise, if we have a native date + + if (event.end) { + dates = `${dateString(event.start, 'YYYYMMDD[T]HHmmss')}/${dateString(event.end, 'YYYYMMDD[T]HHmmss')}`; + + } else { + dates = `${dateString(event.start, 'YYYYMMDD')}/${dateString(event.start, 'YYYYMMDD', 24)}`; + } } return URI('https://calendar.google.com/calendar/render') .addQuery({ diff --git a/fec/fec/static/js/modules/calendar-list-view.js b/fec/fec/static/js/modules/calendar-list-view.js index c0372cb9ea..34d8c633f1 100644 --- a/fec/fec/static/js/modules/calendar-list-view.js +++ b/fec/fec/static/js/modules/calendar-list-view.js @@ -1,8 +1,11 @@ +/* eslint-disable */ /** * */ -import { Calendar } from '@fullcalendar/core'; // import $ from 'jquery'; +import { createPlugin, sliceEvents, Calendar } from '@fullcalendar/core'; +import listPlugin from '@fullcalendar/list'; +// import { ViewRoot } from '@fullcalendar/common'; import { default as moment } from 'moment'; import { default as _chain } from 'underscore/modules/chain.js'; import { default as _each } from 'underscore/modules/each.js'; @@ -10,10 +13,11 @@ import { default as _pairs } from 'underscore/modules/pairs.js'; import { default as _reduce } from 'underscore/modules/reduce.js'; import Dropdown from './dropdowns.js'; -import { default as eventTemplate } from '../templates/calendar/events.hbs'; +import { default as eventTemplate } from '../templates/calendar/event.hbs'; +import { default as eventsTemplate } from '../templates/calendar/events.hbs'; -const FC = $.fullCalendar; -const View = FC.View; +// const FC = $.fullCalendar; +// const View = FC.View; // 'Sort by: Category' view // Property name is the category @@ -101,13 +105,128 @@ const chronologicalGroups = function(events, start, end) { return events; }; -const ListView = View.extend({ - setDate: function(date) { - const intervalUnit = this.options.duration.intervalUnit || this.intervalUnit; - View.prototype.setDate.call(this, date.startOf(intervalUnit)); +// const ListView = View.extend({ +const customViewConfig = { + classNames: ['fec-list-view'],//'fec-list-VIEW'], + + /** + * @param {Object} d + * @param {Object} d + * @param {Function} (callback?) + * @returns {Object} {html: ''} + */ + content: function(props) { + + + // TODO: NEXT STEPS + Working on sending vars like settings from the config option into this plugin so the events.hbs template can use them + + + console.log('CustomViewConfig.content(props): ', props); + console.log(' this: ', this); + // console.log(' self: ', self); + // console.log(' self.window: ', self.window); + console.log(' root self var: ', window.calendar.view.customProps); + // console.log(' self.window.calendar: ', self.window.calendar); + // console.log(' self.calendar.view: ', self.window.calendar.view); + // let segs = sliceEvents(props, true); + // console.log(' segs, true: ', segs); + let segs = sliceEvents(props, false); + console.log(' segs, false: ', segs); + let html = ''; + + // const groups = this.options.categories + // ? categoryGroups(events, this.start, this.end) + // : chronologicalGroups(events, this.start, this.end); + // const settings = { + // duration: this.options.duration.intervalUnit, + // sortBy: this.options.sortBy + // }; + + const eventsObj = { + sortyBy: 'as' + }; + + + + return { html: eventsTemplate({ + + }) }; }, + // render: function(d) { + // console.log('CustomViewConfig.render(d): ', d); + // }, + /** + * @param {Object} d + * @param {Date} d.date + * @param {ViewImpl} d.view + * @returns {Object} `{html: ''}` + */ + dayHeaderContent: function(d, two) { + console.log('CustomViewConfig.dayHeaderContent(d, two): ', d, two); + return { html: `` }; + }, + + /** + * + * @param {Object} d + * @param {Object} d.dateProfile + * @param {HTMLElement} d.el + * @param {Object} d.eventStore + * @param {Object} d.eventUiBases + * @param {Function} [callback] + */ + didMount: function(props, callback) { + console.log('CustomViewConfig.didMount(props): ', props); + self.window.calendar.view.customProps = {self: this}; + console.log(' this: ', this); + }, + /*eventContent: function(arg) { + console.log('CustomViewConfig.eventContent(arg): ', arg); + let toReturn = ''; + const groups = this.options.categories + ? categoryGroups(events, this.start, this.end) + : chronologicalGroups(events, this.start, this.end); + const settings = { + duration: this.options.duration.intervalUnit, + sortBy: this.options.sortBy + }; + + this.el.html(eventsTemplate({ groups: groups, settings: settings })); + + + return {html: toReturn}; + // templates.details( + // Object.assign({}, calEvent, { detailsId: this.detailsId }) + // { + // detailsId: , + // category: , + // allDay: , + // summary: , + // location: , + // description: , + // detailUrl: , + // download: , + // google: , + // } + // ) + // }; + },*/ + + + + // Deprecated + // setDate: function(date) { + // console.log('CustomViewConfig.setDate(props): ', props); + // const intervalUnit = this.options.duration.intervalUnit || this.intervalUnit; + // ViewRoot.prototype.setDate.call(this, date.startOf(intervalUnit)); + // }, + + // Deprecated + /* renderEvents: function(events) { + console.log('CustomViewConfig.renderEvents(events): ', events); const groups = this.options.categories ? categoryGroups(events, this.start, this.end) : chronologicalGroups(events, this.start, this.end); @@ -116,20 +235,44 @@ const ListView = View.extend({ sortBy: this.options.sortBy }; - this.el.html(eventTemplate({ groups: groups, settings: settings })); + this.el.html(eventsTemplate({ groups: groups, settings: settings })); this.dropdowns = $(this.el.html) .find('.dropdown') .map(function(idx, elm) { return new Dropdown($(elm), { checkboxes: false }); }); - }, + },*/ + // Deprecated + /* unrenderEvents: function() { + console.log('CustomViewConfig.unrenderEvents(props): ', props); this.dropdowns.each(function(idx, dropdown) { dropdown.destroy(); }); this.el.html(''); + }*/ +}; + + +/** + * + * @param {Object} d + * @returns {string} + */ +export function eventHtmlString(d) { + console.log('calendar-list-view.eventHtmlString(d): ', d); + return '

YES!

'; +} + + + + +const CustomViewConfig = customViewConfig; + + +export default createPlugin({ + views: { + custom: CustomViewConfig } }); - -FC.views.list = ListView; diff --git a/fec/fec/static/js/modules/calendar.js b/fec/fec/static/js/modules/calendar.js index bdda2ea1cb..1e09bbcf5d 100644 --- a/fec/fec/static/js/modules/calendar.js +++ b/fec/fec/static/js/modules/calendar.js @@ -1,31 +1,40 @@ +/* eslint-disable */ /** * */ +import { Calendar as FullCalendar } from '@fullcalendar/core'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import listPlugin from '@fullcalendar/list'; +import timeGridPlugin from '@fullcalendar/timegrid'; +import bootstrap5Plugin from '@fullcalendar/bootstrap5'; // Being included because fullcalendar requires a themesystem and the default is too bossy +// import '@fullcalendar/core'; +// new Calendar(); +// import '@fullcalendar/common/vdom'; import { default as Handlebars } from 'hbsfy/runtime.js'; import $ from 'jquery'; -import moment from 'moment'; +// import moment from 'moment'; import { default as _extend } from 'underscore/modules/extend.js'; import { default as _isEqual } from 'underscore/modules/isEqual.js'; import { default as URI } from 'urijs'; -import 'fullcalendar'; - import { checkStartTime, className, getGoogleUrl, mapCategoryDescription } from './calendar-helpers.js'; import { CalendarTooltip } from './calendar-tooltip.js'; import Dropdown from './dropdowns.js'; import { LOADING_DELAY, SUCCESS_DELAY, datetime, eq, isLargeScreen, toUpperCase } from './helpers.js'; import { pushQuery, updateQuery } from './urls.js'; -import './calendar-list-view.js'; +import { eventHtmlString } from './calendar-list-view.js'; +import { default as CustomViewPlugin } from './calendar-list-view.js'; import { default as template_details } from '../templates/calendar/details.hbs'; import { default as template_download } from '../templates/calendar/download.hbs'; import { default as template_listToggles } from '../templates/calendar/listToggles.hbs'; import { default as template_subscribe } from '../templates/calendar/subscribe.hbs'; // TODO: do we need to registerHelper? -Handlebars.registerHelper('eq', eq); -Handlebars.registerHelper('datetime', datetime); -Handlebars.registerHelper('toUpperCase', toUpperCase); +// Handlebars.registerHelper('eq', eq); +// Handlebars.registerHelper('datetime', datetime); +// Handlebars.registerHelper('toUpperCase', toUpperCase); +/** @enum {html} */ const templates = { details: template_details, download: template_download, @@ -35,26 +44,34 @@ const templates = { const LIST_VIEWS = ['monthTime', 'monthCategory']; -const FC = $.fullCalendar; -const Grid = FC.Grid; +// const FC = $.fullCalendar; +// const Grid = FC.Grid; // Globally override event sorting to order all-day events last // TODO: Convince fullcalendar.io support this behavior without monkey-patching -Grid.prototype.compareEventSegs = function(seg1, seg2) { - return ( - seg1.event.allDay - seg2.event.allDay || // put all-day events last (booleans cast to 0/1) - seg1.eventStartMS - seg2.eventStartMS || // tie? earlier events go first - seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first - FC.compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs) - ); -}; +// Grid.prototype.compareEventSegs = function(seg1, seg2) { +// return ( +// seg1.event.allDay - seg2.event.allDay || // put all-day events last (booleans cast to 0/1) +// seg1.eventStartMS - seg2.eventStartMS || // tie? earlier events go first +// seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first +// FC.compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs) +// ); +// }; export default function Calendar(opts) { - this.opts = $.extend({}, this.defaultOpts(), opts); - - this.$calendar = $(this.opts.selector); - this.$head = $('.data-container__head'); - this.$calendar.fullCalendar(this.opts.calendarOpts); + this.trashname = Calendar.name; + // this.opts = $.extend({}, this.defaultOpts(), opts); + this.opts = Object.assign({}, this.defaultOpts(), opts); + + // this.$calendar = $(this.opts.selector); + console.log('this.opts.selector: ', this.opts.selector); + this.calendarEl = document.querySelector(this.opts.selector); + // this.$head = $('.data-container__head'); + this.headEl = document.querySelector('.data-container__head'); + // this.$calendar.fullCalendar(this.opts.calendarOpts); + // console.log('this.calendarEl: ', this.calendarEl); + this.calendar = new FullCalendar(this.calendarEl, this.opts.calendarOpts); + window.calendar = this.calendar; this.url = URI(this.opts.url); this.subscribeUrl = URI(this.opts.subscribeUrl); this.exportUrl = URI(this.opts.exportUrl); @@ -64,94 +81,181 @@ export default function Calendar(opts) { this.popoverId = 'calendar-popover'; this.detailsId = 'calendar-details'; - this.sources = null; + this.eventSources = null; this.params = null; - this.$download = $(opts.download); - this.$subscribe = $(opts.subscribe); - - this.$calendar.on('click', '.js-toggle-view', this.toggleListView.bind(this)); + // this.$download = $(opts.selector_download); + this.downloadEl = document.querySelector(opts.selector_download); + // this.$subscribe = $(opts.selector_subscribe); + this.subscribeEl = document.querySelector(opts.selector_subscribe); + + // this.$calendar.on('click', '.js-toggle-view', this.toggleListView.bind(this)); + const toggleView = this.calendarEl.querySelector('.js-toggle-view'); + // toggleView.addEventListener('click', this.toggleListView.bind(this)); + + // this.$calendar.on( + // 'keypress', + // '.fc-event, .fc-more, .fc-close', + // this.simulateClick.bind(this) + // ); + const keypressElements = document.querySelectorAll('.fc-event, .fc-more, .fc-close'); + keypressElements.forEach(el => { + el.addEventListener('keypress', this.simulateClick.bind(this)); + }); - this.$calendar.on( - 'keypress', - '.fc-event, .fc-more, .fc-close', - this.simulateClick.bind(this) - ); - this.$calendar.on('click', '.fc-more', this.managePopoverControl.bind(this)); + // this.$calendar.on('click', '.fc-more', this.managePopoverControl.bind(this)); + // this.calendarEl.querySelector('.fc-more').addEventListener('click', this.managePopoverControl.bind(this)); - this.filterPanel.$form.on('change', this.filter.bind(this)); - $(window).on('popstate', this.filter.bind(this)); + // this.filterPanel.$form.on('change', this.filter.bind(this)); + // this.filterPanel.$form.addEventListener('change', this.filter.bind(this)); + // $(window).on('popstate', this.filter.bind(this)); + window.addEventListener('popstate', this.filter.bind(this)); updateQuery(this.filterSet.serialize(), this.filterSet.fields); this.filter(); - this.styleButtons(); if (!isLargeScreen()) { - this.$head.after($('#filters')); + // this.$head.after($('#filters')); + this.headEl.after(document.querySelector('#filters')); } + + // Make it exist + this.calendar.render(); + + this.styleButtons(); } Calendar.prototype.toggleListView = function(e) { - const newView = $(e.target).data('trigger-view'); - this.$calendar.fullCalendar('changeView', newView); + console.log('Calendar.toggleListView(e): ', e); + // const newView = $(e.target).data('trigger-view'); + const newView = e.target.dataset.triggerView; + // this.$calendar.fullCalendar('changeView', newView); + this.calendar.changeView(newView); }; +/** + * + */ Calendar.prototype.defaultOpts = function() { return { calendarOpts: { - header: { + headerToolbar: { //header: { left: 'prev,next,today', center: '', - right: 'monthTime,month' + right: 'monthTime,month', + // right: 'dayGridMonth,timeGridWeek,listWeek' }, - buttonIcons: false, - buttonText: { + buttonIcons: false, // The icons fc should put on buttons + buttonText: { // The text labels for buttons today: 'This Month', - week: 'Week' + // next: '', + // prev: '' + // week: 'Week YAY' }, - contentHeight: 'auto', - dayRender: this.handleDayRender.bind(this), - dayPopoverFormat: 'MMMM D, YYYY', - defaultView: 'monthTime', - eventRender: this.handleEventRender.bind(this), - eventAfterAllRender: this.handleRender.bind(this), - eventClick: this.handleEventClick.bind(this), - eventLimit: true, - nowIndicator: true, + // contentHeight: 'auto', + // dayRender: this.handleDayRender.bind(this), + // dayPopoverFormat: 'MMMM D, YYYY', + initialView: 'custom',//'listMonth' + themeSystem: 'bootstrap5', // of the three choices, bootstrap5 is the lowest impact + // eventRender: this.handleEventRender.bind(this), + // eventAfterAllRender: this.handleRender.bind(this), + // eventClick: this.handleEventClick.bind(this), + // success: this.success.bind(this), + // failure: this.failure.bind(this), + datesSet: this.datesSet.bind(this), + // eventLimit: true, + // nowIndicator: true, + // plugins: [customViewPlugin], + plugins: [dayGridPlugin, timeGridPlugin, listPlugin, bootstrap5Plugin, CustomViewPlugin], //CustomViewPlugin views: { - agenda: { - scrollTime: '09:00:00', - minTime: '08:00:00', - maxTime: '20:00:00' - }, + // dayGridMonth: {}, + // timeGridWeek: {}, + // dayGrid: {}, + // timeGrid: {}, + // week: {}, + // day: {} + // agenda: { + // scrollTime: '09:00:00', + // minTime: '08:00:00', + // maxTime: '20:00:00' + // }, month: { - eventLimit: 3, + // eventLimit: 3, buttonText: 'Grid' }, - monthCategory: { - type: 'list', - categories: true, - sortBy: 'category', - duration: { months: 1, intervalUnit: 'month' } - }, + // monthCategory: { + // type: 'list', + // categories: true, + // sortBy: 'category', + // duration: { months: 1, intervalUnit: 'month' } + // }, monthTime: { type: 'list', buttonText: 'List', sortBy: 'time', duration: { months: 1, intervalUnit: 'month' } - } - } + }, + custom: { + // listMonth: { + type: 'list', + // categories: true, + buttonText: 'List', + // sortBy: 'time', + // scopeThis: this, + // props: {this: this}, + duration: { months: 1, intervalUnit: 'month' }, + viewDidMount: function(arg) { + console.log('views.custom.viewDidMount(arg): ', arg); + }, + viewWillUnmount: function(arg) { + console.log('views.custom.viewWillUnmount(arg): ', arg); + }, + // dayHeaderContent: function(d) { + // console.log('views.custom.dayHeaderContent(arg): ', d); + // return { html: `` }; + // }, + // eventContent: function(arg) { + // console.log('views.custom.eventContent(arg): ', arg); + // console.log(' this: ', this); + // return {html: eventHtmlString(arg)}; + // }, + eventDataTransform: function(arg) { + console.log('views.custom.eventDataTransform(arg): ', arg); + // return {html: eventHtmlString(arg)}; + } + }, + // noEventsDidMount: function(info) { + // console.log('noEventsDidMount(info): ', info); + // } + }, + dayHeaderContent: function(d) { + console.log('Calendar.dayHeaderContent(arg): ', d); + return { html: `` }; + }, + eventContent: function(arg) { + console.log('Calendar.eventContent(arg): ', arg); + }, + // render: function(arg) { + // console.log('Calendar.render(arg): ', arg); + // }, + // onRender: function(arg) { + // console.log('Calendar.onRender(arg): ', arg); + // } }, sourceOpts: { startParam: 'min_start_date', endParam: 'max_start_date', - success: this.success.bind(this) + initialDate: '2024-12-15', + success: this.success.bind(this), + failure: this.failure.bind(this), + datesSet: this.datesSet.bind(this) } }; }; Calendar.prototype.filter = function() { + console.log('Calendar.filter()'); const params = this.filterSet.serialize(); if (_isEqual(params, this.params)) { return; @@ -160,28 +264,75 @@ Calendar.prototype.filter = function() { .clone() .addQuery(params || {}) .toString(); + + console.log(' url: ', url); pushQuery(this.filterSet.serialize(), this.filterSet.fields); - this.$calendar.fullCalendar('removeEventSource', this.sources); - this.sources = $.extend({}, this.opts.sourceOpts, { url: url }); - this.$calendar.fullCalendar('addEventSource', this.sources); + // this.$calendar.fullCalendar('removeEventSource', this.eventSources); + if (this.eventSources) this.eventSources.remove(); + // this.eventSources = $.extend({}, this.opts.sourceOpts, { url: url }); + this.eventSources = Object.assign( + {}, + this.opts.sourceOpts, + { + url: url, + // success: this.success.bind(this), + // failure: this.failure.bind(this), + // dayRender: this.handleDayRender.bind(this), + // eventRender: this.handleEventRender.bind(this), + // eventAfterAllRender: this.handleRender.bind(this), + // eventClick: this.handleEventClick.bind(this) + } + ); + // this.$calendar.fullCalendar('addEventSource', this.eventSources); + this.calendar.addEventSource(this.eventSources); this.updateLinks(params); this.params = params; + + // this.calendar.render(); }; -Calendar.prototype.success = function(response) { +Calendar.prototype.datesSet = function(v1) { + v1.startStr = v1.startStr.slice(0,10); + v1.endStr = v1.endStr.slice(0,10); +}; + +Calendar.prototype.failure = function(v1) { + console.log('failure(v1): ', v1); +}; + +/** + * + * @param {Object} content + * @param {Response} response + * @returns + */ +Calendar.prototype.success = function(content, response) { + console.log('Calendar.success(content, response): ', content, response); const self = this; setTimeout(function() { - $('.is-loading') - .removeClass('is-loading') - .addClass('is-successful'); + // $('.is-loading') + // .removeClass('is-loading') + // .addClass('is-successful'); + // }, LOADING_DELAY); + const elements = document.querySelectorAll('.is-loading'); + elements.forEach(el => { + el.classList.remove('is-loading'); + el.classList.add('is-successful'); + }); }, LOADING_DELAY); setTimeout(function() { - $('.is-successful').removeClass('is-successful'); + // $('.is-successful').removeClass('is-successful'); + const elements = document.querySelectorAll('is-successful'); + elements.forEach(el => { + el.classList.remove('is-successful'); + }); }, SUCCESS_DELAY); - return response.results.map(function(event) { + self.handleRender(self.calendar.view); + + return content.results.map(function(event) { let processed = { category: event.category, location: event.location, @@ -189,26 +340,26 @@ Calendar.prototype.success = function(response) { summary: event.summary || 'Event summary', description: event.description || 'Event description', state: event.state ? event.state.join(', ') : null, - start: event.start_date ? moment(event.start_date) : null, + start: event.start_date, hasStartTime: checkStartTime(event), - end: event.end_date ? moment(event.end_date) : null, + end: event.end_date, className: className(event), tooltipContent: mapCategoryDescription(event.category), allDay: event.all_day, detailUrl: event.url }; - _extend(processed, { - google: getGoogleUrl(processed), - download: self.subscribeUrl - .clone() - .addQuery({ event_id: event.event_id }) - .toString() - }); + processed.google = getGoogleUrl(processed); + processed.download = self.subscribeUrl + .clone() + .addQuery({ event_id: event.event_id }) + .toString(); + return processed; }); }; Calendar.prototype.updateLinks = function(params) { + console.log('Calendar.updateLinks(params): ', params); const url = this.exportUrl.clone().addQuery(params || {}); const subscribeURL = this.subscribeUrl.clone().addQuery(params || {}); const urls = { @@ -239,8 +390,10 @@ Calendar.prototype.updateLinks = function(params) { ), calendarSubscribe: subscribeURL.protocol('webcal').toString() }; - this.$download.html(templates.download(urls)); - this.$subscribe.html(templates.subscribe(urls)); + // this.$download.html(templates.download(urls)); + this.downloadEl.innerHTML = templates.download(urls); + // this.$subscribe.html(templates.subscribe(urls)); + this.subscribeEl.innerHTML = templates.subscribe(urls); if (this.downloadButton) { this.downloadButton.destroy(); @@ -250,51 +403,75 @@ Calendar.prototype.updateLinks = function(params) { this.subscribeButton.destroy(); } - this.downloadButton = new Dropdown(this.$download, { + this.downloadButton = new Dropdown(this.opts.selector_download, { checkboxes: false }); - this.subscribeButton = new Dropdown(this.$subscribe, { + this.subscribeButton = new Dropdown(this.opts.selector_subscribe, { checkboxes: false }); }; +/** + * Adds the fec-specific class names to fc elements + */ Calendar.prototype.styleButtons = function() { - const baseClasses = 'button'; - this.$calendar.find('.fc-button').addClass(baseClasses); - this.$calendar.find('.fc-today-button').addClass('button--alt'); - this.$calendar - .find('.fc-next-button') - .addClass('button--next button--standard'); - this.$calendar - .find('.fc-prev-button') - .addClass('button--previous button--standard'); - this.$calendar - .find('.fc-right .fc-button-group') - .addClass('toggles--buttons'); - this.$calendar - .find('.fc-monthTime-button') - .addClass('button--list button--alt'); - this.$calendar.find('.fc-month-button').addClass('button--grid button--alt'); + const baseClasses = ['button']; + // document.querySelector('.fc-button').classList.add(...baseClasses); + // document.querySelectorAll('.fc-button').forEach(el => { el.classList.add(...baseClasses) }); + document.querySelector('.fc-toolbar-start .fc-today-button').classList.add('button', 'button--alt'); + document.querySelector('.fc-toolbar-start .fc-next-button').classList.add( + 'button', 'button--next', 'button--standard'); + document.querySelector('.fc-toolbar-start .fc-prev-button').classList.add( + 'button', 'button--previous', 'button--standard'); + document.querySelector('.fc-toolbar-end .btn-group').classList.add('toggles--buttons'); + document.querySelector('.fc-toolbar-end .fc-monthTime-button').classList.add( + 'button', 'button--list', 'button--alt'); + document.querySelector('.fc-toolbar-end .fc-month-button').classList.add('button', 'button--grid', 'button--alt'); }; +/** + * + * @param {ViewImpl} view + * No other params + */ Calendar.prototype.handleRender = function(view) { + console.log('Calendar.handleRender(view, two): ', view); + console.log(' this: ', this); + $(document.body).trigger($.Event('calendar:rendered')); - if (LIST_VIEWS.indexOf(view.name) !== -1) { + + // if (LIST_VIEWS.indexOf(view.name) !== -1) { + if (LIST_VIEWS.indexOf(view.type) !== -1) { + console.log(' if'); this.manageListToggles(view); + } else if (this.$listToggles) { + console.log(' else if'); this.$listToggles.remove(); this.$listToggles = null; + } else { + console.log(' else'); } - this.$calendar - .find('.fc-more') - .attr({ tabindex: '0', 'aria-describedby': this.popoverId }); - this.$head.find('.js-calendar-title').html(view.title); + // this.$calendar + // .find('.fc-more') + // .attr({ tabindex: '0', 'aria-describedby': this.popoverId }); + const fcMore = this.calendarEl.querySelector('.fc-more'); + if (fcMore) fcMore.setAttribute('tabindex', 0); + if (fcMore) fcMore.setAttribute('aria-describedby', this.popoverId); + + // Move 'Month YYYY' to be the page title, not just the view title + document.querySelector('.js-calendar-title').innerHTML = view.title; }; +/** + * + * @param {*} view + */ Calendar.prototype.manageListToggles = function(view) { + console.log('Calendar.manageListToggles(view): ', view); if (!this.$listToggles) { this.$listToggles = $('
'); - this.$listToggles.appendTo(this.$calendar.find('.fc-right')); + this.$listToggles.appendTo(this.$calendar.find('.fc-toolbar-end')); } this.$listToggles.html(templates.listToggles(view.options)); // Highlight the "List" button on monthTime @@ -304,6 +481,7 @@ Calendar.prototype.manageListToggles = function(view) { }; Calendar.prototype.handleEventRender = function(event, element) { + console.log('Calendar.handleEventRender(event, element): ', event, element); const eventLabel = event.title + ' ' + @@ -318,12 +496,14 @@ Calendar.prototype.handleEventRender = function(event, element) { }; Calendar.prototype.handleDayRender = function(date, cell) { + console.log('Calendar.handleDayRender(date, cell): ', date, cell); if (date.date() === 1) { cell.append(date.format('MMMM')); } }; Calendar.prototype.handleEventClick = function(calEvent, jsEvent) { + console.log('Calendar.handleEventClick(calEvent, jsEvent): ', calEvent, jsEvent); const $target = $(jsEvent.target); if (!$target.closest('.tooltip').length) { const $eventContainer = $target.closest('.fc-event'); @@ -338,20 +518,22 @@ Calendar.prototype.handleEventClick = function(calEvent, jsEvent) { // Simulate clicks when hitting enter on certain full-calendar elements Calendar.prototype.simulateClick = function(e) { + console.log('Calendar.simulateClick(e): ', e); if (e.keyCode === 13) { - $(e.target).click(); // TODO: jQuery deprecation + $(e.target).trigger('click'); } }; Calendar.prototype.managePopoverControl = function(e) { + console.log('Calendar.managePopoverControl(e): ', e); const $target = $(e.target); const $popover = this.$calendar.find('.fc-popover'); $popover.attr('id', this.popoverId).attr('role', 'tooltip'); $popover .find('.fc-close') .attr('tabindex', '0') - .focus() // TODO: jQuery deprecation + .trigger('focus') .on('click', function() { - $target.focus(); // TODO: jQuery deprecation + $target.trigger('focus'); }); }; diff --git a/fec/fec/static/js/pages/calendar-page.js b/fec/fec/static/js/pages/calendar-page.js index 7a4fe7a498..702feed812 100644 --- a/fec/fec/static/js/pages/calendar-page.js +++ b/fec/fec/static/js/pages/calendar-page.js @@ -1,6 +1,9 @@ +/* eslint-disable */ /** * */ +import { Calendar as FullCalendar } from '@fullcalendar/core'; + import { calendarDownload, getUrl } from '../modules/calendar-helpers.js'; import Calendar from '../modules/calendar.js'; import FilterPanel from '../modules/filters/filter-panel.js'; @@ -20,12 +23,14 @@ $('.js-filter-tags').prepend($tagList); // Initialize calendar new Calendar({ selector: '#calendar', - download: '#calendar-download', - subscribe: '#calendar-subscribe', + selector_download: '#calendar-download', + selector_subscribe: '#calendar-subscribe', url: getUrl(['calendar-dates']), + // url: '/static/calendar-dates.json', exportUrl: calendarDownload(['calendar-dates', 'export']), subscribeUrl: getUrl(['calendar-dates', 'export'], '', [ 'sub' ]), - filterPanel: filterPanel + filterPanel: filterPanel, + initialDate: '2024-12-15', }); diff --git a/fec/fec/static/js/templates/calendar/event.hbs b/fec/fec/static/js/templates/calendar/event.hbs new file mode 100644 index 0000000000..9e14896f09 --- /dev/null +++ b/fec/fec/static/js/templates/calendar/event.hbs @@ -0,0 +1,53 @@ +
+
+
+ {{datetime start format="pretty"}} + {{datetime start format="fullDayOfWeek"}} +
+
+ {{#if hasStartTime}} + {{datetime start format="time"}} + {{#if end}}–{{datetime end format="time"}}{{/if}} + {{else}} +   + {{/if}} +
+
+

{{summary}}

+

{{description}}

+ {{#if location}} + Location: {{location}} + {{/if}} + {{#if detailUrl}} + Learn more + {{/if}} +
+
+ {{#if tooltipContent }} +
+ {{ toUpperCase category }} +
+ + +
+
+ {{else}} + {{ toUpperCase category }} + {{/if}} +
+
+
+ +
+
diff --git a/fec/fec/static/js/templates/calendar/events.hbs b/fec/fec/static/js/templates/calendar/events.hbs index 5f0acd065d..2ef1bc78c7 100644 --- a/fec/fec/static/js/templates/calendar/events.hbs +++ b/fec/fec/static/js/templates/calendar/events.hbs @@ -2,7 +2,8 @@ {{#each groups}}
{{#if title}} -

{{title}}

+ {{!--

{{title}}

--}} + {{/if}} {{#each events}}
diff --git a/fec/fec/static/scss/components/_calendar.scss b/fec/fec/static/scss/components/_calendar.scss index f787163d29..294efad624 100644 --- a/fec/fec/static/scss/components/_calendar.scss +++ b/fec/fec/static/scss/components/_calendar.scss @@ -3,6 +3,82 @@ // Styles for the Calendar page, which uses fullcalendar // +.calendar__head { + border-bottom: 1px solid $neutral; + padding: u(1rem); + + @include clearfix(); + + .data-container__action { + margin-top: u(1rem); + width: 100%; + + @include media($lg) { + width: auto; + float: right; + margin-top: 0; + } + } +} + +.fc { + display: block; // Overriding fc defaults + + .fc-view-outer { + padding: 0 2rem 2rem !important; + position: relative; + + .fc-view { + display: block; + position: relative; + } + } + + // Controls + .fc-toolbar { + // @include clearfix(); + padding: u(1rem); + } + + .fc-toolbar-start { + .fc-prev-button, + .fc-next-button { + background-position: center; + margin-right: 1px; + padding: u(.8rem 1rem); + text-indent: -9999px; + + &::after { + left: u(.5rem); + right: u(.5rem); + } + } + .fc-today-button { + margin-left: u(1rem); + } + } + + .fc-toolbar-end { + .button--alt { + padding: 8px 2rem; + text-indent: -9999px; + width: 4rem; + } + } + + .fc-list-day { + display: block; + padding: 0; + } +} + +@include media($med) { + .calendar__head { + padding: u(1rem 2rem); + } +} + +/* .fc-view-container { font-family: $sans-serif; padding: u(0 2rem 2rem 2rem); @@ -17,7 +93,7 @@ .fc-day { font-weight: bold; - padding: u(0.7rem 1rem 0 2rem); + padding: u(.7rem 1rem 0 2rem); } } @@ -25,16 +101,10 @@ position: relative; } -// Controls -.fc-toolbar { - padding: u(1rem); - @include clearfix; -} - .calendar__head { + @include clearfix(); border-bottom: 1px solid $neutral; padding: u(1rem); - @include clearfix; .data-container__action { margin-top: u(1rem); @@ -49,12 +119,12 @@ } .fc-view-controls { + @include clearfix(); padding: u(2rem); width: 100%; - @include clearfix; } -.fc-left { +.fc-toolbar-start { float: left; .fc-button-group { @@ -70,11 +140,11 @@ .fc-next-button { margin-right: 1px; text-indent: -9999px; - padding: u(0.8rem 1rem); + padding: u(.8rem 1rem); &::after { - left: u(0.5rem); - right: u(0.5rem); + left: u(.5rem); + right: u(.5rem); } } } @@ -91,7 +161,7 @@ @extend .button--alt, .is-active; } -.fc-right { +.fc-toolbar-end { display: none; } @@ -108,7 +178,7 @@ .fc-day-header { color: $primary; font-weight: normal; - padding-top: u(0.5rem); + padding-top: u(.5rem); } .fc-content-skeleton { @@ -122,7 +192,7 @@ .fc-other-month { &.fc-day-top, &.fc-day { - opacity: 0.5; + opacity: .5; } } @@ -144,7 +214,7 @@ border-bottom: none; color: $primary; font-weight: bold; - padding: u(0.3rem 1rem); + padding: u(.3rem 1rem); } .fc-day-number.fc-today { @@ -174,15 +244,16 @@ &::before { content: ''; display: block; - width: u(0.6rem); - height: u(0.6rem); + width: u(.6rem); + height: u(.6rem); background-color: $primary; - border-radius: u(0.8rem); + border-radius: u(.8rem); float: left; margin: 5px 5px 5px 0; } &::after { + @include linear-gradient(90deg, rgba($gray-lightest, 0), $gray-lightest); content: ''; display: block; position: absolute; @@ -190,7 +261,6 @@ bottom: 0; right: 0; width: u(3rem); - @include linear-gradient(90deg, rgba($gray-lightest, 0), $gray-lightest); } } @@ -238,7 +308,7 @@ .cal__category { border-radius: 3px; - padding: u(0.5rem); + padding: u(.5rem); } // Event details @@ -254,10 +324,10 @@ white-space: initial; &::before { + @include triangle(2rem, $primary, up); background-image: none; left: 20%; top: u(-2rem) !important; - @include triangle(2rem, $primary, up); } .button--close--primary { @@ -296,14 +366,14 @@ .cal-details__date { display: block; - padding-bottom: u(0.5rem); + padding-bottom: u(.5rem); } .cal-details__title { display: block; font-weight: bold; line-height: 1.4; - padding-bottom: u(0.5rem); + padding-bottom: u(.5rem); } .cal-details__summary { @@ -316,13 +386,13 @@ .cal-details__description { display: block; line-height: 1.2; - padding-bottom: u(0.5rem); + padding-bottom: u(.5rem); } .cal-details__location { display: block; line-height: 1.2; - padding-bottom: u(0.5rem); + padding-bottom: u(.5rem); } .cal-details__add { @@ -387,11 +457,11 @@ .fc-agenda-view { .fc-bg { - display: none; // Temporary hack to remove mis-aligned background in agenda view + display: none; // Temporary hack to remove mis-aligned background in agenda view } .fc-axis { - padding: u(0.5rem 1rem 0 1rem); + padding: u(.5rem 1rem 0 1rem); text-align: right; vertical-align: middle; white-space: nowrap; @@ -415,18 +485,23 @@ .cal-list__toggles { float: left; font-family: $sans-serif; - padding-top: u(0.5rem); + padding-top: u(.5rem); } .cal-list { - letter-spacing: -0.3px; + letter-spacing: -.3px; } +*/ .cal-list__title { background-color: $gray-light; - border-left: 0.5rem solid $primary; + border-left: .5rem solid $primary; + display: block; font-family: $sans-serif; + font-size: 1.8rem; + font-weight: 700; letter-spacing: 0; + line-height: 1.6; margin: u(2rem 0 0 0); padding: u(1rem); } @@ -434,7 +509,7 @@ .cal-list__event { border-bottom: 1px solid $neutral; padding: u(2rem 0); - @include clearfix; + @include clearfix(); &:last-child { border-bottom: none; @@ -459,7 +534,7 @@ display: block; font-weight: bold; line-height: 1.2; - margin: 0 0 0.5rem; + margin: 0 0 .5rem 0; } .cal-list__date { @@ -468,9 +543,8 @@ } .cal-list__time { - margin-right: 0 !important; // overriding omega() @include span-columns(4 of 8); - @include omega; + @include omega(); } .cal-list__info { @@ -487,26 +561,27 @@ } .cal-list__category-title { - display: table; - border: 1px solid; border-color: $primary; - font-size: u(1.4rem); - padding: u(0.5rem); border-radius: 3px; - @include clearfix; + border: 1px solid; + display: table; + font-size: u(1.4rem); + padding: u(.5rem); + + @include clearfix(); .tooltip__trigger-text { border-right: 1px solid $primary; display: table-cell; - padding-right: 0.5rem; line-height: 1.2; + padding-right: .5rem; vertical-align: middle; } .tooltip__container { display: table-cell; + padding-left: .5rem; vertical-align: middle; - padding-left: 0.5rem; } .list--bulleted li:last-child { @@ -522,6 +597,8 @@ display: block; } + +/* // Popover .fc-popover { background-color: $inverse; @@ -530,9 +607,9 @@ width: u(22rem); .fc-header { + @include clearfix(); background: $neutral; - padding: u(3px 0.5rem); - @include clearfix; + padding: u(3px .5rem); .fc-title { font-weight: bold; @@ -543,17 +620,17 @@ } .fc-close { + @include u-icon($x, $primary, 2rem, 2rem, 100%); cursor: pointer; float: right; - @include u-icon($x, $primary, 2rem, 2rem, 100%); } .fc-event-container { - padding: u(0.5rem); + padding: u(.5rem); } .fc-content { - margin-bottom: u(0.5rem); + margin-bottom: u(.5rem); } } @@ -580,7 +657,7 @@ text-align: center; } - .fc-right { + .fc-toolbar-end { display: block; float: right; @@ -626,23 +703,23 @@ } .cal-list__event { + @include clearfix(); padding: u(2rem); - @include clearfix; } .cal-list__details { - margin-bottom: 0; @include span-columns(10); + margin-bottom: 0; } .cal-list__add { - font-size: u(1.4rem); @include span-columns(2); + font-size: u(1.4rem); } .cal-list__date { - font-weight: bold; @include span-columns(2 of 10); + font-weight: bold; } .cal-list__time { @@ -650,11 +727,12 @@ } .cal-list__info { - padding-top: 0; @include span-columns(6 of 10); + padding-top: 0; } .cal-list__category { @include span-columns(3 of 10); } } +*/ diff --git a/fec/fec/templates/base.html b/fec/fec/templates/base.html index 6930afcbd3..d3ade68024 100644 --- a/fec/fec/templates/base.html +++ b/fec/fec/templates/base.html @@ -9,7 +9,7 @@ {% include 'partials/meta-tags-preconnects.html' %} {% endif %} - {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }} | FEC {% endif %}{% endblock %}{% block title_suffix %}{% endblock %} + {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }} | FEC{% endif %}{% endblock %}{% block title_suffix %}{% endblock %} {% if not request.is_preview %}{% include 'partials/google-tag-manager-script.html' %}{% endif %} diff --git a/fec/fec/tests/js/calendar.js b/fec/fec/tests/js/calendar.js index 05a0bd916b..1f41622816 100644 --- a/fec/fec/tests/js/calendar.js +++ b/fec/fec/tests/js/calendar.js @@ -36,8 +36,8 @@ describe('calendar', function() { ); this.calendar = new Calendar({ selector: '#calendar', - download: '#download', - subscribe: '#subscribe', + selector_download: '#download', + selector_subscribe: '#subscribe', url: 'http://test.calendar', exportUrl: 'http://test.calendar/export', subscribeUrl: 'http://test.calendar/export', diff --git a/fec/gulpfile.js b/fec/gulpfile.js index 63d114062a..bc04fff6a4 100644 --- a/fec/gulpfile.js +++ b/fec/gulpfile.js @@ -29,7 +29,7 @@ gulp.task('_css-compile', function() { gulp .src('./fec/static/scss/*.scss') // compiles sass - .pipe(sass().on('error', sass.logError)) + .pipe(sass.sync().on('error', sass.logError)) // minifies css .pipe(csso()) // sourcemaps for local to back-trace source of scss @@ -82,7 +82,7 @@ gulp.task('_widgets-compile-sass', function() { gulp .src('./fec/static/scss/widgets/*.scss') // compiles sass - .pipe(sass().on('error', sass.logError)) + .pipe(sass.sync().on('error', sass.logError)) // minifies css .pipe(csso()) // sourcemaps for local to back-trace source of scss diff --git a/fec/webpack.config.cjs b/fec/webpack.config.cjs index 8a77901f99..b48bea3a48 100644 --- a/fec/webpack.config.cjs +++ b/fec/webpack.config.cjs @@ -183,7 +183,8 @@ module.exports = [ }, module: { rules: [ - { test: /\.hbs/, use: ['handlebars-loader'] } + { test: /\.hbs/, use: ['handlebars-loader'] }, + { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, resolve: { @@ -225,7 +226,7 @@ module.exports = [ name: 'draftail', entry: { draftail: `${js}/draftail/App.js` }, output: { - clean: mode === 'development' ? true : undefined, // Deploying to Prod doesn't need this at all + // clean: mode === 'development' ? true : undefined, // Deploying to Prod doesn't need this at all filename: '[name]-[contenthash].js', path: path.resolve(__dirname, './dist/fec/static/js') }, diff --git a/package-lock.json b/package-lock.json index f37c6caa2f..8b77f81030 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "CC0-1.0", "dependencies": { + "@fullcalendar/bootstrap5": "^7.0.0-beta.3", "accessible-mega-menu": "git+https://github.com/noahmanger/Accessible-Mega-Menu.git", "ace-builds": "^1.2.2", "d3": "^3.5.17", @@ -27,9 +28,11 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^6.1.15", - "@fullcalendar/daygrid": "^6.1.15", - "@fullcalendar/list": "^5.11.5", + "@fullcalendar/core": "^7.0.0-beta.3", + "@fullcalendar/daygrid": "^7.0.0-beta.3", + "@fullcalendar/interaction": "^7.0.0-beta.3", + "@fullcalendar/list": "^7.0.0-beta.3", + "@fullcalendar/timegrid": "^7.0.0-beta.3", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", "babel-jest": "^29.7.0", @@ -48,6 +51,7 @@ "component-sticky": "^1.1.0", "concurrently": "^8.2.2", "corejs-typeahead": "^1.3.4", + "css-loader": "^7.1.2", "d3-array": "^3.2.4", "d3-geo": "^3.1.0", "d3-scale": "^4.0.2", @@ -63,7 +67,6 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.0.0", - "fullcalendar": "^4.0.0-alpha.4", "glossary-panel": "^1.0.0", "graceful-fs": "^4.2.10", "gulp": "^4.0.2", @@ -111,6 +114,7 @@ "sinon-chai": "^3.7.0", "sprintf-js": "^1.1.3", "stream-browserify": "^3.0.0", + "style-loader": "^4.0.0", "topojson-client": "^3.1.0", "underscore": "^1.13.6", "urijs": "^1.19.11", @@ -122,7 +126,7 @@ }, "engines": { "node": "22.1.0", - "npm": "10.8.2" + "npm": "10.9.2" } }, "node_modules/@ampproject/remapping": { @@ -2200,45 +2204,66 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fullcalendar/common": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/common/-/common-5.11.5.tgz", - "integrity": "sha512-3iAYiUbHXhjSVXnYWz27Od2cslztUPsOwiwKlfGvQxBixv2Kl6a8IPwaijKFYJHXdwYmfPoEgK7rvqAGVoIYwA==", - "dev": true, + "node_modules/@fullcalendar/bootstrap5": { + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-7.0.0-beta.3.tgz", + "integrity": "sha512-KyDMpkrXWp4Wxi4Pdqc7LAPLq/zew3EtN9LKepyj52DAiYNvcsdXpzPNsUU8d2U6FPDuf5tFyWCFZDNnTJnmhQ==", "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" + "peerDependencies": { + "@fullcalendar/core": "7.0.0-beta.3", + "bootstrap": ">=5.2.0 <6.0.0" } }, "node_modules/@fullcalendar/core": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", - "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", - "dev": true, + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-7.0.0-beta.3.tgz", + "integrity": "sha512-jSXQyF0OQX0jxYBH/Xmj1l8PHH4+eWJENmoAhrsyUbeKITRVHPh/vsaTZ8ej8zFL0fNhLd8lqt/SWR0/5hYwvg==", "license": "MIT", "dependencies": { - "preact": "~10.12.1" + "preact": "~10.23.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", - "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-7.0.0-beta.3.tgz", + "integrity": "sha512-ou6RikuVX0bzEQVSvAAayJAGxxnV4dtP4E8dXZWqkAB40RQQ/fmKzJVn1W0AtMqkBMZ0egmvmXdBR2IdXoi9Cw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "7.0.0-beta.3" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-7.0.0-beta.3.tgz", + "integrity": "sha512-qQhiKiKsAyvTHy6Uv62q/Er7sDIC3iyn84Gc/6+MqRnKehJ8oiXJf6VpV3dPMtY11/Ipi4kLhhgiPgd0HCEZuA==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.15" + "@fullcalendar/core": "7.0.0-beta.3" } }, "node_modules/@fullcalendar/list": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-5.11.5.tgz", - "integrity": "sha512-ZYMPT4CVt9tIYkVVNx7CKkB2xc+n9L56+vgXkurptgYgPsacXYkcpF/1Hy/B5LKlg0ROEF9Qfftjow8xjANqaA==", + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-7.0.0-beta.3.tgz", + "integrity": "sha512-WvM+kBxJ/h6OuNVBWtcuC3wUDxmAWhkd+du16Jd53HtDrw0Y2CY0SokD+ffgaFj7F2N6tC7IwpGXv67KooavTA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "7.0.0-beta.3" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-7.0.0-beta.3.tgz", + "integrity": "sha512-jHe1ZBoW7E6GeYyk/eiZqspuuXwGmBC3Ndole04EPDi7yIAghePb1RduZ5kJV4RDig+MJqAQwCVQMItqoAjhtQ==", "dev": true, "license": "MIT", "dependencies": { - "@fullcalendar/common": "~5.11.5", - "tslib": "^2.1.0" + "@fullcalendar/daygrid": "7.0.0-beta.3" + }, + "peerDependencies": { + "@fullcalendar/core": "7.0.0-beta.3" } }, "node_modules/@gulp-sourcemaps/identity-map": { @@ -2852,6 +2877,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", @@ -4488,13 +4524,6 @@ "node": ">= 0.10" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -6373,6 +6402,26 @@ "dev": true, "license": "ISC" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peer": true, + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7417,19 +7466,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -7723,13 +7759,6 @@ "node": ">= 0.6" } }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true, - "license": "MIT" - }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -7971,6 +8000,55 @@ "source-map-resolve": "^0.6.0" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -8883,16 +8961,6 @@ "node": ">=6" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -11612,33 +11680,6 @@ "dev": true, "license": "BSD" }, - "node_modules/form-data": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", - "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -11726,20 +11767,6 @@ "node": ">= 4.0" } }, - "node_modules/fullcalendar": { - "version": "4.0.0-alpha.4", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-4.0.0-alpha.4.tgz", - "integrity": "sha512-6uup/KTSSlybpj3ntiYvDlpbU82Z9deeW9D8zO/MJLcsKD/i+I4kWEnoJLp1egZdAHq9xGsJ9PLTBU/xz6QqGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "luxon": "^1.10.0", - "moment": "^2.23.0", - "moment-timezone": "^0.5.23", - "rrule": "^2.6.0", - "superagent": "^3.8.3" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -13885,6 +13912,19 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -16694,16 +16734,6 @@ "es5-ext": "~0.10.2" } }, - "node_modules/luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -17051,16 +17081,6 @@ "dev": true, "license": "MIT" }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", @@ -17769,19 +17789,6 @@ "node": "*" } }, - "node_modules/moment-timezone": { - "version": "0.5.46", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz", - "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/moo": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", @@ -19614,6 +19621,97 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-selector-parser": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", @@ -19628,11 +19726,17 @@ "node": ">=4" } }, - "node_modules/preact": { - "version": "10.12.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", - "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true, + "license": "MIT" + }, + "node_modules/preact": { + "version": "10.23.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", + "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", "license": "MIT", "funding": { "type": "opencollective", @@ -21106,16 +21210,6 @@ "inherits": "^2.0.1" } }, - "node_modules/rrule": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", - "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -22917,60 +23011,31 @@ "node": ">=0.10.0" } }, - "node_modules/subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.1.0" - } - }, - "node_modules/superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "dev": true, "license": "MIT", - "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, "engines": { - "node": ">= 4.0" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" + "minimist": "^1.1.0" } }, "node_modules/supports-color": { diff --git a/package.json b/package.json index ae0c185494..8a0660db1e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "watch": "concurrently \"npm run watch-js\" \"npm run watch-css\"" }, "dependencies": { + "@fullcalendar/bootstrap5": "^7.0.0-beta.3", "accessible-mega-menu": "git+https://github.com/noahmanger/Accessible-Mega-Menu.git", "ace-builds": "^1.2.2", "d3": "^3.5.17", @@ -44,9 +45,11 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^6.1.15", - "@fullcalendar/daygrid": "^6.1.15", - "@fullcalendar/list": "^5.11.5", + "@fullcalendar/core": "^7.0.0-beta.3", + "@fullcalendar/daygrid": "^7.0.0-beta.3", + "@fullcalendar/interaction": "^7.0.0-beta.3", + "@fullcalendar/list": "^7.0.0-beta.3", + "@fullcalendar/timegrid": "^7.0.0-beta.3", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", "babel-jest": "^29.7.0", @@ -65,6 +68,7 @@ "component-sticky": "^1.1.0", "concurrently": "^8.2.2", "corejs-typeahead": "^1.3.4", + "css-loader": "^7.1.2", "d3-array": "^3.2.4", "d3-geo": "^3.1.0", "d3-scale": "^4.0.2", @@ -80,7 +84,6 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.0.0", - "fullcalendar": "^4.0.0-alpha.4", "glossary-panel": "^1.0.0", "graceful-fs": "^4.2.10", "gulp": "^4.0.2", @@ -128,6 +131,7 @@ "sinon-chai": "^3.7.0", "sprintf-js": "^1.1.3", "stream-browserify": "^3.0.0", + "style-loader": "^4.0.0", "topojson-client": "^3.1.0", "underscore": "^1.13.6", "urijs": "^1.19.11", @@ -150,7 +154,7 @@ "browserslist": "last 2 versions, > 1%, IE 10", "engines": { "node": "22.1.0", - "npm": "10.8.2" + "npm": "10.9.2" }, "license": "CC0-1.0" } From aa3b83dfb105ac507b210ba3abe504cef2a8d093 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Feb 2025 08:50:44 -0500 Subject: [PATCH 3/4] Calendar - upgrade packages --- package-lock.json | 326 +++++++++++++++++++++++----------------------- package.json | 16 +-- 2 files changed, 168 insertions(+), 174 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b77f81030..8ad5da3755 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.0.0", "license": "CC0-1.0", "dependencies": { - "@fullcalendar/bootstrap5": "^7.0.0-beta.3", "accessible-mega-menu": "git+https://github.com/noahmanger/Accessible-Mega-Menu.git", "ace-builds": "^1.2.2", "d3": "^3.5.17", @@ -28,11 +27,12 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^7.0.0-beta.3", - "@fullcalendar/daygrid": "^7.0.0-beta.3", - "@fullcalendar/interaction": "^7.0.0-beta.3", - "@fullcalendar/list": "^7.0.0-beta.3", - "@fullcalendar/timegrid": "^7.0.0-beta.3", + "@fullcalendar/bootstrap5": "^7.0.0-beta.4", + "@fullcalendar/core": "^7.0.0-beta.4", + "@fullcalendar/daygrid": "^7.0.0-beta.4", + "@fullcalendar/interaction": "^7.0.0-beta.4", + "@fullcalendar/list": "^7.0.0-beta.4", + "@fullcalendar/timegrid": "^7.0.0-beta.4", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", "babel-jest": "^29.7.0", @@ -119,7 +119,7 @@ "underscore": "^1.13.6", "urijs": "^1.19.11", "vue": "^2.7.16", - "webpack": "^5.92.1", + "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "5.1.4", "webpack-manifest-plugin": "^5.0.0" @@ -2205,65 +2205,67 @@ } }, "node_modules/@fullcalendar/bootstrap5": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-7.0.0-beta.3.tgz", - "integrity": "sha512-KyDMpkrXWp4Wxi4Pdqc7LAPLq/zew3EtN9LKepyj52DAiYNvcsdXpzPNsUU8d2U6FPDuf5tFyWCFZDNnTJnmhQ==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/bootstrap5/-/bootstrap5-7.0.0-beta.4.tgz", + "integrity": "sha512-z1ZX73Ix6x/6nLxkBKnRzAt0Xc/oPLqucjfcghL3UzQi4dOTAqvG8Pic+bSCDqw0EYEBCxBY6N2kBKYguGcg2w==", + "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "7.0.0-beta.3", + "@fullcalendar/core": "7.0.0-beta.4", "bootstrap": ">=5.2.0 <6.0.0" } }, "node_modules/@fullcalendar/core": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-7.0.0-beta.3.tgz", - "integrity": "sha512-jSXQyF0OQX0jxYBH/Xmj1l8PHH4+eWJENmoAhrsyUbeKITRVHPh/vsaTZ8ej8zFL0fNhLd8lqt/SWR0/5hYwvg==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-7.0.0-beta.4.tgz", + "integrity": "sha512-fHVQ+mGjHuDImaOlA5Sdh8yioc9vOJNMqN3J91zJlm8HNsUWwbDNvByWnVX+WhcysluUtvRTWf3sQXzmW5eCLA==", + "dev": true, "license": "MIT", "dependencies": { "preact": "~10.23.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-7.0.0-beta.3.tgz", - "integrity": "sha512-ou6RikuVX0bzEQVSvAAayJAGxxnV4dtP4E8dXZWqkAB40RQQ/fmKzJVn1W0AtMqkBMZ0egmvmXdBR2IdXoi9Cw==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-7.0.0-beta.4.tgz", + "integrity": "sha512-Eon7XhXygtvNO+VPSF65TfmchtQpik2oG2XejbUWsZxWgiONLoBnmxp2GHGWxeUCFF3lcT4PnzZNAlCc8aYGjw==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "7.0.0-beta.3" + "@fullcalendar/core": "7.0.0-beta.4" } }, "node_modules/@fullcalendar/interaction": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-7.0.0-beta.3.tgz", - "integrity": "sha512-qQhiKiKsAyvTHy6Uv62q/Er7sDIC3iyn84Gc/6+MqRnKehJ8oiXJf6VpV3dPMtY11/Ipi4kLhhgiPgd0HCEZuA==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-7.0.0-beta.4.tgz", + "integrity": "sha512-NuaSi5nKPtuhvNk/1te/WeTa2d9yqjI52XDS3o4nPl15Fn6SjxJefVlIoRqmF0OKAkpPbc6yTUEFCzmExjmj+g==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "7.0.0-beta.3" + "@fullcalendar/core": "7.0.0-beta.4" } }, "node_modules/@fullcalendar/list": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-7.0.0-beta.3.tgz", - "integrity": "sha512-WvM+kBxJ/h6OuNVBWtcuC3wUDxmAWhkd+du16Jd53HtDrw0Y2CY0SokD+ffgaFj7F2N6tC7IwpGXv67KooavTA==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-7.0.0-beta.4.tgz", + "integrity": "sha512-pp3VcUURvmXnloCuHSEyE1Z34H8QQ1NFXBJ+XE91tKK1DDLqRlKEJEtQYSX/yQTnf0t8d+y8lmSgISPVARMXEw==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "7.0.0-beta.3" + "@fullcalendar/core": "7.0.0-beta.4" } }, "node_modules/@fullcalendar/timegrid": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-7.0.0-beta.3.tgz", - "integrity": "sha512-jHe1ZBoW7E6GeYyk/eiZqspuuXwGmBC3Ndole04EPDi7yIAghePb1RduZ5kJV4RDig+MJqAQwCVQMItqoAjhtQ==", + "version": "7.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-7.0.0-beta.4.tgz", + "integrity": "sha512-Sz7iNJnlXgSe1OvGp3Ir9SWt+jLIRBTsztStSNG6qhd1AI3zAtDnVYIrOXV++WaoOZKBA5VutCrXdvPfuVBWrA==", "dev": true, "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "7.0.0-beta.3" + "@fullcalendar/daygrid": "7.0.0-beta.4" }, "peerDependencies": { - "@fullcalendar/core": "7.0.0-beta.3" + "@fullcalendar/core": "7.0.0-beta.4" } }, "node_modules/@gulp-sourcemaps/identity-map": { @@ -2881,6 +2883,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, "license": "MIT", "peer": true, "funding": { @@ -3099,9 +3102,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -3261,73 +3264,73 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", "dependencies": { @@ -3335,9 +3338,9 @@ } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3345,79 +3348,79 @@ } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -3518,9 +3521,9 @@ "license": "BSD" }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -3530,16 +3533,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6406,6 +6399,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "dev": true, "funding": [ { "type": "github", @@ -6701,9 +6695,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "funding": [ { @@ -6721,10 +6715,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -6979,9 +6973,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -9431,9 +9425,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.832", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz", - "integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==", + "version": "1.5.79", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz", + "integrity": "sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==", "dev": true, "license": "ISC" }, @@ -9589,9 +9583,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10004,9 +9998,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -18119,9 +18113,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, @@ -19366,9 +19360,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -19737,6 +19731,7 @@ "version": "10.23.2", "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -24323,9 +24318,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "dev": true, "funding": [ { @@ -24343,8 +24338,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -25080,22 +25075,21 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/package.json b/package.json index 8a0660db1e..36c90032df 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "watch": "concurrently \"npm run watch-js\" \"npm run watch-css\"" }, "dependencies": { - "@fullcalendar/bootstrap5": "^7.0.0-beta.3", "accessible-mega-menu": "git+https://github.com/noahmanger/Accessible-Mega-Menu.git", "ace-builds": "^1.2.2", "d3": "^3.5.17", @@ -45,11 +44,12 @@ "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/preset-env": "^7.24.7", "@babel/preset-react": "^7.24.7", - "@fullcalendar/core": "^7.0.0-beta.3", - "@fullcalendar/daygrid": "^7.0.0-beta.3", - "@fullcalendar/interaction": "^7.0.0-beta.3", - "@fullcalendar/list": "^7.0.0-beta.3", - "@fullcalendar/timegrid": "^7.0.0-beta.3", + "@fullcalendar/bootstrap5": "^7.0.0-beta.4", + "@fullcalendar/core": "^7.0.0-beta.4", + "@fullcalendar/daygrid": "^7.0.0-beta.4", + "@fullcalendar/interaction": "^7.0.0-beta.4", + "@fullcalendar/list": "^7.0.0-beta.4", + "@fullcalendar/timegrid": "^7.0.0-beta.4", "a11y-dialog": "^8.0.4", "aria-accordion": "^1.1.0", "babel-jest": "^29.7.0", @@ -121,9 +121,9 @@ "mockdate": "^3.0.5", "moment": "^2.29.4", "npm-watch": "^0.13.0", + "numeral": "^1.5.6", "prettier": "^3.3.3", "puppeteer": "^22.11.2", - "numeral": "^1.5.6", "puppeteer-core": "^21.11.0", "sass": "^1.78.0", "scrollmonitor": "^1.2.11", @@ -136,7 +136,7 @@ "underscore": "^1.13.6", "urijs": "^1.19.11", "vue": "^2.7.16", - "webpack": "^5.92.1", + "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "5.1.4", "webpack-manifest-plugin": "^5.0.0" From a6b08a6acacea9a15ec028df71f323429a88a216 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 Feb 2025 08:52:20 -0500 Subject: [PATCH 4/4] Calendar - overhaul and upgrade --- fec/fec/static/js/modules/calendar-helpers.js | 168 +++- .../static/js/modules/calendar-list-view.js | 462 +++++---- fec/fec/static/js/modules/calendar-tooltip.js | 122 ++- fec/fec/static/js/modules/calendar.js | 672 ++++++++----- fec/fec/static/js/modules/dropdowns.js | 7 + .../static/js/modules/filters/filter-set.js | 2 +- .../static/js/modules/filters/filter-tags.js | 1 + fec/fec/static/js/modules/urls.js | 5 +- fec/fec/static/js/pages/calendar-page.js | 29 +- .../static/js/templates/calendar/details.hbs | 28 +- .../static/js/templates/calendar/event.hbs | 4 +- .../static/js/templates/calendar/events.hbs | 27 +- .../js/templates/calendar/listToggles.hbs | 12 +- .../js/templates/calendar/subscribe.hbs | 8 +- fec/fec/static/scss/components/_buttons.scss | 2 +- fec/fec/static/scss/components/_calendar.scss | 896 ++++++++---------- .../static/scss/components/_form-styles.scss | 2 +- fec/fec/tests/js/calendar.js | 66 +- 18 files changed, 1418 insertions(+), 1095 deletions(-) diff --git a/fec/fec/static/js/modules/calendar-helpers.js b/fec/fec/static/js/modules/calendar-helpers.js index 96da737596..7e443f5aed 100644 --- a/fec/fec/static/js/modules/calendar-helpers.js +++ b/fec/fec/static/js/modules/calendar-helpers.js @@ -26,41 +26,148 @@ function dateString(dateToParse, format, adjustmentHours) { return dateToParse; } +/** + * @enum {Object} - Returns an object with settings for various datetime formats. + * For dates, use .toLocaleDateString('en-US', {}), for time use .toLocaleTimeString('en-US', {}); + * Converts all dates to U.S. Eastern (i.e. 'America/New_York') + * For ISO dates (i.e. 1970-01-01), use only .toISOString(); + * @see https://moment.github.io/luxon/#/formatting?id=presets + */ +export const dateTimeFormatOptions = { + /** @example '1/1/1970' (This is the default for en-US) */ + DATE_SHORT: {}, + + /** @example '01/01/1970' */ + DATE_SHORT_01: { month: '2-digit', day: '2-digit', year: 'numeric' }, + + /** @example 'January 1, 1970' */ + DATE_FULL: { month: 'long', day: 'numeric', year: 'numeric' }, + + /** @example 'January 01, 1970' */ + DATE_FULL_01: { month: 'long', day: '2-digit', year: 'numeric' }, + + /** @example 'January 1 */ + DATE_FULL_MONTH_DAY: { month: 'long', day: '2-digit' }, + + /** @example 'Wednesday' */ + WEEKDAY_FULL: { weekday: 'long' }, + + /** @example '1:30 PM' */ + TIME_SIMPLE: { hour: "numeric", minute: "2-digit" }, + + /** @example '13:30' */ + TIME_24_SIMPLE: {}, +}; + /** * @param {Object} event - {start: '', end: '', title: '', summary: ''} - * @param {Moment} [event.start] - * @param {Moment} [event.end] + * @param {string} event.start + * @param {string} [event.end] * @param {string} [event.title] * @param {string} [event.summary] * @returns {string} */ export function getGoogleUrl(event) { - let fmt, dates; - // TODO: remove this .format check after Moment has been removed - if (event.start.format) { - // If we're dealing with a Moment date (i.e. it has a .format() ) - if (event.end) { - fmt = 'YYYYMMDD[T]HHmmss'; - dates = event.start.format(fmt) + '/' + event.end.format(fmt); - } else { - fmt = 'YYYYMMDD'; - dates = - event.start.format(fmt) + - '/' + - event.start - .clone() - .add(1, 'day') - .format(fmt); - } + console.log('getGoogleUrl(event): ', event); + let datesString; + + if (event.end) { + datesString = `${convertDateForGoogle(event.start, true)}/${convertDateForGoogle(event.end, true)}`; + + } else { + const startDateObj = new Date(event.start); + startDateObj.setHours(startDateObj.getHours() + 24); + const fakeEndDate = startDateObj.toISOString(); + const startEndComboString = `${convertDateForGoogle(event.start, false).substring(0,10)}/${convertDateForGoogle(fakeEndDate, false)}`; + datesString = startEndComboString; + } + + const toReturn = + URI('https://calendar.google.com/calendar/render') + .addQuery({ + action: 'TEMPLATE', + text: event.title, + details: event.summary, + dates: datesString + }) + .toString(); + + console.log(' toReturn: ', toReturn); + + return toReturn; +} + +export function getMs365Url(event) { + console.log('getMs365Url(event): ', event); + let datesString; + + if (event.end) { + datesString = `${convertDateForGoogle(event.start, true)}/${convertDateForGoogle(event.end, true)}`; + } else { - // Otherwise, if we have a native date + const startDateObj = new Date(event.start); + startDateObj.setHours(startDateObj.getHours() + 24); + const fakeEndDate = startDateObj.toISOString(); + const startEndComboString = `${convertDateForGoogle(event.start, false).substring(0,10)}/${convertDateForGoogle(fakeEndDate, false)}`; + datesString = startEndComboString; + } + + const toReturn = + URI('https://outlook.office.com/calendar/0/deeplink/compose') + .addQuery({ + allday: true, + body: event.summary, + enddt: '2025-01-31', + location: '', + // path: '', + // rru: '', + startdt: '2025-01-30', + subject: event.title + // action: 'TEMPLATE', + // text: event.title, + // details: event.summary, + // dates: datesString + }) + .toString(); + console.log(' toReturn: ', toReturn); + + return toReturn; +} - if (event.end) { - dates = `${dateString(event.start, 'YYYYMMDD[T]HHmmss')}/${dateString(event.end, 'YYYYMMDD[T]HHmmss')}`; +/** + * Takes a Date object, removes the dashes and colon + * @param {string} input -sdf + * @param {boolean} [includeTime=true] - Whether the returned string should be 200012311945 or 20001231 + * @returns {string} + */ +function convertDateForGoogle(input, includeTime = true) { + // const passedDate = new Date(input); + const toReturn = input.replaceAll(/[-:.]/g, ''); + return includeTime ? toReturn : toReturn.substring(0, 7); +} - } else { - dates = `${dateString(event.start, 'YYYYMMDD')}/${dateString(event.start, 'YYYYMMDD', 24)}`; - } +/** + * @param {Object} event - {start: '', end: '', title: '', summary: ''} + * @param {Moment} [event.start] + * @param {Moment} [event.end] + * @param {string} [event.title] + * @param {string} [event.summary] + * @returns {string} + */ +export function getGoogleUrl_moment(event) { + let fmt, dates; + if (event.end) { + fmt = 'YYYYMMDD[T]HHmmss'; + dates = event.start.format(fmt) + '/' + event.end.format(fmt); + } else { + fmt = 'YYYYMMDD'; + dates = + event.start.format(fmt) + + '/' + + event.start + .clone() + .add(1, 'day') + .format(fmt); } return URI('https://calendar.google.com/calendar/render') .addQuery({ @@ -77,7 +184,9 @@ export function getGoogleUrl(event) { * @param {Object} params - key-value pairs on an Object * @returns {string} a string like '/v1/path/?param1key=param1val¶m2key=param2val' */ -export function calendarDownload(path, params) { +// export function calendarDownload(path, params) { +export function getDownloadUrl(path, params) { + console.log('getDownloadUrl(path, params): ', path, params); const url = URI(window.API_LOCATION) .path(Array.prototype.concat(window.API_VERSION, path || [], '').join('/')) .addQuery({ @@ -91,6 +200,13 @@ export function calendarDownload(path, params) { return URI.decode(url); } +/** + * Builds a URL from the parameters provided plus API_LOCATION and API_VERSION + * @param {string[]} path - An array of strings to be combined to build the path. ex: ['data', 'path'] will become 'data/path' + * @param {Object} params - Key/value pairs to add after the ? in the full URL + * @param {string} [type] - Adds the public key or, if 'sub', uses the calendar public key + * @returns {string} + */ export function getUrl(path, params, type) { //if 'type' arg is present and set to 'sub', use API_KEY_PUBLIC_CALENDAR as api_key, otherwise use API_KEY_PUBLIC; const apiKey = diff --git a/fec/fec/static/js/modules/calendar-list-view.js b/fec/fec/static/js/modules/calendar-list-view.js index 34d8c633f1..17dc2da6e3 100644 --- a/fec/fec/static/js/modules/calendar-list-view.js +++ b/fec/fec/static/js/modules/calendar-list-view.js @@ -1,23 +1,18 @@ -/* eslint-disable */ +/* eslint-disable */ // TODO: remove eslint-disable /** - * + * @fileoverview Plugin for fullcalendar.io being used by ./calendar.js + * @license CC0-1.0 + * @owner fec.gov + * @version 2.0 */ -// import $ from 'jquery'; -import { createPlugin, sliceEvents, Calendar } from '@fullcalendar/core'; -import listPlugin from '@fullcalendar/list'; -// import { ViewRoot } from '@fullcalendar/common'; -import { default as moment } from 'moment'; -import { default as _chain } from 'underscore/modules/chain.js'; -import { default as _each } from 'underscore/modules/each.js'; -import { default as _pairs } from 'underscore/modules/pairs.js'; -import { default as _reduce } from 'underscore/modules/reduce.js'; -import Dropdown from './dropdowns.js'; -import { default as eventTemplate } from '../templates/calendar/event.hbs'; -import { default as eventsTemplate } from '../templates/calendar/events.hbs'; +import { createPlugin, sliceEvents } from '@fullcalendar/core'; + +import { dateTimeFormatOptions, getDownloadUrl, getGoogleUrl, getMs365Url } from '../modules/calendar-helpers.js'; -// const FC = $.fullCalendar; -// const View = FC.View; +import Dropdown from './dropdowns.js'; +import { default as template_events } from '../templates/calendar/events.hbs'; +import { default as template_listToggles } from '../templates/calendar/listToggles.hbs'; // 'Sort by: Category' view // Property name is the category @@ -25,8 +20,12 @@ import { default as eventsTemplate } from '../templates/calendar/events.hbs'; // List items are the first token of the event category parameter from the API // example: 'ie' for 'IE Periods' +/** */ +const customClassName = 'fec-list-view'; +const LIST_SORT = ['monthTime', 'monthCategory']; + /** @enum {string[]} */ -const categories = { +const categories_lookup = { Elections: ['election'], 'Filing deadlines': ['reporting', 'pre'], 'Reporting and compliance periods': ['ie', 'ec', 'fea'], @@ -36,139 +35,195 @@ const categories = { Other: ['other'] }; -const categoriesInverse = _reduce( - _pairs(categories), - function(memo, pair) { - const key = pair[0]; - const values = pair[1]; - _each(values, function(value) { - memo[value] = key; - }); - return memo; - }, - {} -); +/** + * Similar to categories_lookup but flipped so every value there is a key here. + * e.g. {Meetings: ['open', 'executive']} in categories_lookup would be {open: 'Meetings', executive: 'Meetings']} here + * @enum {string[]} + */ +const categories_reverseLookup = Object.getOwnPropertyNames(categories_lookup).reduce((accumulator, key) => { + categories_lookup[key].forEach(val => { + accumulator[val] = key; + }) + return accumulator; +}, {}); +/** + * Group the events by category,
+ * otherwise events with no recognized category will be group at the top as Unknown. + * @param {Object[]} events - Array of Objects with event details + * @param {string} [start] - ISO-formatted date string + * @param {string} [end] - ISO-formatted date string + * @returns {Object[]} An array of objects groups of events like [{title: 'Filing deadlines', datetime_string: '', events: [{}]}, …] + */ const categoryGroups = function(events, start, end) { - return _chain(events) - .filter(function(event) { - return start <= event.start && event.start < end; - }) - .sortBy('start') - .groupBy(function(event) { - const category = event.category - ? event.category.split(/[ -]+/)[0].toLowerCase() - : null; - return categoriesInverse[category]; - }) - .map(function(values, key) { - return { - title: key, - events: values - }; - }) - .sortBy(function(group) { - return Object.keys(categories).indexOf(group.title); - }) - .value(); + const filteredEvents = [...events];//.filter(event => (event.range.start && event.range.start < end)); + + filteredEvents.forEach(event => { + // When does the event start? + const eventStartDate = new Date(event.range.start); + // Adjust for the time offset // TODO: this may need to be done elsewhere, too + eventStartDate.setMinutes(eventStartDate.getMinutes() + eventStartDate.getTimezoneOffset()); + event.groupByLabel = categories_reverseLookup[event.def.extendedProps.category]; + event.datetime = eventStartDate.toISOString().substring(0,10); // used inside + }); + + // Sort them by date + filteredEvents.sort((a, b) => { + if (a.range.start == b.range.start) return 0; + else return a.range.start < b.range.start ? -1 : 1; + }); + + const toReturn = []; + let nextGroup = {title: '', datetime_string: '', events: []}; + // For each pretty category name + Object.getOwnPropertyNames(categories_lookup).forEach(key => { + // The events with this pretty category name + const eventsInThisCat = filteredEvents.filter(event => { + const lowerFirstWordOfCategory = event.def.extendedProps.category.split(/[ -]+/)[0].toLowerCase(); + return key == categories_reverseLookup[lowerFirstWordOfCategory]; + }); + + // To account for any events that aren't in designated categories, + // let's remove each of these chosen events from filteredEvents + eventsInThisCat.forEach(event => { + filteredEvents.splice(filteredEvents.indexOf(event), 1); + }); + + // If there are events, create a new group + if (eventsInThisCat.length > 0) { + nextGroup = {title: key, datetime_string: '', events: [...eventsInThisCat]}; + toReturn.push(nextGroup); + } + }); + + // If there are filteredEvents left, they didn't get a category, so… + if (filteredEvents.length > 0) { + nextGroup = {title: 'Unknown category', datetime_string: '', events: [...filteredEvents]}; + // …let's put them at the top of the list + toReturn.unshift(nextGroup); + } + + return toReturn; }; /** * Group the events by the formatted value of their start dates, - * otherwise events with a time on their date will be grouped separately from those that just have a date * @param {Object[]} events - Array of Objects with event details - * @param {Moment} start - * @param {Moment} end - * @returns {Object[]} + * @param {string} [start] - ISO-formatted date string + * @param {string} [end] - ISO-formatted date string + * @returns {Object[]} An array of objects groups of events like [{title: 'January 1, 1970', datetime_string: '1970-01-01', events: [{}]}, …] */ const chronologicalGroups = function(events, start, end) { - events = _chain(events) - .filter(function(event) { - return start <= event.start && event.start < end; - }) - .map(function(value) { - // Group the events by the formatted value of their start dates, - // otherwise events with a time on their date will be grouped separately - // from those that just have a date - value.groupByValue = value.start.format('MMMM D, YYYY'); - return value; - }) - .sortBy('start') - .groupBy('groupByValue') - .map(function(values, key) { - return { - title: moment.utc(new Date(key)).format('MMMM D, YYYY'), - events: values - }; - }) - .value(); - return events; + const filteredEvents = [...events];//events.filter(event => (event.range.start && event.range.start < end)); + + // Go through every event and add its eventStartTime, the string like January 1, 1970 + filteredEvents.forEach(event => { + console.log('chronologicalGroups.forEach()'); + console.log(' event: ', event); + const thisEventObj = calendar.getEventById(event.def.publicId); + console.log(' thisEventObj: ', thisEventObj); + console.log(' .id: ', thisEventObj.id); + console.log(' .start: ', thisEventObj.start); + console.log(' .end: ', thisEventObj.end); + console.log(' .startStr: ', thisEventObj.startStr); + console.log(' .endStr: ', thisEventObj.endStr); + console.log(' .extendedProps: ', thisEventObj.extendedProps); + + // When does the event start? + const eventStartDate = new Date(event.range.start); + console.log(' eventStartDate: ', eventStartDate); + console.log(' typeof, start, eventStartDate: ', typeof thisEventObj.start, typeof eventStartDate); + console.log(' typeof, start, eventStartDate: ', Date.parse(thisEventObj.start), Date.parse(eventStartDate)); + // Adjust for the time offset // TODO: this may need to be done elsewhere, too + eventStartDate.setMinutes(eventStartDate.getMinutes() + eventStartDate.getTimezoneOffset()); + event.groupByLabel = eventStartDate.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL); + event.datetime = eventStartDate.toISOString().substring(0,10); // used inside + }); + + filteredEvents.sort((a, b) => { + if (a.range.start == b.range.start) return 0; + else return a.range.start < b.range.start ? -1 : 1; + }); + + const toReturn = []; + let prevGroupTitle = ''; + let nextGroup = {title: '', datetime_string: '', events: []}; + filteredEvents.forEach(event => { + // If the label has changed, we'll need to start a new group + if (event.groupByLabel != prevGroupTitle) { + // First, close out the previous group if it's not empty + if (nextGroup.events.length > 0) toReturn.push(nextGroup); + // Save this label + prevGroupTitle = event.groupByLabel; + // Start a new group + nextGroup = {title: event.groupByLabel, datetime_string: event.datetime, events: []}; + } + nextGroup.events.push(event); + }); + toReturn.push(nextGroup); + + return toReturn; }; -// const ListView = View.extend({ -const customViewConfig = { - classNames: ['fec-list-view'],//'fec-list-VIEW'], +/** + * + * @param {*} view + */ +const manageListToggles = function(listEventOrder) { + const listTogglesSelector = '.cal-list__toggles'; + + const headerToolbarCenter = document.querySelector('.fc-header-toolbar .fc-toolbar-center'); - /** - * @param {Object} d - * @param {Object} d - * @param {Function} (callback?) - * @returns {Object} {html: ''} - */ - content: function(props) { + let listToggles = document.querySelector('.cal-list__toggles'); + // If there's no listToggles, create it + if (!listToggles) { + listToggles = document.createElement('div'); + listToggles.classList.add(`${listTogglesSelector.substring(1)}`); // drop the period from the selector + headerToolbarCenter.appendChild(listToggles); + } - // TODO: NEXT STEPS - Working on sending vars like settings from the config option into this plugin so the events.hbs template can use them + listToggles.innerHTML = template_listToggles({listEventOrder: listEventOrder}); + // Activate the list buttons + listToggles.querySelectorAll('.js-toggle-list-sort').forEach(button => { + button.addEventListener('click', e => { + // If we aren't already sorting like this + if (!e.target.classList.contains('is-active')) { + calendar.view.custom.listEventOrder = e.target.dataset.triggerSort; + calendar.render(); + } + }); + }); - console.log('CustomViewConfig.content(props): ', props); - console.log(' this: ', this); - // console.log(' self: ', self); - // console.log(' self.window: ', self.window); - console.log(' root self var: ', window.calendar.view.customProps); - // console.log(' self.window.calendar: ', self.window.calendar); - // console.log(' self.calendar.view: ', self.window.calendar.view); - // let segs = sliceEvents(props, true); - // console.log(' segs, true: ', segs); - let segs = sliceEvents(props, false); - console.log(' segs, false: ', segs); - let html = ''; - - // const groups = this.options.categories - // ? categoryGroups(events, this.start, this.end) - // : chronologicalGroups(events, this.start, this.end); - // const settings = { - // duration: this.options.duration.intervalUnit, - // sortBy: this.options.sortBy - // }; - - const eventsObj = { - sortyBy: 'as' - }; + if (!listEventOrder) listToggles.setAttribute('aria-hidden', true); + else listToggles.removeAttribute('aria-hidden'); - + // Highlight the "List" button on monthTime + // if (view.name === 'monthCategory') { + // this.$calendar.find('.fc-monthTime-button').addClass('fc-state-active'); + // } +}; - return { html: eventsTemplate({ - - }) }; - }, - // render: function(d) { - // console.log('CustomViewConfig.render(d): ', d); - // }, - /** - * @param {Object} d - * @param {Date} d.date - * @param {ViewImpl} d.view - * @returns {Object} `{html: ''}` - */ - dayHeaderContent: function(d, two) { - console.log('CustomViewConfig.dayHeaderContent(d, two): ', d, two); - return { html: `` }; - }, +/** + * Called from inside `content` after an animation frame. + * `didMount` fires first, which doesn't do any good here. + * `content` fires next but only returns the html for every calendar entry and the view and + * doesn't actually create the elements. + * TODO: as fullcalendar adds it for custom views, I'd like to move this to something like eventDidMount + */ +const initDropdowns = function() { + const dropdownsToInit = document.querySelectorAll(`.${customClassName} .dropdown`); + dropdownsToInit.forEach(el => { + new Dropdown($(el), { checkboxes: false }); + }); +}; + +const fecListViewConfig = { + classNames: [customClassName], /** - * + * Called when this view is created * @param {Object} d * @param {Object} d.dateProfile * @param {HTMLElement} d.el @@ -177,43 +232,107 @@ const customViewConfig = { * @param {Function} [callback] */ didMount: function(props, callback) { - console.log('CustomViewConfig.didMount(props): ', props); - self.window.calendar.view.customProps = {self: this}; - console.log(' this: ', this); + calendar.el.dataset.fecView = 'list'; + }, + willUnmount: function(e, e1) { + manageListToggles(false); + calendar.el.dataset.fecView = 'grid'; }, - /*eventContent: function(arg) { - console.log('CustomViewConfig.eventContent(arg): ', arg); - let toReturn = ''; + + eventDidMount: function(e) { + console.log('CustomViewConfig.eventDidMount(e): ', e); + }, + + /** + * @param {Object} d + * @param {(Function)} callback + * @returns {Object} {html: ''} + */ + content: function(props) { + console.log('CustomViewConfig.content(props): ', props); - const groups = this.options.categories - ? categoryGroups(events, this.start, this.end) - : chronologicalGroups(events, this.start, this.end); - const settings = { - duration: this.options.duration.intervalUnit, - sortBy: this.options.sortBy - }; + const segs = sliceEvents(props, false); + console.log(' segs: ', segs); + + if (!calendar.view.custom) calendar.view.custom = {}; + if (!calendar.view.custom.settings) calendar.view.custom.settings = {}; + if (!calendar.view.custom.listEventOrder) calendar.view.custom.listEventOrder = 'monthTime'; + + if (LIST_SORT.includes(calendar.view.custom.listEventOrder)) + manageListToggles(calendar.view.custom.listEventOrder); + + const groups = calendar.view.custom.listEventOrder == 'monthCategory' + ? categoryGroups( + segs, + props.dateProfile.currentRange.start, + props.dateProfile.currentRange.end) + : chronologicalGroups( + segs, + props.dateProfile.currentRange.start, + props.dateProfile.currentRange.end); + + // Let's convert these events to something the template can use + const groupsForTemplate = []; + groups.forEach(group => { + const newGroup = {title: group.title, datetime_string: group.datetime_string, events: []}; + // + group.events.forEach(event => { + // Start with a new event, based on the incoming extendedProps + console.log(' customview.forEach(event): ', event); + const newEvent = Object.assign({},event.def.extendedProps); + // Get the start and end Date objects (GMT) + let start_date = new Date(event.range.start); + let end_date = event.def.hasEnd ? new Date(event.range.end) : false; + + newEvent.start_google = 'YAY-start'; + newEvent.end_google = 'YAY-end'; + + start_date.setMinutes(start_date.getMinutes() + start_date.getTimezoneOffset()); + newEvent.start_iso = start_date.toISOString(); + newEvent.start_day = start_date.toLocaleDateString('en-US', dateTimeFormatOptions.WEEKDAY_FULL); + newEvent.start_date_string = start_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL); + if (newEvent.hasStartTime) + newEvent.start_time_string = start_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE) + .toLowerCase().replace(' ', ''); + + if (end_date) { + end_date.setMinutes(end_date.getMinutes() + end_date.getTimezoneOffset()); + newEvent.end_iso = end_date.toISOString(); + newEvent.end_day = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.WEEKDAY_FULL); + newEvent.end_date_string = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL); + if (newEvent.hasStartTime) + newEvent.end_time_string = end_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE) + .toLowerCase().replace(' ', ''); + } + newEvent.download = getDownloadUrl(newEvent); + newEvent.google = getGoogleUrl({ + start: new Date(event.range.start).toISOString(), + end: newEvent.hasStartTime && event.range.end ? new Date(event.range.end).toISOString() : false, + title: newEvent.title, + summary: newEvent.description || newEvent.title + }); + newEvent.ms365 = getMs365Url({ + start: new Date(event.range.start).toISOString(), + end: newEvent.hasStartTime && event.range.end ? new Date(event.range.end).toISOString() : false, + title: newEvent.title, + summary: newEvent.description || newEvent.title + }); + + newGroup.events.push(newEvent); + }); + groupsForTemplate.push(newGroup); + }); - this.el.html(eventsTemplate({ groups: groups, settings: settings })); - - - return {html: toReturn}; - // templates.details( - // Object.assign({}, calEvent, { detailsId: this.detailsId }) - // { - // detailsId: , - // category: , - // allDay: , - // summary: , - // location: , - // description: , - // detailUrl: , - // download: , - // google: , - // } - // ) - // }; - },*/ - + window.requestAnimationFrame(initDropdowns.bind(this)); + + return { html: template_events({ + groups: groupsForTemplate, settings: {listEventOrder: calendar.view.custom.listEventOrder} + }) }; + + // TRYING TO ADD MOUSE EVENT LISTENERS TO DROPDOWNS + }, + // dayHeaderContent: function(d, two) {}, // Doesn't do anything here + // render: function(d) {} // Not valid here // Deprecated @@ -235,7 +354,7 @@ const customViewConfig = { sortBy: this.options.sortBy }; - this.el.html(eventsTemplate({ groups: groups, settings: settings })); + this.el.html(template_events({ groups: groups, settings: settings })); this.dropdowns = $(this.el.html) .find('.dropdown') .map(function(idx, elm) { @@ -255,24 +374,13 @@ const customViewConfig = { }; -/** - * - * @param {Object} d - * @returns {string} - */ -export function eventHtmlString(d) { - console.log('calendar-list-view.eventHtmlString(d): ', d); - return '

YES!

'; -} - -const CustomViewConfig = customViewConfig; export default createPlugin({ views: { - custom: CustomViewConfig + fecList: fecListViewConfig } }); diff --git a/fec/fec/static/js/modules/calendar-tooltip.js b/fec/fec/static/js/modules/calendar-tooltip.js index 3b087e968a..634cacb63c 100644 --- a/fec/fec/static/js/modules/calendar-tooltip.js +++ b/fec/fec/static/js/modules/calendar-tooltip.js @@ -2,38 +2,94 @@ * */ import Dropdown from './dropdowns.js'; -import Listeners from './listeners.js'; - -export function CalendarTooltip(content, $container) { - this.$content = $(content); - this.$container = $container; - this.$close = this.$content.find('.js-close'); - this.$dropdown = this.$content.find('.dropdown'); - this.exportDropdown = new Dropdown(this.$dropdown, { - checkboxes: false - }); - - this.events = new Listeners(); - this.events.on(this.$close, 'click', this.close.bind(this)); - this.events.on($(document.body), 'click', this.handleClickAway.bind(this)); - - this.$container.addClass('is-active'); -} +// import Listeners from './listeners.js'; + +/** + * Activates a new .tooltip element. Doesn't create it, but + * - activates listeners + * - activates the Dropdown + * - destroys the element on click-outside + * - handles adding listeners to and then destroying calendar events' tooltips/details pop-up + * (doesn't create the element but will remove it) + */ +export class CalendarTooltip { + /** + * @constructor + * @param {HTMLElement} el - The HTMLElement that is the tooltip, i.e. the .tooltip element + */ + constructor(el) { + CalendarTooltip.instances.push(this); + + el.previousElementSibling.classList.add('visible-tooltip'); -CalendarTooltip.prototype.handleClickAway = function(e) { - const $target = $(e.target); - if ( - !this.$content.has($target).length && - !this.$container.has($target).length - ) { - this.close(); + this.dropdown = new Dropdown(el.querySelector('.dropdown'), { checkboxes: false }); + + // Catch the click on its way in, which happens before the tooltip is created, + // otherwise we get into + // # 1) click capture phase + // # 2) create tooltip and add listeners + // # 3) click bubbling is caught by tooltip listeners + // # 4) tooltip is removed + window.addEventListener('click', CalendarTooltip.handleExternalClicks, { capture: true }); + + el.querySelector('.js-close').addEventListener('click', CalendarTooltip.handleCloseClick); } -}; - -CalendarTooltip.prototype.close = function() { - this.$content.remove(); - this.exportDropdown.destroy(); - this.$container.removeClass('is-active'); - this.$container.focus(); // TODO: jQuery deprecation - this.events.clear(); -}; + + // PUBLIC METHODS + + // GETTERS + + // SETTERS + + // METHODS + + // PUBLIC PROPERTIES + + // STATIC + static instances = []; + + /** + * @param {PointerEvent} e + */ + static handleCloseClick = function(e) { + e.stopImmediatePropagation(); + e.preventDefault(); + CalendarTooltip.destroyAll(); + }; + + /** + * @param {PointerEvent} e + */ + static handleExternalClicks = function(e) { + // If the target isn't inside a .tooltip, close this tooltip and destroy everything + if (!e.target.closest('.tooltip')) { + CalendarTooltip.destroyAll(); + } + }; + + static destroyAll = function() { + CalendarTooltip.instances.forEach(instance => { + instance.#selfDestruct(); + }); + CalendarTooltip.instances = []; + let calendarEntriesWithTooltips = document.querySelectorAll('.visible-tooltip'); + calendarEntriesWithTooltips.forEach(el => { + el.classList.remove('visible-tooltip'); + }); + calendarEntriesWithTooltips = []; + }; + + #selfDestruct = function() { + window.removeEventListener('click', CalendarTooltip.handleExternalClicks, true); + document.querySelector('.tooltip .js-close').removeEventListener('click', CalendarTooltip.handleCloseClick); + + let el = document.querySelector('#calendar-details'); + el.removeEventListener('mouseleave', CalendarTooltip.handleMouseLeave); + + this.dropdown.destroy(); + this.dropdown = null; + el.remove(); + el = null; + delete this; + }; +} diff --git a/fec/fec/static/js/modules/calendar.js b/fec/fec/static/js/modules/calendar.js index 1e09bbcf5d..f49bd2900c 100644 --- a/fec/fec/static/js/modules/calendar.js +++ b/fec/fec/static/js/modules/calendar.js @@ -1,51 +1,37 @@ -/* eslint-disable */ +/* eslint-disable */ // TODO: remove eslint-disable /** - * + * @fileoverview Using fullcalendar.io, builds and runs the various views for the fec.gov calendar pages + * @license CC0-1.0 + * @owner fec.gov + * @version 2.0 */ + +import bootstrap5Plugin from '@fullcalendar/bootstrap5'; // Being included because fullcalendar requires a themesystem and the default is too bossy import { Calendar as FullCalendar } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; -import listPlugin from '@fullcalendar/list'; -import timeGridPlugin from '@fullcalendar/timegrid'; -import bootstrap5Plugin from '@fullcalendar/bootstrap5'; // Being included because fullcalendar requires a themesystem and the default is too bossy -// import '@fullcalendar/core'; -// new Calendar(); -// import '@fullcalendar/common/vdom'; -import { default as Handlebars } from 'hbsfy/runtime.js'; +// import { default as Handlebars } from 'hbsfy/runtime.js'; import $ from 'jquery'; -// import moment from 'moment'; -import { default as _extend } from 'underscore/modules/extend.js'; import { default as _isEqual } from 'underscore/modules/isEqual.js'; import { default as URI } from 'urijs'; - -import { checkStartTime, className, getGoogleUrl, mapCategoryDescription } from './calendar-helpers.js'; +import { checkStartTime, dateTimeFormatOptions, getGoogleUrl, getDownloadUrl, getMs365Url, mapCategoryDescription } from './calendar-helpers.js'; +import { default as FecListViewPlugin } from './calendar-list-view.js'; import { CalendarTooltip } from './calendar-tooltip.js'; import Dropdown from './dropdowns.js'; -import { LOADING_DELAY, SUCCESS_DELAY, datetime, eq, isLargeScreen, toUpperCase } from './helpers.js'; +import { LOADING_DELAY, SUCCESS_DELAY, isLargeScreen } from './helpers.js'; import { pushQuery, updateQuery } from './urls.js'; -import { eventHtmlString } from './calendar-list-view.js'; -import { default as CustomViewPlugin } from './calendar-list-view.js'; import { default as template_details } from '../templates/calendar/details.hbs'; import { default as template_download } from '../templates/calendar/download.hbs'; -import { default as template_listToggles } from '../templates/calendar/listToggles.hbs'; import { default as template_subscribe } from '../templates/calendar/subscribe.hbs'; -// TODO: do we need to registerHelper? -// Handlebars.registerHelper('eq', eq); -// Handlebars.registerHelper('datetime', datetime); -// Handlebars.registerHelper('toUpperCase', toUpperCase); - /** @enum {html} */ const templates = { details: template_details, download: template_download, subscribe: template_subscribe, - listToggles: template_listToggles }; -const LIST_VIEWS = ['monthTime', 'monthCategory']; - -// const FC = $.fullCalendar; -// const Grid = FC.Grid; +// Moved to the plugin +// const FEC_LIST_SORT = ['monthTime', 'monthCategory']; // Globally override event sorting to order all-day events last // TODO: Convince fullcalendar.io support this behavior without monkey-patching @@ -59,24 +45,20 @@ const LIST_VIEWS = ['monthTime', 'monthCategory']; // }; export default function Calendar(opts) { - this.trashname = Calendar.name; - // this.opts = $.extend({}, this.defaultOpts(), opts); + console.log('new Calendar(opts): ', opts); this.opts = Object.assign({}, this.defaultOpts(), opts); - // this.$calendar = $(this.opts.selector); - console.log('this.opts.selector: ', this.opts.selector); this.calendarEl = document.querySelector(this.opts.selector); - // this.$head = $('.data-container__head'); this.headEl = document.querySelector('.data-container__head'); - // this.$calendar.fullCalendar(this.opts.calendarOpts); - // console.log('this.calendarEl: ', this.calendarEl); this.calendar = new FullCalendar(this.calendarEl, this.opts.calendarOpts); window.calendar = this.calendar; - this.url = URI(this.opts.url); - this.subscribeUrl = URI(this.opts.subscribeUrl); - this.exportUrl = URI(this.opts.exportUrl); + this.url_api = URI(this.opts.url_api); + this.url_subscribe = URI(this.opts.url_subscribe); + console.log('this.url_subscribe: ', this.url_subscribe); + this.url_export = URI(this.opts.url_export); this.filterPanel = this.opts.filterPanel; this.filterSet = this.filterPanel.filterSet; + this.tagList = this.opts.tagList; this.popoverId = 'calendar-popover'; this.detailsId = 'calendar-details'; @@ -106,28 +88,33 @@ export default function Calendar(opts) { // this.$calendar.on('click', '.fc-more', this.managePopoverControl.bind(this)); // this.calendarEl.querySelector('.fc-more').addEventListener('click', this.managePopoverControl.bind(this)); - // this.filterPanel.$form.on('change', this.filter.bind(this)); - // this.filterPanel.$form.addEventListener('change', this.filter.bind(this)); - // $(window).on('popstate', this.filter.bind(this)); - window.addEventListener('popstate', this.filter.bind(this)); + this.buildTagsForInitialFilters(); + + // if (queryParams.search) + // + + this.filterPanel.$form.on('change', this.handleFilterChange.bind(this)); + // this.filterPanel.$form.addEventListener('change', this.handleFilterChange.bind(this)); + $(window).on('popstate', this.handleFilterChange.bind(this)); + // window.addEventListener('popstate', this.handleFilterChange.bind(this)); updateQuery(this.filterSet.serialize(), this.filterSet.fields); - this.filter(); + this.handleFilterChange(); if (!isLargeScreen()) { // this.$head.after($('#filters')); - this.headEl.after(document.querySelector('#filters')); + this.headEl.insertAdjacentElement('afterend', document.querySelector('#filters')); } // Make it exist - this.calendar.render(); + // this.calendar.render(); this.styleButtons(); } Calendar.prototype.toggleListView = function(e) { - console.log('Calendar.toggleListView(e): ', e); + // console.log('Calendar.toggleListView(e): ', e); // const newView = $(e.target).data('trigger-view'); const newView = e.target.dataset.triggerView; // this.$calendar.fullCalendar('changeView', newView); @@ -135,16 +122,16 @@ Calendar.prototype.toggleListView = function(e) { }; /** - * + * TODO: TEST HOW DATES DISPLAY FOR COMPUTERS IN OTHER TIME ZONES */ Calendar.prototype.defaultOpts = function() { return { calendarOpts: { - headerToolbar: { //header: { + timeZone: 'America/New_York', // or 'UTC'? 'GMT'? + headerToolbar: { left: 'prev,next,today', - center: '', - right: 'monthTime,month', - // right: 'dayGridMonth,timeGridWeek,listWeek' + center: '', // this is where we'll put the list toggles + right: 'fecList,dayGridMonth', // fecList is custom, dayGridMonth comes from @fullcalendar/daygrid }, buttonIcons: false, // The icons fc should put on buttons buttonText: { // The text labels for buttons @@ -154,150 +141,178 @@ Calendar.prototype.defaultOpts = function() { // week: 'Week YAY' }, // contentHeight: 'auto', - // dayRender: this.handleDayRender.bind(this), + // dayRender: this.handleDayRender.bind(this), // Not valid here // dayPopoverFormat: 'MMMM D, YYYY', - initialView: 'custom',//'listMonth' + initialView: 'fecList',//'listMonth' themeSystem: 'bootstrap5', // of the three choices, bootstrap5 is the lowest impact - // eventRender: this.handleEventRender.bind(this), - // eventAfterAllRender: this.handleRender.bind(this), - // eventClick: this.handleEventClick.bind(this), - // success: this.success.bind(this), - // failure: this.failure.bind(this), - datesSet: this.datesSet.bind(this), - // eventLimit: true, - // nowIndicator: true, - // plugins: [customViewPlugin], - plugins: [dayGridPlugin, timeGridPlugin, listPlugin, bootstrap5Plugin, CustomViewPlugin], //CustomViewPlugin + // eventRender: this.handleEventRender.bind(this), // Not valid here + // eventAfterAllRender: this.handleRender.bind(this), // Not valid here + // eventClick: this.handleEventClick.bind(this), // TODO: What does this do? + // success: this.success.bind(this), // Not valid here + // failure: this.failure.bind(this), // Not valid here + datesSet: this.handleCalendarDatesSet.bind(this), // TODO: Is this doing anything here? + // eventLimit: true, // Not valid here + nowIndicator: true, + plugins: [dayGridPlugin, /*timeGridPlugin, listPlugin,*/ bootstrap5Plugin, FecListViewPlugin], views: { - // dayGridMonth: {}, - // timeGridWeek: {}, - // dayGrid: {}, - // timeGrid: {}, - // week: {}, - // day: {} + // dayGridMonth: {}, + // timeGridWeek: {}, + // dayGrid: {}, + // timeGrid: {}, + // week: {}, + // day: {} // agenda: { // scrollTime: '09:00:00', // minTime: '08:00:00', // maxTime: '20:00:00' // }, month: { - // eventLimit: 3, - buttonText: 'Grid' - }, - // monthCategory: { - // type: 'list', - // categories: true, - // sortBy: 'category', - // duration: { months: 1, intervalUnit: 'month' } - // }, - monthTime: { - type: 'list', - buttonText: 'List', - sortBy: 'time', - duration: { months: 1, intervalUnit: 'month' } - }, - custom: { - // listMonth: { - type: 'list', - // categories: true, - buttonText: 'List', - // sortBy: 'time', - // scopeThis: this, - // props: {this: this}, + type: 'grid', + dayMaxEvents: 3, + buttonText: 'Grid', + editable: false, + eventClassNames: function(info) { return ['FEC-EVENT']}, duration: { months: 1, intervalUnit: 'month' }, + // render: function(arg) {}, // Not valid here + // onRender: function(arg) {}, // Not valid here + // noEventsDidMount: function(info) {}, // Not valid here + // eventContent: function(info) { + // If this exists, it's called instead of Calendar.eventContent() + // If this doesn't return anything, events will be put onto the page but will have aria-hidden="true" + // return {html: ''}; + // }, + // dayHeaderContent: function(d) { + // The row of "Sun", "Mon", etc + // console.log('views.month.dayHeaderContent(d): ', d); + // If this exists, it's called instead of Calendar.dayHeaderContent() + // If this doesn't return anything, day headers will be put onto the page but will have aria-hidden="true" + // return { html: `
${d.text}
` }; + // }, + // dayHeaderDidMount: function(d) { + // console.log('views.month.dayHeaderDidMount(d): ', d); + // // If this exists, it's called instead of Calendar.dayHeaderContent() + // // If this doesn't return anything, day headers will be put onto the page but will have aria-hidden="true" + // return d; + // }, viewDidMount: function(arg) { - console.log('views.custom.viewDidMount(arg): ', arg); + console.log('month.view.viewDidMount(arg): ', arg); + // For some reason, the dayHeader (Sun, Mon, etc) and dateHeader (1, 2, 3) are getting aria-hidden + const hiddenElementsToShow = document.querySelectorAll( + `.fc-daygrid-header .fc-cell-inner[aria-hidden="true"], .fc-daygrid-day-number[aria-hidden="true"]` + ); + hiddenElementsToShow.forEach(el => {el.removeAttribute('aria-hidden')}); }, viewWillUnmount: function(arg) { - console.log('views.custom.viewWillUnmount(arg): ', arg); + console.log('views.month.viewWillUnmount(arg): ', arg); }, - // dayHeaderContent: function(d) { - // console.log('views.custom.dayHeaderContent(arg): ', d); - // return { html: `` }; - // }, - // eventContent: function(arg) { - // console.log('views.custom.eventContent(arg): ', arg); - // console.log(' this: ', this); - // return {html: eventHtmlString(arg)}; - // }, - eventDataTransform: function(arg) { - console.log('views.custom.eventDataTransform(arg): ', arg); - // return {html: eventHtmlString(arg)}; + eventClick: this.handleEventClick.bind(this), + // eventMouseEnter: function(info) { info.jsEvent.preventDefault(); console.log('eventMouseEnter(info): ', info); }, + // eventMouseLeave: function(info) { info.jsEvent.preventDefault(); console.log('eventMouseLeave(info): ', info); }, + eventDidMount: function(event) { + console.log('views.month.eventDidMount(event): ', event); + // console.log(' event.event._context.getCurrentData(): ', event.event._context.getCurrentData()); } }, - // noEventsDidMount: function(info) { - // console.log('noEventsDidMount(info): ', info); - // } - }, - dayHeaderContent: function(d) { - console.log('Calendar.dayHeaderContent(arg): ', d); - return { html: `` }; - }, - eventContent: function(arg) { - console.log('Calendar.eventContent(arg): ', arg); + fecList: { + type: 'list', + buttonText: 'List', + duration: { months: 1, intervalUnit: 'month' }, + // noEventsDidMount: function(info) {}, // Not valid here + // dayHeaderContent: function(d) {}, // Overridden by FecListViewPlugin + // eventContent: function(arg) {}, // Overridden by FecListViewPlugin + // eventDataTransform: function(arg) {}, // Overridden by FecListViewPlugin + // viewDidMount: function(arg) {}, // Overridden by FecListViewPlugin + // viewWillUnmount: function(arg) {}, // Overridden by FecListViewPlugin + // + // onRender: function(arg) {}, // Not valid here + // render: function(arg) {}, // Not valid here + eventDidMount: function(event) { + console.log('views.fecList.eventDidMount(event): ', event); + // console.log(' event.event._context.getCurrentData(): ', event.event._context.getCurrentData()); + } + } }, - // render: function(arg) { - // console.log('Calendar.render(arg): ', arg); + // dayHeaderContent: function(d) { + // This is called if views.month.dayHeaderContent doesn't exist but is never called for views.fecList + // If this doesn't return anything, day headers will be put onto the page but will have aria-hidden="true" + // return { html: `` }; + // }, + // eventContent: function(arg) { + // This is called if views.month.eventContent doesn't exist but is never called for views.fecList + // If this doesn't return anything, events will be put onto the page but will have aria-hidden="true" + // console.log('Calendar.eventContent(arg): ', arg); // }, - // onRender: function(arg) { - // console.log('Calendar.onRender(arg): ', arg); - // } + // render: function(arg) {}, // Not valid here + // onRender: function(arg) {}, // Not valid here }, sourceOpts: { startParam: 'min_start_date', endParam: 'max_start_date', initialDate: '2024-12-15', - success: this.success.bind(this), - failure: this.failure.bind(this), - datesSet: this.datesSet.bind(this) + success: this.handleCalendarSuccess.bind(this), + failure: this.handleCalendarFailure.bind(this), + datesSet: this.handleCalendarDatesSet.bind(this), // TODO: is this doing anything here? + eventDidMount: function(event) { + console.log('sourceOpts.eventDidMount(event): ', event); + // console.log(' event.event._context.getCurrentData(): ', event.event._context.getCurrentData()); + } } }; }; -Calendar.prototype.filter = function() { - console.log('Calendar.filter()'); +/** + * Called on $form.change, but also on window.popstate and inside the Calendar constructor + * @returns + */ +Calendar.prototype.handleFilterChange = function() { + // console.log('Calendar.handleFilterChange()'); const params = this.filterSet.serialize(); + // A possible way to get rid of _isEqual for Objects is to use + // new Set(Object.getOwnPropertyNames(o1)).intersection(new Set(Object.getOwnPropertyNames(o2))); + // and + // new Set(Object.values(o1)).intersection(new Set(Object.values(o2))); if (_isEqual(params, this.params)) { return; } - const url = this.url + const url_data = this.url_api .clone() .addQuery(params || {}) .toString(); - console.log(' url: ', url); + // console.log(' url_data: ', url_data); pushQuery(this.filterSet.serialize(), this.filterSet.fields); // this.$calendar.fullCalendar('removeEventSource', this.eventSources); - if (this.eventSources) this.eventSources.remove(); + // if (this.eventSources) this.eventSources.remove();// TODO: do we need to remove them anymore? // this.eventSources = $.extend({}, this.opts.sourceOpts, { url: url }); this.eventSources = Object.assign( {}, this.opts.sourceOpts, { - url: url, - // success: this.success.bind(this), - // failure: this.failure.bind(this), - // dayRender: this.handleDayRender.bind(this), - // eventRender: this.handleEventRender.bind(this), - // eventAfterAllRender: this.handleRender.bind(this), - // eventClick: this.handleEventClick.bind(this) + url: url_data, } ); - // this.$calendar.fullCalendar('addEventSource', this.eventSources); + this.calendar.removeAllEventSources(); this.calendar.addEventSource(this.eventSources); this.updateLinks(params); this.params = params; - // this.calendar.render(); + this.calendar.render(); }; -Calendar.prototype.datesSet = function(v1) { - v1.startStr = v1.startStr.slice(0,10); - v1.endStr = v1.endStr.slice(0,10); +/** + * Called when the view or month changes + * TODO: Do we still need this? is it doing anything—are startStr or endStr being used anywhere? + * @param {Object} obj + */ +Calendar.prototype.handleCalendarDatesSet = function(obj) { + console.log('Calendar.handleCalendarDatesSet(obj): ', obj); + // obj.startStr = obj.startStr.slice(0,10); + // obj.endStr = obj.endStr.slice(0,10); }; -Calendar.prototype.failure = function(v1) { - console.log('failure(v1): ', v1); +// TODO: do this? +Calendar.prototype.handleCalendarFailure = function(arg) { + console.log('Calendar.handleCalendarFailure(arg): ', arg); }; /** @@ -306,15 +321,11 @@ Calendar.prototype.failure = function(v1) { * @param {Response} response * @returns */ -Calendar.prototype.success = function(content, response) { - console.log('Calendar.success(content, response): ', content, response); +Calendar.prototype.handleCalendarSuccess = function(content, response) { + console.log('Calendar.handleCalendarSuccess(content, response): ', content, response); const self = this; setTimeout(function() { - // $('.is-loading') - // .removeClass('is-loading') - // .addClass('is-successful'); - // }, LOADING_DELAY); const elements = document.querySelectorAll('.is-loading'); elements.forEach(el => { el.classList.remove('is-loading'); @@ -323,45 +334,25 @@ Calendar.prototype.success = function(content, response) { }, LOADING_DELAY); setTimeout(function() { - // $('.is-successful').removeClass('is-successful'); const elements = document.querySelectorAll('is-successful'); elements.forEach(el => { el.classList.remove('is-successful'); }); }, SUCCESS_DELAY); - self.handleRender(self.calendar.view); - - return content.results.map(function(event) { - let processed = { - category: event.category, - location: event.location, - title: event.summary || 'Event title', - summary: event.summary || 'Event summary', - description: event.description || 'Event description', - state: event.state ? event.state.join(', ') : null, - start: event.start_date, - hasStartTime: checkStartTime(event), - end: event.end_date, - className: className(event), - tooltipContent: mapCategoryDescription(event.category), - allDay: event.all_day, - detailUrl: event.url - }; - processed.google = getGoogleUrl(processed); - processed.download = self.subscribeUrl - .clone() - .addQuery({ event_id: event.event_id }) - .toString(); + self.handleCalendarRender(self.calendar.view); - return processed; + content.results = content.results.map(event => { + return self.convertEventImplToVisualEvent(event); }); + + return content.results; }; Calendar.prototype.updateLinks = function(params) { console.log('Calendar.updateLinks(params): ', params); - const url = this.exportUrl.clone().addQuery(params || {}); - const subscribeURL = this.subscribeUrl.clone().addQuery(params || {}); + const url = this.url_export.clone().addQuery(params || {}); + const url_subscribe = this.url_subscribe.clone().addQuery(params || {}); const urls = { ics: url.toString(), csv: url @@ -375,40 +366,45 @@ Calendar.prototype.updateLinks = function(params) { encodeURIComponent( url .clone() - .protocol('http') + .protocol('webcal') .toString() ), calendar: url.protocol('webcal').toString(), + subscribe: url_subscribe.protocol('webcal').toString(), - googleSubscribe: + subscribe_webcal: url_subscribe.protocol('webcal').toString(), + subscribe_google: 'https://calendar.google.com/calendar/render?cid=' + encodeURIComponent( - subscribeURL + url_subscribe + .clone() + .protocol('webcal') + .toString() + ), + subscribe_ms365: + // 'https://outlook.office.com/owa?path=%2Fcalendar%2Faction%2Fcompose&rru=addsubscription&name=FEC&url=' + + 'https://outlook.office.com/calendar/addfromweb?name=FEC&url=' + + encodeURIComponent( + url_subscribe .clone() - .protocol('http') + // .setSearch('min_start_date', '01/01/2025') + // .setSearch('per_page', 2) + .protocol('webcal') .toString() ), - calendarSubscribe: subscribeURL.protocol('webcal').toString() + + // url_subscribe.protocol('webcal').toString(), }; // this.$download.html(templates.download(urls)); this.downloadEl.innerHTML = templates.download(urls); // this.$subscribe.html(templates.subscribe(urls)); this.subscribeEl.innerHTML = templates.subscribe(urls); - if (this.downloadButton) { - this.downloadButton.destroy(); - } - - if (this.subscribeButton) { - this.subscribeButton.destroy(); - } + if (this.downloadButton) this.downloadButton.destroy(); + this.downloadButton = new Dropdown(this.opts.selector_download, { checkboxes: false }); - this.downloadButton = new Dropdown(this.opts.selector_download, { - checkboxes: false - }); - this.subscribeButton = new Dropdown(this.opts.selector_subscribe, { - checkboxes: false - }); + if (this.subscribeButton) this.subscribeButton.destroy(); + this.subscribeButton = new Dropdown(this.opts.selector_subscribe, { checkboxes: false }); }; /** @@ -418,15 +414,15 @@ Calendar.prototype.styleButtons = function() { const baseClasses = ['button']; // document.querySelector('.fc-button').classList.add(...baseClasses); // document.querySelectorAll('.fc-button').forEach(el => { el.classList.add(...baseClasses) }); - document.querySelector('.fc-toolbar-start .fc-today-button').classList.add('button', 'button--alt'); - document.querySelector('.fc-toolbar-start .fc-next-button').classList.add( - 'button', 'button--next', 'button--standard'); - document.querySelector('.fc-toolbar-start .fc-prev-button').classList.add( - 'button', 'button--previous', 'button--standard'); - document.querySelector('.fc-toolbar-end .btn-group').classList.add('toggles--buttons'); - document.querySelector('.fc-toolbar-end .fc-monthTime-button').classList.add( - 'button', 'button--list', 'button--alt'); - document.querySelector('.fc-toolbar-end .fc-month-button').classList.add('button', 'button--grid', 'button--alt'); + // document.querySelector('.fc-toolbar-start .fc-today-button').classList.add('button', 'button--alt'); + // document.querySelector('.fc-toolbar-start .fc-next-button').classList.add( + // 'button', 'button--next', 'button--standard'); + // document.querySelector('.fc-toolbar-start .fc-prev-button').classList.add( + // 'button', 'button--previous', 'button--standard'); + // document.querySelector('.fc-toolbar-end .btn-group').classList.add('toggles--buttons'); + // document.querySelector('.fc-toolbar-end .fc-fecList-button').classList.add( + // 'button', 'button--list', 'button--alt'); + // document.querySelector('.fc-toolbar-end .fc-dayGridMonth-button').classList.add('button', 'button--grid', 'button--alt'); }; /** @@ -434,24 +430,29 @@ Calendar.prototype.styleButtons = function() { * @param {ViewImpl} view * No other params */ -Calendar.prototype.handleRender = function(view) { - console.log('Calendar.handleRender(view, two): ', view); - console.log(' this: ', this); +Calendar.prototype.handleCalendarRender = function(view) { + console.log('Calendar.handleCalendarRender(view, two): ', view); + console.log(' calendar.view: ', calendar.view); - $(document.body).trigger($.Event('calendar:rendered')); + $(document.body).trigger($.Event('calendar:rendered')); // TODO: do we need this? Is anything listening to it? - // if (LIST_VIEWS.indexOf(view.name) !== -1) { - if (LIST_VIEWS.indexOf(view.type) !== -1) { - console.log(' if'); - this.manageListToggles(view); - } else if (this.$listToggles) { - console.log(' else if'); - this.$listToggles.remove(); - this.$listToggles = null; - } else { - console.log(' else'); - } + // const viewButtonMonth = document.querySelector('.fc-dayGrid-button'); + // viewButtonMonth.dataset.triggerView = 'dayGridMonth'; + // viewButtonMonth.addEventListener('click', this.handleViewToggle.bind(this)); + + // MOVED TO THE PLUGIN + // if (FEC_LIST_SORT.includes(calendar.view.custom.sortBy)) { + // console.log(' if'); + // this.manageListToggles(view); + + // } else if (this.$listToggles) { + // console.log(' else if'); + // this.$listToggles.remove(); + // this.$listToggles = null; + // } else { + // console.log(' else'); + // } // this.$calendar // .find('.fc-more') // .attr({ tabindex: '0', 'aria-describedby': this.popoverId }); @@ -465,19 +466,24 @@ Calendar.prototype.handleRender = function(view) { /** * - * @param {*} view + * @param {PointerEvent} e */ -Calendar.prototype.manageListToggles = function(view) { - console.log('Calendar.manageListToggles(view): ', view); - if (!this.$listToggles) { - this.$listToggles = $('
'); - this.$listToggles.appendTo(this.$calendar.find('.fc-toolbar-end')); - } - this.$listToggles.html(templates.listToggles(view.options)); - // Highlight the "List" button on monthTime - if (view.name === 'monthCategory') { - this.$calendar.find('.fc-monthTime-button').addClass('fc-state-active'); - } +Calendar.prototype.handleViewToggle = function(e) { + // console.log('Calendar.handleViewToggle(view): ', e); + calendar.changeView(e.target.dataset.triggerView); + e.target.classList.add('active'); + + // const togglesHolder = document.querySelector('.fc-header-toolbar .fc-toolbar-center'); + + // if (!this.$listToggles) { + // this.$listToggles = $('
'); + // this.$listToggles.appendTo(this.$calendar.find('.fc-toolbar-end .toggles--buttons')); + // } + // this.$listToggles.html(templates.listToggles(view.options)); + // // Highlight the "List" button on monthTime + // if (view.name === 'monthCategory') { + // this.$calendar.find('.fc-monthTime-button').addClass('fc-state-active'); + // } }; Calendar.prototype.handleEventRender = function(event, element) { @@ -502,18 +508,78 @@ Calendar.prototype.handleDayRender = function(date, cell) { } }; -Calendar.prototype.handleEventClick = function(calEvent, jsEvent) { - console.log('Calendar.handleEventClick(calEvent, jsEvent): ', calEvent, jsEvent); - const $target = $(jsEvent.target); - if (!$target.closest('.tooltip').length) { - const $eventContainer = $target.closest('.fc-event'); - const tooltip = new CalendarTooltip( - templates.details(_extend({}, calEvent, { detailsId: this.detailsId })), +/** + * + * @param {Object} eventObject + * @param {HTMLElement} eventObject.el - The button element clicked (.fc-event) + * @param {EventImpl} eventObject.event + * @param {PointerEvent} eventObject.jsEvent + * @param {ViewImpl} eventObject.view + */ +Calendar.prototype.handleEventClick = function(eventObject) { + console.log('Calendar.handleEventClick(eventObject): ', eventObject); + // eventObject.jsEvent.preventDefault(); + // eventObject.jsEvent.stopPropagation(); - $eventContainer - ); - $eventContainer.append(tooltip.$content); + const tooltipParentEl = eventObject.el.parentNode; + console.log('tooltipParentEl: ', tooltipParentEl); + + + let tooltip = tooltipParentEl.querySelector('.tooltip'); + console.log(' tooltip: ', tooltip); + + // If there's no tooltip, + if (!tooltip || tooltip == null) { + // build its content, + + tooltip = document.createElement('div'); + tooltip.setAttribute('class', 'tooltip tooltip--under cal-details'); + tooltip.setAttribute('id', this.detailsId); + tooltip.setAttribute('role', 'tooltip'); + // + + + const eventDataObjForTemplate = this.convertEventImplToVisualEvent(eventObject.event._def.publicId); + // eventDataObjForTemplate.detailsId = this.detailsId; + console.log(' eventDataObjForTemplate: ', eventDataObjForTemplate); + const popupDetailsContent = templates.details(eventDataObjForTemplate); + + // console.log(' going to create this tooltip: ', popupDetailsContent); + // append the content, + tooltip.innerHTML = popupDetailsContent; + // and select the element again. + // tooltip = tooltipParentEl.querySelector('.tooltip'); + eventObject.el.insertAdjacentElement('afterend', tooltip); + + // tooltip = null; + // event + // tooltip = document.createElement('div'); + // tooltipHolder.classList.add('tooltip-holder'); + // target.appendChild(tooltipHolder); } + + new CalendarTooltip(tooltip); + tooltip = null; + + // NOW we can activate the tooltip for this event + + + // const tooltip = new CalendarTooltip( + // , + // target + // ); + // target.appendChild(tooltip.$content); + + // const $target = $(jsEvent.target); + // if (!$target.closest('.tooltip').length) { + // const $eventContainer = $target.closest('.fc-event'); + // const tooltip = new CalendarTooltip( + // templates.details(_extend({}, calEvent, { detailsId: this.detailsId })), + + // $eventContainer + // ); + // $eventContainer.append(tooltip.$content); + // } }; // Simulate clicks when hitting enter on certain full-calendar elements @@ -524,6 +590,10 @@ Calendar.prototype.simulateClick = function(e) { } }; +/** + * + * @param {*} e + */ Calendar.prototype.managePopoverControl = function(e) { console.log('Calendar.managePopoverControl(e): ', e); const $target = $(e.target); @@ -537,3 +607,127 @@ Calendar.prototype.managePopoverControl = function(e) { $target.trigger('focus'); }); }; + +/** + * Creates the original dataSources event, adds parameters as needed, and returns an object used to replace that + * initial element or used to send into hbs templates. + * @param {(number|Object)} event - the number that comes in from dataSources or the value saved to publicId + * @returns {Object} Data object to be used for events through the app and in hbs templates + */ +Calendar.prototype.convertEventImplToVisualEvent = function(event) { + console.log('Calendar.convertEventImplToVisualEvent(event): ', event); + + let d = {}; + let toReturn = {}; + + // If event is an object, we need to create it for the first time, + if (typeof event == 'object') { + d = event; + + toReturn = { + allDay: d.all_day, + category: d.category, + className: '', // className(d), + description: d.description || 'Event description', + end: d.end_date ? d.end_date : null, // used by getGoogleUrl() // TODO: Do we need this if we're using GMT? + end_google: d.end_date, + event_id: d.event_id, + display: 'block', + hasStartTime: checkStartTime(d), + id: d.event_id, // gets converted to publicId to be used with calendar.getEventById(); + location: d.location, + start: d.start_date, // used by getGoogleUrl() // TODO: Do we need this if we're using GMT? + start_google: d.start_date, + state: d.state ? d.state.join(', ') : null, + summary: d.summary || 'Event summary', + title: d.summary || 'Event title', + tooltipContent: mapCategoryDescription(d.category), + url_details: d.url + }; + + // Adjusting the datetime here (from GMT to ET) doesn't seem to work + + toReturn.url_google = getGoogleUrl({ + start: 'START', + end: 'END', + title: 'TITLE', + summary: 'SUMMARY' + }); + toReturn.url_ms365 = getMs365Url({ + start: 'START', + end: 'END', + title: 'TITLE', + summary: 'SUMMARY' + }); + toReturn.url_download = getDownloadUrl( + this.url_subscribe + .clone() + .addQuery({ event_id: d.event_id }) + .toString() + ); + + // But it's not an object, we need to find the calendar's event object to just use those values + } else { + d = calendar.getEventById(event); + console.log(' d: ', d); + toReturn = Object.assign( + {}, + d._def.extendedProps, + { + allDay: d._def.allDay, + className: d._def.className, + hasEnd: d._def.hasEnd, + // end: d._def.end_date ? d.end_date : null, // used by getGoogleUrl() // TODO: Do we need this if we're using GMT? + id: d.event_id, // gets converted to publicId to be used with calendar.getEventById(); + publicId: d._def.publicId, + range: d._instance.range, + // start: d._def.start_date, // used by getGoogleUrl() // TODO: Do we need this if we're using GMT? + title: d._def.title, + // url_details: d._def.url, + } + ); + toReturn.url_google = getGoogleUrl({ + start: 'START2', + end: 'END2', + title: 'TITLE2', + summary: 'SUMMARY2' + }); + + // Get the start and end Date objects (GMT) + let start_date = new Date(toReturn.range.start); + let end_date = toReturn.hasEnd ? new Date(toReturn.range.end) : false; + + start_date.setMinutes(start_date.getMinutes() + start_date.getTimezoneOffset()); + toReturn.start_dayMonthTime = start_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL_MONTH_DAY); + toReturn.start_dayMonthTime += `, ${start_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE)}`; + + toReturn.start_iso = start_date.toISOString(); + // toReturn.start_day = start_date.toLocaleDateString('en-US', dateTimeFormatOptions.WEEKDAY_FULL); + toReturn.start_date_string = start_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL); + // if (toReturn.hasStartTime) + // toReturn.start_time = start_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE); + + if (end_date) { + end_date.setMinutes(end_date.getMinutes() + end_date.getTimezoneOffset()); + toReturn.end_dayMonthTime = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL_MONTH_DAY); + toReturn.end_dayMonthTime += `, ${end_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE)}`; + toReturn.end_iso = end_date.toISOString(); + // toReturn.end_dayMonth = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL_MONTH_DAY); + + // toReturn.end_day = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.WEEKDAY_FULL); + // toReturn.end_date_string = end_date.toLocaleDateString('en-US', dateTimeFormatOptions.DATE_FULL); + // if (toReturn.hasStartTime) + // toReturn.end_time = end_date.toLocaleTimeString('en-US', dateTimeFormatOptions.TIME_SIMPLE); + } + } + console.log(' going to return: ', toReturn); + return toReturn; +} + +Calendar.prototype.buildTagsForInitialFilters = function() { + const filterAndTagName = 'calendar_category_id'; + const selectedCategoryInputs = document.querySelectorAll(`#category-filters input[name="${filterAndTagName}"]:checked`); + selectedCategoryInputs.forEach(input => { + this.tagList.addTag(null, { key: input.value, name: filterAndTagName, value: input.labels[0].textContent }); + }); +} diff --git a/fec/fec/static/js/modules/dropdowns.js b/fec/fec/static/js/modules/dropdowns.js index 0d1c0762f1..5bbb4118e2 100644 --- a/fec/fec/static/js/modules/dropdowns.js +++ b/fec/fec/static/js/modules/dropdowns.js @@ -16,6 +16,7 @@ const defaultOpts = { * @constructor * @param {string} selector - CSS selector for the fieldset that contains everything * @param {Object} opts - Options + * @param {boolean} opts.checkboxes */ export default function Dropdown(selector, opts) { this.opts = $.extend({}, defaultOpts, opts); @@ -171,6 +172,12 @@ Dropdown.prototype.handleCheckboxRemoval = function($input) { } }; +/** + * + * @param {JQuery.Events} e + * @param {Object} opts + * @param {string} opts.key + */ Dropdown.prototype.handleRemoveClick = function(e, opts) { let $input = $(e.target) .parent() diff --git a/fec/fec/static/js/modules/filters/filter-set.js b/fec/fec/static/js/modules/filters/filter-set.js index c7f1e3829c..97bcf47a19 100644 --- a/fec/fec/static/js/modules/filters/filter-set.js +++ b/fec/fec/static/js/modules/filters/filter-set.js @@ -27,7 +27,7 @@ export default function FilterSet(elm) { this.$body.on('filters:validation', this.handleValidation.bind(this)); this.efiling = this.$body.data('efiling-filters') || false; - /** Array of names of filter/API var name IDs {@example ['committee_id', 'form_line_number']} */ + /** Array of names of filter/API var name IDs ex. ['committee_id', 'form_line_number'] */ this.fields = []; this.isValid = true; this.firstLoad = true; diff --git a/fec/fec/static/js/modules/filters/filter-tags.js b/fec/fec/static/js/modules/filters/filter-tags.js index ac0706ea22..23d2e35c80 100644 --- a/fec/fec/static/js/modules/filters/filter-tags.js +++ b/fec/fec/static/js/modules/filters/filter-tags.js @@ -110,6 +110,7 @@ export default function TagList(opts) { * @param {boolean} [opts.range=false] - If true, will use opts.rangename over opts.name * @param {string} [opts.rangeName] - Used as data-tag-category="" if opts.range is true * @param {boolean} [opts.nonremovable=false] - determines which template to use. Default: false + * @param {string} [opts.value] - The text label */ TagList.prototype.addTag = function(e, opts) { const tag = opts.nonremovable diff --git a/fec/fec/static/js/modules/urls.js b/fec/fec/static/js/modules/urls.js index c8931aa2cb..0ed6a7e32c 100644 --- a/fec/fec/static/js/modules/urls.js +++ b/fec/fec/static/js/modules/urls.js @@ -12,8 +12,9 @@ import { sanitizeQueryParams } from './helpers.js'; /** * Takes a list of key/value, runs them through {@linkcode nextUrl()} and sets them to the window.history * then logs an analytics pageView - * @param {Object} params - Object of key/value for query params and their values - * @param {Array} fields - Object TODO + * @param {Object} params - Object of key/value for query params and their values. + * ex: {data_type: ['processed'], max_date: ['12/31/2000'], min_date: ['01/01/2000']} + * @param {Array} fields - Array of allowed filter fields. ex: ['data_type', 'committee_id', 'max_date', 'min_date'] */ export function updateQuery(params, fields) { const queryString = nextUrl(params, fields); diff --git a/fec/fec/static/js/pages/calendar-page.js b/fec/fec/static/js/pages/calendar-page.js index 702feed812..35111e7371 100644 --- a/fec/fec/static/js/pages/calendar-page.js +++ b/fec/fec/static/js/pages/calendar-page.js @@ -1,10 +1,10 @@ -/* eslint-disable */ /** - * + * For the calendar page, + * - initializes the filterPanel + * - initializes the tagList + * - initializes the Calendar (Calendar is the FEC implementation of @fullcalendar/core) */ -import { Calendar as FullCalendar } from '@fullcalendar/core'; - -import { calendarDownload, getUrl } from '../modules/calendar-helpers.js'; +import { getUrl } from '../modules/calendar-helpers.js'; import Calendar from '../modules/calendar.js'; import FilterPanel from '../modules/filters/filter-panel.js'; import TagList from '../modules/filters/filter-tags.js'; @@ -13,24 +13,21 @@ import TagList from '../modules/filters/filter-tags.js'; const filterPanel = new FilterPanel(); // Initialize filter tags -const $tagList = new TagList({ +const tagList = new TagList({ resultType: 'events', emptyText: 'all events' -}).$body; +}); -$('.js-filter-tags').prepend($tagList); +$('.js-filter-tags').prepend(tagList.$body); // Initialize calendar new Calendar({ + filterPanel: filterPanel, + tagList: tagList, selector: '#calendar', selector_download: '#calendar-download', selector_subscribe: '#calendar-subscribe', - url: getUrl(['calendar-dates']), - // url: '/static/calendar-dates.json', - exportUrl: calendarDownload(['calendar-dates', 'export']), - subscribeUrl: getUrl(['calendar-dates', 'export'], '', [ - 'sub' - ]), - filterPanel: filterPanel, - initialDate: '2024-12-15', + url_api: getUrl(['calendar-dates']), + url_export: getUrl(['calendar-dates', 'export']), + url_subscribe: getUrl(['calendar-dates', 'export'], '', ['sub']) }); diff --git a/fec/fec/static/js/templates/calendar/details.hbs b/fec/fec/static/js/templates/calendar/details.hbs index 870995b027..72c62ff92c 100644 --- a/fec/fec/static/js/templates/calendar/details.hbs +++ b/fec/fec/static/js/templates/calendar/details.hbs @@ -1,34 +1,32 @@ - --}} diff --git a/fec/fec/static/js/templates/calendar/event.hbs b/fec/fec/static/js/templates/calendar/event.hbs index 9e14896f09..ceb0ea4b7e 100644 --- a/fec/fec/static/js/templates/calendar/event.hbs +++ b/fec/fec/static/js/templates/calendar/event.hbs @@ -18,8 +18,8 @@ {{#if location}} Location: {{location}} {{/if}} - {{#if detailUrl}} - Learn more + {{#if url_details}} + Learn more {{/if}}
diff --git a/fec/fec/static/js/templates/calendar/events.hbs b/fec/fec/static/js/templates/calendar/events.hbs index 2ef1bc78c7..be88c4cb87 100644 --- a/fec/fec/static/js/templates/calendar/events.hbs +++ b/fec/fec/static/js/templates/calendar/events.hbs @@ -1,24 +1,24 @@ -
+
{{#each groups}} -
+
{{#if title}} {{!--

{{title}}

--}} - + {{/if}} {{#each events}}
- {{datetime start format="pretty"}} - {{datetime start format="fullDayOfWeek"}} + {{start_date_string}} + {{start_day}}
- {{#if hasStartTime}} - {{datetime start format="time"}} - {{#if end}}–{{datetime end format="time"}}{{/if}} - {{else}} + {{~#if hasStartTime ~}} + {{~ start_time_string ~}} + {{#if end_time_string}}–{{end_time_string}}{{/if}} + {{~else~}}   - {{/if}} + {{~/if~}}

{{summary}}

@@ -26,8 +26,8 @@ {{#if location}} Location: {{location}} {{/if}} - {{#if detailUrl}} - Learn more + {{#if url_details}} + Learn more {{/if}}
@@ -49,12 +49,13 @@
- diff --git a/fec/fec/static/js/templates/calendar/listToggles.hbs b/fec/fec/static/js/templates/calendar/listToggles.hbs index 9b99342cde..22a592e088 100644 --- a/fec/fec/static/js/templates/calendar/listToggles.hbs +++ b/fec/fec/static/js/templates/calendar/listToggles.hbs @@ -1,12 +1,12 @@
Sort by: + class="toggle js-toggle-list-sort + {{#if (eq listEventOrder 'monthTime')}}is-active{{/if}}" + data-trigger-sort="monthTime">Date | + class="toggle js-toggle-list-sort + {{#if (eq listEventOrder 'monthCategory')}}is-active{{/if}}" + data-trigger-sort="monthCategory">Category
diff --git a/fec/fec/static/js/templates/calendar/subscribe.hbs b/fec/fec/static/js/templates/calendar/subscribe.hbs index 6ec52830ed..b7395328b6 100644 --- a/fec/fec/static/js/templates/calendar/subscribe.hbs +++ b/fec/fec/static/js/templates/calendar/subscribe.hbs @@ -1,9 +1,9 @@ - diff --git a/fec/fec/static/scss/components/_buttons.scss b/fec/fec/static/scss/components/_buttons.scss index caa7adfd51..5436679f76 100644 --- a/fec/fec/static/scss/components/_buttons.scss +++ b/fec/fec/static/scss/components/_buttons.scss @@ -74,7 +74,7 @@ } .button--alt { - background-color: none; + background-color: transparent; border-color: $gray; color: $base; @extend .button; diff --git a/fec/fec/static/scss/components/_calendar.scss b/fec/fec/static/scss/components/_calendar.scss index 294efad624..9dce2cd861 100644 --- a/fec/fec/static/scss/components/_calendar.scss +++ b/fec/fec/static/scss/components/_calendar.scss @@ -3,11 +3,12 @@ // Styles for the Calendar page, which uses fullcalendar // +// Before the calendar starts .calendar__head { border-bottom: 1px solid $neutral; padding: u(1rem); - @include clearfix(); + // @include clearfix(); .data-container__action { margin-top: u(1rem); @@ -21,477 +22,336 @@ } } -.fc { - display: block; // Overriding fc defaults - - .fc-view-outer { - padding: 0 2rem 2rem !important; - position: relative; - - .fc-view { - display: block; - position: relative; - } - } - // Controls - .fc-toolbar { - // @include clearfix(); - padding: u(1rem); - } - - .fc-toolbar-start { - .fc-prev-button, - .fc-next-button { - background-position: center; - margin-right: 1px; - padding: u(.8rem 1rem); - text-indent: -9999px; - - &::after { - left: u(.5rem); - right: u(.5rem); - } - } - .fc-today-button { - margin-left: u(1rem); - } - } - - .fc-toolbar-end { - .button--alt { - padding: 8px 2rem; - text-indent: -9999px; - width: 4rem; - } - } - - .fc-list-day { - display: block; - padding: 0; - } -} - -@include media($med) { - .calendar__head { - padding: u(1rem 2rem); - } -} - -/* -.fc-view-container { +// Header toolbar (prev, next, This Month, Sort by, List, Grid) +.fc-header-toolbar { font-family: $sans-serif; - padding: u(0 2rem 2rem 2rem); -} - -.fc-bg { - z-index: -1; - position: absolute; - top: 0; - left: 0; - right: 0; - - .fc-day { - font-weight: bold; - padding: u(.7rem 1rem 0 2rem); - } -} - -.fc-row { - position: relative; -} - -.calendar__head { - @include clearfix(); - border-bottom: 1px solid $neutral; - padding: u(1rem); - - .data-container__action { - margin-top: u(1rem); - width: 100%; - - @include media($lg) { - width: auto; - float: right; - margin-top: 0; - } - } -} - -.fc-view-controls { - @include clearfix(); - padding: u(2rem); - width: 100%; + padding: 1rem; + // @include clearfix(); } - .fc-toolbar-start { - float: left; - - .fc-button-group { - float: left; - margin-right: u(1rem); - } - - .fc-today-button { - margin-left: u(1rem); - } - .fc-prev-button, .fc-next-button { + background-position: center; margin-right: 1px; - text-indent: -9999px; padding: u(.8rem 1rem); + text-indent: -9999px; + @extend .button--standard; &::after { left: u(.5rem); right: u(.5rem); } } + .fc-prev-button { + @extend .button--previous; + } + .fc-next-button { + @extend .button--next; + } + .fc-today-button { + margin-left: u(1rem); + @extend .button--alt; + } } +.fc-toolbar-center { + display: none; + flex-grow: 1; + text-align: right; -.fc-prev-button { - border-radius: 2px 0 0 2px; -} - -.fc-next-button { - border-radius: 0 2px 2px 0; -} + @include media($med) { + display: block; + } -.fc-state-active.button--alt { - @extend .button--alt, .is-active; + .toggle:last-child { + margin-right: 0; + } } - .fc-toolbar-end { display: none; -} -.fc-center { - text-align: left; + .btn-group { + @extend .toggles--buttons; + } + .btn { + background-position: center; + padding: 8px 2rem; + text-indent: -9999px; + width: 4rem; - h2 { - margin: 0; + &.active { + @extend .is-active; + } + } + .fc-fecList-button { + @extend .button--list; + @extend .button--alt; + } + .fc-dayGridMonth-button { + @extend .button--grid; + @extend .button--alt; + } + @include media($med) { + display: block; } } -// Body - -.fc-day-header { - color: $primary; - font-weight: normal; - padding-top: u(.5rem); -} -.fc-content-skeleton { - height: 100%; +// fullcalendar/bootstrap overrides +.fc { + font-family: $sans-serif; + gap: 1rem; - table { - min-height: u(12rem); + .fc-view-outer { + padding: u(0 2rem 2rem 2rem); + } + + // overrides specifically for our custom list view + &[data-fec-view="list"] { + display: block; // Overriding fc defaults + + .fc-view-outer { + padding: 0 2rem 2rem !important; + position: relative; + } + .fc-fecList-view { + display: block; + position: relative; + } + .fc-list-day { + display: block; + padding: 0; + } } } -.fc-other-month { - &.fc-day-top, - &.fc-day { - opacity: .5; - } +.fc-theme-bootstrap5 { + line-height: inherit; } -.fc-week { - td:not(.fc-day-top) { - border-width: 0 2px; - border-style: solid; - border-color: $neutral; - border-top: none; - } - - &:last-child { - border-bottom: 2px solid $neutral; +// Only applies to the grid +.fc-daygrid-header { + .fc-day { + color: $primary; + font-weight: normal; + padding-top: u(.5rem); } } -.fc-day-top { - border: 2px solid $neutral; - border-bottom: none; - color: $primary; - font-weight: bold; - padding: u(.3rem 1rem); +.fc-daygrid { + padding: 0 1rem; } - -.fc-day-number.fc-today { - background-color: $primary; - color: $inverse; +.fc-daygrid-day-header { + flex-direction: row; + font-weight: bold; + min-height: 3rem; + overflow-x: hidden; + width: 100%; } - -.fc-event-container { +.fc-daygrid-day-body { padding: 2px; - vertical-align: top; -} - -.fc-more { - margin: 0 4px; } +.fc-daygrid-day { + border: 1px solid $neutral; + min-height: 12rem; + padding: 0; -.fc-content { - white-space: nowrap; - overflow: hidden; - background: none; - color: $primary; - font-size: u(1.4rem); - line-height: 1.2; - padding: 2px 4px; - position: relative; - - &::before { - content: ''; - display: block; - width: u(.6rem); - height: u(.6rem); - background-color: $primary; - border-radius: u(.8rem); - float: left; - margin: 5px 5px 5px 0; + &.fc-day-today { + background: transparent; } - &::after { - @include linear-gradient(90deg, rgba($gray-lightest, 0), $gray-lightest); - content: ''; - display: block; + &[data-date$="-01"] .fc-daygrid-day-number::after { + display: inline-block; position: absolute; - top: 0; - bottom: 0; - right: 0; - width: u(3rem); + left: 1.33em; } -} - -.fc-multi-day { - .fc-content { - border: 1px solid $gray; - border-radius: 2px; - padding-left: 2px; - - &::before { - display: none; - } + &[data-date$="-01-01"] .fc-daygrid-day-number::after { + content: 'January'; } -} - -.is-active { - .fc-content { - background-color: $primary; - border-radius: 4px; - color: $inverse; - - &::before { - background-color: $inverse; - } - - &::after { - display: none; - background-color: transparent; - } + &[data-date$="-02-01"] .fc-daygrid-day-number::after { + content: 'February'; + } + &[data-date$="-03-01"] .fc-daygrid-day-number::after { + content: 'March'; + } + &[data-date$="-04-01"] .fc-daygrid-day-number::after { + content: 'April'; + } + &[data-date$="-05-01"] .fc-daygrid-day-number::after { + content: 'May'; + } + &[data-date$="-06-01"] .fc-daygrid-day-number::after { + content: 'June'; + } + &[data-date$="-07-01"] .fc-daygrid-day-number::after { + content: 'July'; + } + &[data-date$="-08-01"] .fc-daygrid-day-number::after { + content: 'August'; + } + &[data-date$="-09-01"] .fc-daygrid-day-number::after { + content: 'September'; + } + &[data-date$="-10-01"] .fc-daygrid-day-number::after { + content: 'October'; + } + &[data-date$="-11-01"] .fc-daygrid-day-number::after { + content: 'November'; + } + &[data-date$="-12-01"] .fc-daygrid-day-number::after { + content: 'December'; } -} - -.fc-title { - display: inline-block; - vertical-align: top; -} -.fc-time { - display: inline-block; + .fc-daygrid-day-number { + padding: .5em 1rem; + } } -.fc-limited { - display: none; -} +// GRID CALENDAR ENTRIES +// UNDO - First, strictly undoing the defaults +// Overriding the awful event absolute positioning +.fc-daygrid-day-events { + height: auto !important; -.cal__category { - border-radius: 3px; - padding: u(.5rem); + .fc-abs { + position: relative !important; + left: initial !important; + right: initial !important; + top: initial !important; + } } -// Event details -.cal-details { +.fc-daygrid-event { font-size: u(1.4rem); - left: auto !important; - top: auto !important; - margin-top: u(1.5rem); - margin-left: u(-8rem); - padding: 0 !important; - text-align: left; - width: u(26rem); - white-space: initial; + line-height: 1.2; - &::before { - @include triangle(2rem, $primary, up); - background-image: none; - left: 20%; - top: u(-2rem) !important; + @include media($lg) { + font-size: 1.6rem; } - .button--close--primary { - position: absolute; - top: 0; - right: 0; + &:focus { + box-shadow: none; } -} + &:hover { + cursor: pointer; -.fc-event-container:nth-child(1) { - .cal-details { - margin-left: unset; + .fc-event-inner { + background-color: $gray-lightest; + } + } - &::before { - right: auto; - left: u(2rem); + &.visible-tooltip { + pointer-events: none; + + .fc-event-inner { + background-color: $federal-blue; + border-radius: 4px; + color: $inverse; /* (white) */ + + &::before { + background-color: $inverse; + } + &::after { + display: none; + } } } } -.fc-event-container:nth-child(7) { - .cal-details { - right: 0; +.fc-h-event { + background-color: inherit; + border: none; + + .fc-event-inner { + color: unset; + padding: 2px 4px; &::before { - left: auto; - right: u(1rem); + background-color: $primary; + border-radius: u(.8rem); + content: ''; + display: block; + float: left; + height: u(.6rem); + margin: 5px 5px 5px 0; + width: u(.6rem); } &::after { - left: auto; - right: u(1.2rem); + bottom: 0; + content: ''; + display: block; + position: absolute; + right: 0; + top: 0; + width: 2rem; + @include linear-gradient(90deg, rgba($gray-lightest, 0), $inverse); } } } - -.cal-details__date { - display: block; - padding-bottom: u(.5rem); -} - -.cal-details__title { - display: block; - font-weight: bold; - line-height: 1.4; - padding-bottom: u(.5rem); +.fc-daygrid-block-event { + .fc-event-time { + font-weight: inherit; + padding: 0; + } + .fc-event-title { + padding: 0; + } } -.cal-details__summary { - display: block; - font-weight: bold; - line-height: 1.2; - padding-bottom: u(1rem); +// Overriding the default flex and absolute garbage +.fc-view-outer-aspect-ratio { + padding-bottom: 0 !important; } - -.cal-details__description { - display: block; - line-height: 1.2; - padding-bottom: u(.5rem); -} - -.cal-details__location { +.fc-view-outer-aspect-ratio > .fc-view.fc-dayGridMonth-view { + position: relative; display: block; - line-height: 1.2; - padding-bottom: u(.5rem); } -.cal-details__add { - margin-top: u(1rem); -} - -.fc-popup { - background: $inverse; +.fc-popover { + background: $neutral; border: 2px solid $neutral; position: absolute; - max-width: u(20rem); - - .button { - position: absolute; - top: 0; - right: 0; - width: u(3rem); - height: u(3rem); - } -} - -.fc-popup__header { - background: $neutral; - color: $primary; - font-weight: bold; - padding: 4px; - position: relative; -} - -// Week view -.fc-time-grid-container { - overflow: scroll; -} - -.fc-time-grid { - position: relative; + width: u(22rem); - .fc-content-skeleton { - border-bottom: 2px solid $neutral; - position: absolute; - top: 0; - left: 0; - right: 0; - } -} + // .fc-close { + // // cursor: pointer; + // float: right; + // @include u-icon($x, $primary, 2rem, 2rem, 100%); + // } -.fc-slats { - td { - height: u(1.5rem); - } + // .fc-event-container { + // padding: u(.5rem); + // } - tr { - border: 2px solid $neutral; - border-bottom: none; + // .fc-content { + // margin-bottom: u(.5rem); + // } + .fc-event { + margin-bottom: .5rem; } - - .fc-minor { - border-bottom: none; - border-top: none; + .fc-event-title { + font-weight: bold; } } - -.fc-agenda-view { - .fc-bg { - display: none; // Temporary hack to remove mis-aligned background in agenda view - } - - .fc-axis { - padding: u(.5rem 1rem 0 1rem); - text-align: right; - vertical-align: middle; - white-space: nowrap; - } - - .fc-divider { - display: none; - } - - .fc-week { - border-top: 2px solid $neutral; - } +.fc-popover-header { + padding: u(3px .5rem); } - -.cal-view__toggles { - float: right; - padding-left: u(1rem); +.fc-popover-title { + font-weight: bold; + height: auto; + line-height: u(2rem); + width: auto; } - -// List view -.cal-list__toggles { - float: left; - font-family: $sans-serif; - padding-top: u(.5rem); +.fc-popover-body { + background-color: transparent; } .cal-list { letter-spacing: -.3px; + display: flex; + flex-direction: column; } -*/ .cal-list__title { background-color: $gray-light; @@ -505,31 +365,81 @@ margin: u(2rem 0 0 0); padding: u(1rem); } - +.cal-list__toggles { + font-family: $sans-serif; +} .cal-list__event { border-bottom: 1px solid $neutral; + display: block; + float: left; padding: u(2rem 0); - @include clearfix(); + width: 100%; &:last-child { border-bottom: none; padding-bottom: 0; } -} + @include media($lg) { + padding: 2rem; + } +} +.cal-list__event-title { + margin: 0; +} +.cal-list__date { + float: left; + font-weight: bold; + width: calc(100% - 15rem); +} +.cal-list__time { + display: inline-block; + width: 15rem; +} .cal-list__details { margin-bottom: u(1rem); @include span-columns(8); -} + + @include media($lg) { + font-size: 1.6rem; + @include span-columns(10); + } + // large sizes for and inside this element + @include media($lg) { + + margin-bottom: 0; + .cal-list__date { + display: none; + } + .cal-list__time { + float: left; + width: 7rem; + } + .cal-list__info { + float: left; + margin: 0; + padding: 0 1rem 0; + width: calc(70% - 7rem); + } + .cal-list__category { + float: right; + width: calc(30%); + } + } +} .cal-list__add { @include span-columns(4); + @include media($lg) { + float: right; + @include span-columns(2); + } + .button { float: right; } } - .cal-list__summary { display: block; font-weight: bold; @@ -537,22 +447,16 @@ margin: 0 0 .5rem 0; } -.cal-list__date { - font-weight: bold; - @include span-columns(4 of 8); -} - -.cal-list__time { - @include span-columns(4 of 8); - @include omega(); -} - .cal-list__info { padding-top: u(2rem); @include span-columns(8 of 8); p { font-size: u(1.4rem); + + @include media($lg) { + font-size: 1.6rem; + } } } @@ -561,27 +465,30 @@ } .cal-list__category-title { + flex-direction: row; + align-items: center; border-color: $primary; border-radius: 3px; border: 1px solid; - display: table; + display: inline-flex; font-size: u(1.4rem); padding: u(.5rem); - @include clearfix(); - + .tooltip__trigger { + margin: 0; + } .tooltip__trigger-text { border-right: 1px solid $primary; display: table-cell; - line-height: 1.2; + margin-right: .5rem; padding-right: .5rem; vertical-align: middle; } .tooltip__container { - display: table-cell; - padding-left: .5rem; - vertical-align: middle; + height: unset; + margin: 0; + width: unset; } .list--bulleted li:last-child { @@ -589,150 +496,97 @@ } } -.cal-list__event-title { - margin: 0; -} - .cal-list__location { display: block; -} - - -/* -// Popover -.fc-popover { - background-color: $inverse; - border: 2px solid $neutral; - position: absolute; - width: u(22rem); - - .fc-header { - @include clearfix(); - background: $neutral; - padding: u(3px .5rem); - .fc-title { - font-weight: bold; - height: auto; - line-height: u(2rem); - width: auto; - } + @include media($lg) { + font-size: 1.6rem; } +} - .fc-close { - @include u-icon($x, $primary, 2rem, 2rem, 100%); - cursor: pointer; - float: right; - } - .fc-event-container { - padding: u(.5rem); +.cal-details { + font-size: u(1.4rem); + left: auto !important; + top: auto !important; + margin-top: u(1.5rem); + margin-left: u(-8rem); + padding: 0 !important; + text-align: left; + width: u(26rem); + white-space: initial; + + &::before { + background-image: none; + left: 20%; + top: u(-2rem) !important; + @include triangle(2rem, $primary, up); } - .fc-content { - margin-bottom: u(.5rem); + .button--close--primary { + position: absolute; + top: 0; + right: 0; } } -.fc-clear { - clear: both; +.cal-details__date { + display: block; + padding-bottom: u(.5rem); } -// BREAKPOINT: MEDIUM -// - Navigation buttons on the left -// - Title in the center -// - View buttons on the right - -@include media($med) { - .calendar__head { - padding: u(1rem 2rem); - } +.cal-details__title { + display: block; + font-weight: bold; + line-height: 1.4; + padding-bottom: u(.5rem); +} - .fc-clear { - display: none; - } +.cal-details__summary { + display: block; + font-weight: bold; + line-height: 1.2; + padding-bottom: u(1rem); +} - .fc-center { - float: left; - text-align: center; - } +.cal-details__description { + display: block; + line-height: 1.2; + padding-bottom: u(.5rem); +} - .fc-toolbar-end { - display: block; - float: right; +.cal-details__location { + display: block; + line-height: 1.2; + padding-bottom: u(.5rem); +} - .fc-button-group { - float: right; - } +.cal-details__add { + margin-top: u(1rem); +} +.fc-day:nth-child(1) { + .cal-details { + margin-left: unset; - .button { - background-position: 50% 50%; - padding-left: u(2rem); - text-indent: -9999px; - width: u(4rem); + &::before { + right: auto; + left: u(2rem); } } - - .cal-list__toggles { - float: right; - } } -// BREAKPOINT: LARGE -// - List events line up horizontally - -@include media($lg) { - .cal-list { - font-size: u(1.6rem); - - p { - font-size: u(1.6rem); - } - } +.fc-day:nth-child(7) { + .cal-details { + right: 0; - .cal-list--time { - .cal-list__date { - display: none; + &::before { + left: auto; + right: u(1rem); } - } - .cal-list--category { - .cal-list__category { - visibility: hidden; + &::after { + left: auto; + right: u(1.2rem); } } - - .cal-list__event { - @include clearfix(); - padding: u(2rem); - } - - .cal-list__details { - @include span-columns(10); - margin-bottom: 0; - } - - .cal-list__add { - @include span-columns(2); - font-size: u(1.4rem); - } - - .cal-list__date { - @include span-columns(2 of 10); - font-weight: bold; - } - - .cal-list__time { - @include span-columns(1 of 10); - } - - .cal-list__info { - @include span-columns(6 of 10); - padding-top: 0; - } - - .cal-list__category { - @include span-columns(3 of 10); - } } -*/ diff --git a/fec/fec/static/scss/components/_form-styles.scss b/fec/fec/static/scss/components/_form-styles.scss index 28dbf7bd58..f6bdf52d62 100644 --- a/fec/fec/static/scss/components/_form-styles.scss +++ b/fec/fec/static/scss/components/_form-styles.scss @@ -29,7 +29,7 @@ // .select--alt { - background-color: none; + background-color: transparent; border: 2px solid $gray; color: $base; diff --git a/fec/fec/tests/js/calendar.js b/fec/fec/tests/js/calendar.js index 1f41622816..60040c3115 100644 --- a/fec/fec/tests/js/calendar.js +++ b/fec/fec/tests/js/calendar.js @@ -29,18 +29,14 @@ describe('calendar', function() { }); beforeEach(function() { - this.$fixture.empty().append( - '
' + - '
' + - '
' - ); + this.$fixture.empty().append('
'); this.calendar = new Calendar({ selector: '#calendar', selector_download: '#download', selector_subscribe: '#subscribe', - url: 'http://test.calendar', - exportUrl: 'http://test.calendar/export', - subscribeUrl: 'http://test.calendar/export', + url_base: 'http://test.calendar', + url_export: 'http://test.calendar/export', + url_subscribe: 'http://test.calendar/export', filterPanel: { $form: { on: function() {} }, // eslint-disable-line no-empty-function filterSet: { @@ -58,8 +54,8 @@ describe('calendar', function() { describe('constructor()', function() { it('memorizes options', function() { - expect(this.calendar.opts.url).to.equal('http://test.calendar'); - expect(this.calendar.opts.exportUrl).to.equal('http://test.calendar/export'); + expect(this.calendar.opts.url_base).to.equal('http://test.calendar'); + expect(this.calendar.opts.url_export).to.equal('http://test.calendar/export'); }); it('finds dom nodes', function() { @@ -209,7 +205,7 @@ describe('calendar', function() { this.calendar.handleEventClick({}, { target: target }); expect(calendarTooltip.CalendarTooltip).to.have.been.called; }); - + it('does not make a tooltip if there is one', function() { var target = '
'; this.calendar.handleEventClick({}, { target: target }); @@ -233,10 +229,7 @@ describe('calendar', function() { describe('calendar tooltip', function() { beforeEach(function() { - var dom = - '
' + - '' + - '
'; + var dom = '
'; $(document.body).append($(dom)); var $container = $('.cal-event'); var content = tooltipContent({}); @@ -271,9 +264,9 @@ describe('calendar tooltip', function() { }); describe('helpers', function() { - describe('getGoogleUrl()', function() { + describe('getGoogleUrl_moment()', function() { it('builds the correct Google url', function() { - var googleUrl = calendarHelpers.getGoogleUrl({ + var googleUrl = calendarHelpers.getGoogleUrl_moment({ title: 'the big one', summary: 'vote today', end: moment('2016-11-01'), @@ -290,36 +283,27 @@ describe('helpers', function() { }); it('builds the correct subscribe url', function() { - var subscribeUrl = calendarHelpers.getUrl('calendar', { category: 'election' }, 'sub'); - expect(subscribeUrl).to.equal('/v1/calendar/?api_key=67890&per_page=500&category=election'); - + var url_subscribe = calendarHelpers.getUrl('calendar', { category: 'election' }, 'sub'); + expect(url_subscribe).to.equal('/v1/calendar/?api_key=67890&per_page=500&category=election'); + }); }); describe('calendar.updateLinks()', function() { it('builds the correct, encoded googleSubscribe url', function() { - var subscribeUrl = calendarHelpers.getUrl('calendar-dates/export', { category: 'election' }, 'sub').toString(); - var googleSubscribe = - 'https://calendar.google.com/calendar/render?cid=' + - encodeURIComponent( - subscribeUrl - .toString() - ); - expect(googleSubscribe).to.equal('https://calendar.google.com/calendar/render?cid=%2Fv1%2Fcalendar-dates%2Fexport%2F%3Fapi_key%3D67890%26per_page%3D500%26category%3Delection') - }); - }); + var url_subscribe = calendarHelpers.getUrl('calendar-dates/export', { category: 'election' }, 'sub').toString(); + var googleSubscribe = + 'https://calendar.google.com/calendar/render?cid=' + + encodeURIComponent(url_subscribe.toString()); + expect(googleSubscribe).to.equal('https://calendar.google.com/calendar/render?cid=%2Fv1%2Fcalendar-dates%2Fexport%2F%3Fapi_key%3D67890%26per_page%3D500%26category%3Delection'); + }); + }); describe('className()', function() { it('adds a multi-day class for multi-day events', function() { - var multiEvent = { - start_date: moment('2012-11-02'), - end_date: moment('2012-11-03') - }; - var singleEvent = { - start_date: moment('2012-11-02'), - end_date: moment('2012-11-02') - }; + var multiEvent = { start_date: moment('2012-11-02'), end_date: moment('2012-11-03') }; + var singleEvent = { start_date: moment('2012-11-02'), end_date: moment('2012-11-02') }; var multiDayClass = calendarHelpers.className(multiEvent); var singleDayClass = calendarHelpers.className(singleEvent); expect(multiDayClass).to.equal('fc-multi-day'); @@ -327,3 +311,9 @@ describe('helpers', function() { }); }); }); + +describe('calendar date functions', function() { + it('should work properly', function() { + expect(true).to.be(false); + }); +});