@asd20/ui 3.2.986 → 3.2.988

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui",
3
- "version": "3.2.964",
3
+ "version": "3.2.987",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -1285,7 +1285,6 @@
1285
1285
  "version": "7.27.0",
1286
1286
  "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
1287
1287
  "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
1288
- "dev": true,
1289
1288
  "requires": {
1290
1289
  "regenerator-runtime": "^0.14.0"
1291
1290
  }
@@ -1423,6 +1422,12 @@
1423
1422
  "restore-cursor": "^1.0.1"
1424
1423
  }
1425
1424
  },
1425
+ "date-fns": {
1426
+ "version": "1.30.1",
1427
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
1428
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
1429
+ "dev": true
1430
+ },
1426
1431
  "figures": {
1427
1432
  "version": "1.7.0",
1428
1433
  "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@@ -11548,9 +11553,12 @@
11548
11553
  }
11549
11554
  },
11550
11555
  "date-fns": {
11551
- "version": "1.30.1",
11552
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
11553
- "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
11556
+ "version": "2.30.0",
11557
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
11558
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
11559
+ "requires": {
11560
+ "@babel/runtime": "^7.21.0"
11561
+ }
11554
11562
  },
11555
11563
  "dateformat": {
11556
11564
  "version": "3.0.3",
@@ -21394,6 +21402,12 @@
21394
21402
  "figures": "^2.0.0"
21395
21403
  },
21396
21404
  "dependencies": {
21405
+ "date-fns": {
21406
+ "version": "1.30.1",
21407
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
21408
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
21409
+ "dev": true
21410
+ },
21397
21411
  "figures": {
21398
21412
  "version": "2.0.0",
21399
21413
  "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -21833,6 +21847,12 @@
21833
21847
  "restore-cursor": "^1.0.1"
21834
21848
  }
21835
21849
  },
21850
+ "date-fns": {
21851
+ "version": "1.30.1",
21852
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
21853
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
21854
+ "dev": true
21855
+ },
21836
21856
  "figures": {
21837
21857
  "version": "1.7.0",
21838
21858
  "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@@ -27520,8 +27540,7 @@
27520
27540
  "regenerator-runtime": {
27521
27541
  "version": "0.14.1",
27522
27542
  "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
27523
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
27524
- "dev": true
27543
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
27525
27544
  },
27526
27545
  "regenerator-transform": {
27527
27546
  "version": "0.15.2",
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "*.scss",
6
6
  "*.vue"
7
7
  ],
8
- "version": "3.2.986",
8
+ "version": "3.2.988",
9
9
  "private": false,
10
10
  "license": "MIT",
11
11
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "axios": "^0.19.2",
31
31
  "basicscroll": "^3.0.2",
32
32
  "countup.js": "^2.0.0",
33
- "date-fns": "^1.30.1",
33
+ "date-fns": "^2.30.0",
34
34
  "focus-trap": "^6.9.4",
35
35
  "focus-trap-vue": "^1.1.1",
36
36
  "js-cookie": "^2.2.1",
@@ -14,7 +14,7 @@ export default {
14
14
  formattedDate() {
15
15
  // let apStyle = ['Jan.', 'Feb.', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.']
16
16
  // let shortMonths = /January|February|August|September|October|November|December /
17
- let formatted = format(new Date(this.dateTime), ' MMMM D, YYYY')
17
+ let formatted = format(new Date(this.dateTime), ' MMMM d, yyyy')
18
18
  // for (let month of apStyle) {
19
19
  // if (formatted.includes(month)) {
20
20
  // formatted = month
@@ -343,8 +343,9 @@ export default {
343
343
  font-family: var(--website-typography__font-family-headlines);
344
344
  display: flex;
345
345
  align-items: flex-start;
346
- margin-top: space(0.5);
347
- margin-bottom: space(0.5);
346
+ margin-top: space(0.25);
347
+ margin-bottom: space(0.25);
348
+ align-items: center;
348
349
  .asd20-icon {
349
350
  --line-color: var(--website-icon__line-color);
350
351
  margin-left: -0.125em;
@@ -356,6 +357,7 @@ export default {
356
357
  }
357
358
  &__time + &__location {
358
359
  margin-top: space(0);
360
+ margin-bottom: space(0.25);
359
361
  }
360
362
  // &__date {
361
363
  // margin-right: space(0.5);
@@ -17,7 +17,6 @@
17
17
  </template>
18
18
 
19
19
  <script>
20
- import parse from 'date-fns/parse'
21
20
  import format from 'date-fns/format'
22
21
 
23
22
  export default {
@@ -28,27 +27,27 @@ export default {
28
27
  computed: {
29
28
  startDay() {
30
29
  if (!this.event) return ''
31
- return format(parse(this.event.start.dateTime), 'DD')
30
+ return format(new Date(this.event.start.dateTime), 'dd')
32
31
  },
33
32
  startMonthName() {
34
33
  if (!this.event) return ''
35
- return format(parse(this.event.start.dateTime), 'MMM')
34
+ return format(new Date(this.event.start.dateTime), 'MMM')
36
35
  },
37
36
  startDate() {
38
37
  if (!this.event) return ''
39
- return format(parse(this.event.start.dateTime), 'dddd MMM D, YYYY')
38
+ return format(new Date(this.event.start.dateTime), 'EEEE MMM d, yyyy')
40
39
  },
41
40
  endDate() {
42
41
  if (!this.event) return ''
43
- return format(parse(this.event.end.dateTime), 'dddd MMM D, YYYY')
42
+ return format(new Date(this.event.end.dateTime), 'EEEE MMM d, yyyy')
44
43
  },
45
44
  startTime() {
46
45
  if (!this.event) return ''
47
- return format(parse(this.event.start.dateTime), 'h:mm A')
46
+ return format(new Date(this.event.start.dateTime), 'h:mm aa')
48
47
  },
49
48
  endTime() {
50
49
  if (!this.event) return ''
51
- return format(parse(this.event.end.dateTime), 'h:mm A')
50
+ return format(new Date(this.event.end.dateTime), 'h:mm aa')
52
51
  },
53
52
  },
54
53
  }
@@ -254,25 +254,8 @@ export default {
254
254
  .asd20-notification-group--floating {
255
255
  position: absolute;
256
256
  top: space(2.0375);
257
- // .bell {
258
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
259
- // svg {
260
- // fill: var(--color__accent) !important;
261
- // }
262
- // span {
263
- // background: var(--color__accent) !important;
264
- // top: inherit !important;
265
- // right: inherit !important;
266
- // width: 1.2rem !important;
267
- // height: 1.2rem !important;
268
- // font-size: 0.75rem !important;
269
- // }
270
- // }
271
257
  }
272
258
 
273
- // .notification-group--inline {
274
- // margin-bottom: space(3);
275
- // }
276
259
  .asd20-feeds-section--top {
277
260
  // margin-top: space(1);
278
261
  .asd20-swiper-feed:first-child {
@@ -290,9 +273,6 @@ export default {
290
273
  }
291
274
  }
292
275
  }
293
- // .asd20-page-content h2 {
294
- // font-size: 20.8px !important;
295
- // }
296
276
  }
297
277
 
298
278
  @media (min-width: 1024px) {
@@ -303,13 +283,6 @@ export default {
303
283
  .asd20-notification-group--floating {
304
284
  position: absolute;
305
285
  top: space(1);
306
- // .bell {
307
- // span {
308
- // background: var(--color__accent);
309
- // top: -0.6em !important;
310
- // right: -0.6em !important;
311
- // }
312
- // }
313
286
  }
314
287
  .notification-group--inline {
315
288
  flex-basis: 100%;
@@ -320,10 +293,6 @@ export default {
320
293
  }
321
294
  }
322
295
 
323
- // .asd20-feeds-section--top {
324
- // margin-top: space(0);
325
- // }
326
-
327
296
  .asd20-page-content {
328
297
  // margin-top: space(1);
329
298
  display: flex;
@@ -343,6 +312,7 @@ export default {
343
312
  max-width: 50vw;
344
313
  flex: 1 1 0;
345
314
  min-width: 0;
315
+ flex-direction: column;
346
316
  }
347
317
  }
348
318
  }
@@ -246,24 +246,7 @@ export default {
246
246
  .asd20-notification-group--floating {
247
247
  position: absolute;
248
248
  top: space(2);
249
- // .bell {
250
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
251
- // svg {
252
- // fill: var(--color__accent) !important;
253
- // }
254
- // span {
255
- // background: var(--color__accent) !important;
256
- // top: inherit !important;
257
- // right: inherit !important;
258
- // width: 1.2rem !important;
259
- // height: 1.2rem !important;
260
- // font-size: 0.75rem !important;
261
- // }
262
- // }
263
249
  }
264
- // .asd20-page-content h2 {
265
- // font-size: 20.8px !important;
266
- // }
267
250
  }
268
251
 
269
252
  @media (min-width: 768px) {
@@ -271,20 +254,6 @@ export default {
271
254
  .asd20-notification-group--floating {
272
255
  position: absolute;
273
256
  top: space(0.5);
274
- // .bell {
275
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
276
- // svg {
277
- // fill: var(--color__accent) !important;
278
- // }
279
- // span {
280
- // background: var(--color__accent) !important;
281
- // top: inherit !important;
282
- // right: inherit !important;
283
- // width: 1.2rem !important;
284
- // height: 1.2rem !important;
285
- // font-size: 0.75rem !important;
286
- // }
287
- // }
288
257
  }
289
258
  }
290
259
  }
@@ -320,7 +289,7 @@ export default {
320
289
  padding: space(2) !important;
321
290
  flex: 1 1 0;
322
291
  min-width: 0;
323
- // max-width: 50vw;
292
+ flex-direction: column;
324
293
  }
325
294
  }
326
295
  }
@@ -239,24 +239,7 @@ export default {
239
239
  .asd20-notification-group--floating {
240
240
  position: absolute;
241
241
  top: space(2);
242
- // .bell {
243
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
244
- // svg {
245
- // fill: var(--color__accent) !important;
246
- // }
247
- // span {
248
- // background: var(--color__accent) !important;
249
- // top: inherit !important;
250
- // right: inherit !important;
251
- // width: 1.2rem !important;
252
- // height: 1.2rem !important;
253
- // font-size: 0.75rem !important;
254
- // }
255
- // }
256
242
  }
257
- // .asd20-page-content h2 {
258
- // font-size: 20.8px !important;
259
- // }
260
243
  }
261
244
 
262
245
  @media (min-width: 768px) {
@@ -264,20 +247,6 @@ export default {
264
247
  .asd20-notification-group--floating {
265
248
  position: absolute;
266
249
  top: space(0.5);
267
- // .bell {
268
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
269
- // svg {
270
- // fill: var(--color__accent) !important;
271
- // }
272
- // span {
273
- // background: var(--color__accent) !important;
274
- // top: inherit !important;
275
- // right: inherit !important;
276
- // width: 1.2rem !important;
277
- // height: 1.2rem !important;
278
- // font-size: 0.75rem !important;
279
- // }
280
- // }
281
250
  }
282
251
  }
283
252
  }
@@ -316,6 +285,7 @@ export default {
316
285
  max-width: 50vw;
317
286
  flex: 1 1 0;
318
287
  min-width: 0;
288
+ flex-direction: column;
319
289
  }
320
290
  }
321
291
 
@@ -242,24 +242,7 @@ export default {
242
242
  .asd20-notification-group--floating {
243
243
  position: absolute;
244
244
  top: space(2.0375);
245
- // .bell {
246
- // box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125) !important;
247
- // svg {
248
- // fill: var(--color__accent) !important;
249
- // }
250
- // span {
251
- // background: var(--color__accent) !important;
252
- // top: inherit !important;
253
- // right: inherit !important;
254
- // width: 1.2rem !important;
255
- // height: 1.2rem !important;
256
- // font-size: 0.75rem !important;
257
- // }
258
- // }
259
245
  }
260
- // .asd20-page-content h2 {
261
- // font-size: 20.8px !important;
262
- // }
263
246
  }
264
247
 
265
248
  @media (min-width: 1024px) {
@@ -270,13 +253,6 @@ export default {
270
253
  .asd20-notification-group--floating {
271
254
  position: absolute;
272
255
  top: space(1);
273
- // .bell {
274
- // span {
275
- // background: var(--color__accent);
276
- // top: -0.6em !important;
277
- // right: -0.6em !important;
278
- // }
279
- // }
280
256
  }
281
257
  .notification-group--inline {
282
258
  margin: space(2) space(3) space(1) space(3);
@@ -304,7 +280,7 @@ export default {
304
280
  flex: 1;
305
281
  flex: 1 1 0;
306
282
  min-width: 0;
307
- // max-width: 50vw;
283
+ flex-direction: column;
308
284
  }
309
285
  }
310
286
  }
@@ -29,8 +29,8 @@
29
29
  "summary": "Winter Break - No School",
30
30
  "description": "No School",
31
31
  "location": "AAHS",
32
- "start": "2025-12-19T07:00:00Z",
33
- "end": "2026-01-05T07:00:00Z",
32
+ "start": "2025-12-20T07:00:00Z",
33
+ "end": "2026-12-25T07:00:00Z",
34
34
  "dtstamp": "2024-12-19T07:00:00Z",
35
35
  "class": "",
36
36
  "priority": "",
@@ -56,8 +56,8 @@
56
56
  "summary": "Register for Summer School",
57
57
  "description": "Ok. We're actually registeriing for Summer School really, really, really early.",
58
58
  "location": "EAC",
59
- "start": "2025-12-24T07:01:00Z",
60
- "end": "2025-12-24T07:01:00Z",
59
+ "start": "2025-12-24T16:01:00Z",
60
+ "end": "2025-12-24T16:01:00Z",
61
61
  "dtstamp": "2024-12-23T17:00:00Z",
62
62
  "class": "",
63
63
  "priority": "",
@@ -1,13 +1,13 @@
1
- import parse from 'date-fns/parse'
2
1
  import format from 'date-fns/format'
3
- import addDays from 'date-fns/add_days'
4
- import isSameDay from 'date-fns/is_same_day'
2
+ import addDays from 'date-fns/addDays'
3
+ import isSameDay from 'date-fns/isSameDay'
5
4
 
6
5
  export default function expandEvents(events, includePastEvents = false) {
7
6
  return events
8
7
  .reduce((acc, event) => {
9
- const start = parse(event.start)
10
- const end = event.end ? parse(event.end) : start
8
+ // Use new Date() for ISO date strings
9
+ const start = new Date(event.start)
10
+ const end = event.end ? new Date(event.end) : start
11
11
 
12
12
  const today = new Date()
13
13
  today.setHours(0, 0, 0, 0)
@@ -16,7 +16,6 @@ export default function expandEvents(events, includePastEvents = false) {
16
16
  const diffHours = diffTime / (1000 * 60 * 60)
17
17
  let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
18
18
 
19
- // Safety net for zero-day events
20
19
  if (diffDays === 0) diffDays = 1
21
20
 
22
21
  const isAllDay = event.allDay || diffHours >= 23
@@ -28,22 +27,12 @@ export default function expandEvents(events, includePastEvents = false) {
28
27
  const normalizedEnd = new Date(end)
29
28
  normalizedEnd.setHours(0, 0, 0, 0)
30
29
 
31
- // let isMultiDay
32
-
33
- // if (isAllDay && diffDays === 1) {
34
- // // All-day event lasting exactly one day → NOT multi-day
35
- // isMultiDay = false
36
- // } else {
37
- // isMultiDay = !isSameDay(normalizedStart, normalizedEnd)
38
- // }
39
-
40
30
  const isMultiDay =
41
31
  diffDays > 1 || !isSameDay(normalizedStart, normalizedEnd)
42
32
 
43
33
  const expandedEvents = []
44
34
 
45
35
  if (isMultiDay) {
46
- // Multi-day event expansion
47
36
  for (let d = 0; d < diffDays; d++) {
48
37
  const currentDay = addDays(normalizedStart, d)
49
38
 
@@ -55,16 +44,15 @@ export default function expandEvents(events, includePastEvents = false) {
55
44
  originalStart: start,
56
45
  allDay: true,
57
46
  multiDay: true,
58
- weekday: format(currentDay, 'dddd'),
59
- day: format(currentDay, 'DD'),
60
- number: format(currentDay, 'D'),
47
+ weekday: format(currentDay, 'EEEE'),
48
+ day: format(currentDay, 'dd'),
49
+ number: format(currentDay, 'd'),
61
50
  month: format(currentDay, 'MMMM'),
62
- year: format(currentDay, 'YYYY')
51
+ year: format(currentDay, 'yyyy'),
63
52
  })
64
53
  }
65
54
  }
66
55
  } else {
67
- // Single-day event
68
56
  if (includePastEvents || normalizedStart >= today) {
69
57
  expandedEvents.push({
70
58
  ...event,
@@ -73,11 +61,11 @@ export default function expandEvents(events, includePastEvents = false) {
73
61
  originalStart: null,
74
62
  allDay: isAllDay,
75
63
  multiDay: false,
76
- weekday: format(normalizedStart, 'dddd'),
77
- day: format(normalizedStart, 'DD'),
78
- number: format(normalizedStart, 'D'),
64
+ weekday: format(normalizedStart, 'EEEE'),
65
+ day: format(normalizedStart, 'dd'),
66
+ number: format(normalizedStart, 'd'),
79
67
  month: format(normalizedStart, 'MMMM'),
80
- year: format(normalizedStart, 'YYYY')
68
+ year: format(normalizedStart, 'yyyy'),
81
69
  })
82
70
  }
83
71
  }
@@ -110,7 +98,6 @@ export default function expandEvents(events, includePastEvents = false) {
110
98
  if (isAHighPriority && !isBHighPriority) return -1
111
99
  if (!isAHighPriority && isBHighPriority) return 1
112
100
 
113
- // Fall back to alphabetical order if both have same priority
114
101
  return aSummary.localeCompare(bSummary)
115
102
  })
116
103
  }
@@ -1,47 +1,39 @@
1
1
  import format from 'date-fns/format'
2
- import parse from 'date-fns/parse'
2
+
3
+ function safeFormatDate(dateObj, fmt) {
4
+ if (!dateObj || isNaN(dateObj)) return ''
5
+ try {
6
+ return format(dateObj, fmt)
7
+ } catch {
8
+ return ''
9
+ }
10
+ }
3
11
 
4
12
  export default function formattedTime(event) {
5
- if (!event) return
13
+ if (!event) return ''
6
14
 
7
15
  if (event.allDay) return 'All Day'
8
16
 
9
17
  if (event.multiDay) {
10
- return `<b>MultiDay Event:</b><br/>Starts: ${format(
11
- parse(event.originalStart, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
12
- '<b>h:mm aa</b>, ddd, MMM DD, YYYY'
13
- )
14
- .replace(/#/g, '&nbsp;')
15
- .replace(/:00/g, '')}<br>Ends: ${format(
16
- parse(event.end, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
17
- '<b>h:mm aa</b>, ddd, MM DD, YYYY'
18
- )
19
- .replace(/#/g, '&nbsp;')
20
- .replace(/:00/g, '')}`
18
+ const startStr = safeFormatDate(event.originalStart, 'h:mm aa, EEE, MMM dd, yyyy')
19
+ const endStr = safeFormatDate(event.end, 'h:mm aa, EEE, MM dd, yyyy')
20
+ if (startStr && endStr) {
21
+ return `<b>MultiDay Event:</b><br/>Starts: ${startStr}<br>Ends: ${endStr}`
22
+ } else if (startStr) {
23
+ return `<b>MultiDay Event:</b><br/>Starts: ${startStr}`
24
+ } else if (endStr) {
25
+ return `<b>MultiDay Event:</b><br/>Ends: ${endStr}`
26
+ }
27
+ return 'MultiDay Event'
21
28
  }
22
29
 
23
- const startTime = parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
24
- const endTime = parse(event.end, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
25
- // const startHour = startTime.getHours()
26
-
27
- // if (
28
- // startTime.getTime() === endTime.getTime() &&
29
- // (startHour >= 23 || startHour < 3)
30
- // ) {
31
- // return 'TBD'
32
- // }
33
-
34
- let time = `${format(startTime, '<b>h:mm aa</b>').replace(/:00/g, '')}`
30
+ const start = safeFormatDate(event.start, 'h:mm aa')
31
+ const end = safeFormatDate(event.end, 'h:mm aa')
35
32
 
36
- if (startTime.getTime() === endTime.getTime()) {
37
- time += ''
38
- } else if (event.endTimeUndetermined) {
39
- time += `${format(endTime).replace(/:00/g, '')}`
40
- } else {
41
- time += ` - ${format(endTime, '<b>h:mm aa</b>')
42
- .replace(/#/g, '&nbsp;')
43
- .replace(/:00/g, '')}`
33
+ if (start && end && event.start.getTime() !== event.end.getTime()) {
34
+ return `${start} - ${end}`
44
35
  }
36
+ if (start) return start
45
37
 
46
- return time
38
+ return ''
47
39
  }
@@ -1,11 +1,10 @@
1
1
  import format from 'date-fns/format'
2
- import parse from 'date-fns/parse'
2
+ import parseISO from 'date-fns/parseISO'
3
3
  import formattedTime from './formatEventTime'
4
4
 
5
5
  // Function to sanitize the summary
6
6
  const sanitizeSummary = summary => {
7
7
  if (!summary) return summary
8
-
9
8
  const pattern = /vs\.[^>]*>Multiple Schools/
10
9
  return summary.replace(pattern, 'vs. Multiple Schools')
11
10
  }
@@ -16,15 +15,12 @@ const sanitizeDescription = description => {
16
15
 
17
16
  // Check if the description contains ">Multiple Schools"
18
17
  if (description.includes('>Multiple Schools')) {
19
- // Remove text between the ">" backwards to the first "." or ":" and replace '>Multiple Schools' with '.'
20
18
  description = description.replace(
21
19
  /([.:][^>]*?)>([^>]*Multiple Schools)/,
22
20
  '$1.'
23
21
  )
24
-
25
22
  // Separate capital letters preceded by lowercase letters with ', '
26
23
  description = description.replace(/([a-z])([A-Z])/g, '$1, $2')
27
-
28
24
  // Remove unmatched quotes between "." or ":" and ">"
29
25
  const quotePattern = /[.:][^>]*['"][^'"]*$|[^'"]*['"][^>]*['"]>Multiple Schools/g
30
26
  description = description.replace(quotePattern, match => {
@@ -32,7 +28,6 @@ const sanitizeDescription = description => {
32
28
  return quotes.length % 2 !== 0 ? match.replace(/['"]/g, '') : match
33
29
  })
34
30
  }
35
-
36
31
  return description
37
32
  }
38
33
 
@@ -40,11 +35,31 @@ export default function mapEventToCard(event) {
40
35
  const sanitizedSummary = sanitizeSummary(event.summary)
41
36
  const sanitizedDescription = sanitizeDescription(event.description)
42
37
 
38
+ let dateObj = null
39
+ if (event.start) {
40
+ try {
41
+ dateObj = parseISO(event.start)
42
+ if (isNaN(dateObj)) {
43
+ // fallback
44
+ const { parse } = require('date-fns')
45
+ dateObj = parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
46
+ }
47
+ } catch (e) {
48
+ dateObj = null
49
+ }
50
+ }
51
+
52
+ // Always calculate these fields, not just rely on input
53
+ // const weekday = dateObj && !isNaN(dateObj) ? format(dateObj, 'EEEE') : ''
54
+ // const day = dateObj && !isNaN(dateObj) ? format(dateObj, 'dd') : ''
55
+ // const month = dateObj && !isNaN(dateObj) ? format(dateObj, 'MMMM') : ''
56
+ // const year = dateObj && !isNaN(dateObj) ? format(dateObj, 'yyyy') : ''
57
+
43
58
  return {
44
59
  title: sanitizedSummary,
45
60
  description: sanitizedDescription,
46
61
  categories: event.calendarCategories.concat([event.calendarName]),
47
- date: format(parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())),
62
+ date: dateObj && !isNaN(dateObj) ? format(dateObj, 'MMMM d, yyyy') : '',
48
63
  time: formattedTime(event),
49
64
  weekday: event.weekday,
50
65
  day: event.day,
@@ -1,18 +1,10 @@
1
- import parse from 'date-fns/parse'
2
1
  import format from 'date-fns/format'
3
- import addDays from 'date-fns/add_days'
4
- import isSameDay from 'date-fns/is_same_day'
5
- import getDaysInMonth from 'date-fns/get_days_in_month'
2
+ import addDays from 'date-fns/addDays'
3
+ import isSameDay from 'date-fns/isSameDay'
4
+ import getDaysInMonth from 'date-fns/getDaysInMonth'
6
5
 
7
6
  /**
8
7
  * Given an array of events, returns an array of days with events grouped by date.
9
- *
10
- * @export
11
- * @param {Array} events - Array of events (should already be expanded if needed)
12
- * @param {number} year - Target year (used when showAll is false)
13
- * @param {number} month - Target month (zero-indexed, used when showAll is false)
14
- * @param {boolean} showAll - If true, returns only days with events (useful for agenda views)
15
- * @returns {Array} Array of day objects { date, events, ... }
16
8
  */
17
9
  export default function mapEventsToDays(
18
10
  events,
@@ -30,8 +22,8 @@ export default function mapEventsToDays(
30
22
  const uniqueDatesMap = new Map()
31
23
 
32
24
  events.forEach(event => {
33
- const eventDate = parse(event.start)
34
- const dateKey = format(eventDate, 'YYYY-MM-DD')
25
+ const eventDate = new Date(event.start)
26
+ const dateKey = format(eventDate, 'yyyy-MM-dd')
35
27
  if (!uniqueDatesMap.has(dateKey)) {
36
28
  uniqueDatesMap.set(dateKey, eventDate)
37
29
  }
@@ -44,8 +36,8 @@ export default function mapEventsToDays(
44
36
  const lastDayOfMonth = new Date(year, month + 1, 0)
45
37
  const daysInMonth = getDaysInMonth(firstDayOfMonth)
46
38
 
47
- const prevMonthDays = firstDayOfMonth.getDay() // How many days to show before the 1st of the month (0=Sunday)
48
- const nextMonthDays = 6 - lastDayOfMonth.getDay() // How many days to show after the last day of the month
39
+ const prevMonthDays = firstDayOfMonth.getDay()
40
+ const nextMonthDays = 6 - lastDayOfMonth.getDay()
49
41
  const totalDays = daysInMonth + prevMonthDays + nextMonthDays
50
42
 
51
43
  for (let i = -prevMonthDays; i < totalDays - prevMonthDays; i++) {
@@ -61,12 +53,6 @@ export default function mapEventsToDays(
61
53
 
62
54
  /**
63
55
  * Builds a day object with events attached.
64
- *
65
- * @param {Date} date - The date this day represents.
66
- * @param {Array} events - The full events list.
67
- * @param {boolean} outsideOfCurrentMonth - Whether this date is outside the target month.
68
- * @param {Date} today - Today's date (normalized to 00:00).
69
- * @returns {Object} The day object.
70
56
  */
71
57
  function createDayObject(date, events, outsideOfCurrentMonth = false, today) {
72
58
  const normalizedDate = new Date(date)
@@ -77,12 +63,12 @@ function createDayObject(date, events, outsideOfCurrentMonth = false, today) {
77
63
  date: normalizedDate,
78
64
  unix: normalizedDate.getTime(),
79
65
  today: isSameDay(normalizedDate, today),
80
- events: events.filter(event => isSameDay(parse(event.start), normalizedDate)),
81
- weekday: format(normalizedDate, 'dddd'),
82
- day: format(normalizedDate, 'DD'),
83
- shortDay: format(normalizedDate, 'D'),
84
- number: format(normalizedDate, 'D'),
66
+ events: events.filter(event => isSameDay(new Date(event.start), normalizedDate)),
67
+ weekday: format(normalizedDate, 'EEEE'),
68
+ day: format(normalizedDate, 'dd'),
69
+ shortDay: format(normalizedDate, 'd'),
70
+ number: format(normalizedDate, 'd'),
85
71
  month: format(normalizedDate, 'MMMM'),
86
- year: format(normalizedDate, 'YYYY'),
72
+ year: format(normalizedDate, 'yyyy'),
87
73
  }
88
74
  }
@@ -1,4 +1,5 @@
1
1
  import format from 'date-fns/format'
2
+ import parseISO from 'date-fns/parseISO'
2
3
 
3
4
  export default function mapMessageToCard(
4
5
  message,
@@ -48,14 +49,43 @@ export default function mapMessageToCard(
48
49
  }
49
50
  }
50
51
 
52
+ // Safely format dates (handle missing/null)
53
+ let date = ''
54
+ let modifiedDate = ''
55
+ let time = ''
56
+
57
+ if (message.publishDateTime) {
58
+ try {
59
+ const parsed = parseISO(message.publishDateTime)
60
+ if (!isNaN(parsed)) {
61
+ date = format(parsed, 'MMM. d, yyyy')
62
+ time = format(parsed, 'h:mm aa')
63
+ }
64
+ } catch (e) {
65
+ date = ''
66
+ time = ''
67
+ }
68
+ }
69
+
70
+ if (message.modifiedDateTime) {
71
+ try {
72
+ const parsed = parseISO(message.modifiedDateTime)
73
+ if (!isNaN(parsed)) {
74
+ modifiedDate = format(parsed, 'MMM. d, yyyy')
75
+ }
76
+ } catch (e) {
77
+ modifiedDate = ''
78
+ }
79
+ }
80
+
51
81
  return Object.assign(
52
82
  {
53
83
  title: message.title,
54
84
  description: message.description || message.summary,
55
85
  categories: message.categories,
56
- date: format(message.publishDateTime, 'MMM. D, YYYY'),
57
- modifiedDate: format(message.modifiedDateTime, 'MMM. D, YYYY'),
58
- time: format(message.publishDateTime, 'h:mm A'),
86
+ date,
87
+ modifiedDate,
88
+ time,
59
89
  pinned: message.isFeatured || message.isDistrictFeatured,
60
90
  image: bannerImageOrDefault ? bannerImageOrDefault.url : '',
61
91
  alt: bannerImageOrDefault ? coverImageAlt : '',
@@ -134,25 +134,66 @@ export default {
134
134
  }
135
135
  },
136
136
 
137
- primaryDetailMessages() {
138
- // If there are more than 2 or more messages on a detail page
139
- // -- Detail Pages
137
+ // primaryDetailMessages() {
138
+ // // If there are more than 2 or more messages on a detail page
139
+ // // -- Detail Pages
140
+
141
+ // let originalMessages = JSON.parse(JSON.stringify(this.messages))
142
+
143
+ // let firstMessage = originalMessages[0]
144
+ // delete firstMessage.title
145
+ // delete firstMessage.heading
146
+ // delete firstMessage.summary
147
+ // delete firstMessage.shortDescription
148
+
149
+ // let messagesArray = originalMessages.slice(1)
150
+ // messagesArray.unshift(firstMessage)
151
+ // messagesArray.slice(
152
+ // 1,
153
+ // Math.min(originalMessages.length, this.primaryMessageLimit + 1)
154
+ // )
155
+ // return messagesArray
156
+ // },
140
157
 
141
- let originalMessages = JSON.parse(JSON.stringify(this.messages))
158
+ primaryDetailMessages() {
159
+ const originalMessages = JSON.parse(JSON.stringify(this.messages))
160
+
161
+ return originalMessages.map((message, index) => {
162
+ // Clone the message
163
+ const newMessage = { ...message }
164
+
165
+ if (index === 0) {
166
+ // Strip fields already used in pageHeader
167
+ delete newMessage.title
168
+ delete newMessage.heading
169
+ delete newMessage.summary
170
+ delete newMessage.shortDescription
171
+ return newMessage
172
+ }
142
173
 
143
- let firstMessage = originalMessages[0]
144
- delete firstMessage.title
145
- delete firstMessage.heading
146
- delete firstMessage.summary
147
- delete firstMessage.shortDescription
148
-
149
- let messagesArray = originalMessages.slice(1)
150
- messagesArray.unshift(firstMessage)
151
- messagesArray.slice(
152
- 1,
153
- Math.min(originalMessages.length, this.primaryMessageLimit + 1)
154
- )
155
- return messagesArray
174
+ // Construct title + summary markup if available
175
+ const titleMarkup = newMessage.title
176
+ ? `<h2>${newMessage.title}</h2>`
177
+ : ''
178
+
179
+ const summaryMarkup = newMessage.shortDescription
180
+ ? `<p>${newMessage.shortDescription}</p>`
181
+ : ''
182
+
183
+ // Merge into bodyContent (inserted before existing body)
184
+ const originalBody = newMessage.bodyContent || ''
185
+ newMessage.bodyContent = `${titleMarkup}${summaryMarkup}${originalBody}`
186
+
187
+ // Remove title/summary fields to prevent accidental rendering
188
+ delete newMessage.title
189
+ delete newMessage.heading
190
+ delete newMessage.summary
191
+ delete newMessage.shortDescription
192
+ delete newMessage.detailLink
193
+ delete newMessage.detailLinkLabel
194
+
195
+ return newMessage
196
+ })
156
197
  },
157
198
 
158
199
  secondaryMessages() {
@@ -215,7 +256,9 @@ export default {
215
256
  coverImageFull.filename.includes('headerimage')
216
257
  ) {
217
258
  imageUrl = null
218
- headerImageUrl = coverImageFull ? coverImageFull.url : coverImage.url || ''
259
+ headerImageUrl = coverImageFull
260
+ ? coverImageFull.url
261
+ : coverImage.url || ''
219
262
  } else {
220
263
  imageUrl = coverImageFull ? coverImageFull.url : coverImage.url || ''
221
264
  imageCaption = coverImage.metadata