@luckydye/calendar 1.1.3 → 1.2.1
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/dist/calendar.js +485 -415
- package/package.json +1 -2
- package/src/CalendarView.ts +320 -164
- package/src/app.ts +2 -0
package/src/CalendarView.ts
CHANGED
|
@@ -241,6 +241,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
241
241
|
overflow-anchor: none;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
:host([scroll-lock]) .scroll-container {
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
}
|
|
247
|
+
|
|
244
248
|
.scroll-container.zoom-cursor {
|
|
245
249
|
cursor: ns-resize;
|
|
246
250
|
}
|
|
@@ -330,12 +334,11 @@ export class CalendarViewElement extends LitElement {
|
|
|
330
334
|
font-size: 16px;
|
|
331
335
|
font-weight: 600;
|
|
332
336
|
color: var(--text-primary, rgba(255, 255, 255, 0.9));
|
|
333
|
-
background: var(--input-bg, rgba(0, 0, 0, 0.2));
|
|
334
337
|
border: 1px solid var(--input-border, rgba(255, 255, 255, 0.1));
|
|
338
|
+
background: transparent;
|
|
335
339
|
border-radius: 4px;
|
|
336
340
|
padding: 6px 8px;
|
|
337
341
|
width: 100%;
|
|
338
|
-
outline: none;
|
|
339
342
|
font-family: inherit;
|
|
340
343
|
margin: 0 -8px 0 -8px;
|
|
341
344
|
field-sizing: content;
|
|
@@ -356,7 +359,6 @@ export class CalendarViewElement extends LitElement {
|
|
|
356
359
|
|
|
357
360
|
.event-detail-title-input:focus {
|
|
358
361
|
border-color: var(--input-border-focus, rgba(255, 255, 255, 0.3));
|
|
359
|
-
background: var(--input-bg-focus, rgba(0, 0, 0, 0.3));
|
|
360
362
|
}
|
|
361
363
|
|
|
362
364
|
.event-detail-time {
|
|
@@ -377,6 +379,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
377
379
|
font-size: 20px;
|
|
378
380
|
line-height: 1;
|
|
379
381
|
flex-shrink: 0;
|
|
382
|
+
position: absolute;
|
|
383
|
+
top: 0.75rem;
|
|
384
|
+
right: 1rem;
|
|
380
385
|
}
|
|
381
386
|
|
|
382
387
|
.event-detail-close:hover {
|
|
@@ -745,19 +750,91 @@ export class CalendarViewElement extends LitElement {
|
|
|
745
750
|
return this._scrollTop;
|
|
746
751
|
}
|
|
747
752
|
|
|
748
|
-
setView(
|
|
749
|
-
|
|
750
|
-
|
|
753
|
+
setView(
|
|
754
|
+
toDayHeight: number,
|
|
755
|
+
toScrollTop: number,
|
|
756
|
+
syncScrollDOM = true,
|
|
757
|
+
animate = false,
|
|
758
|
+
): void {
|
|
759
|
+
this.cancelScrollAnimation();
|
|
760
|
+
|
|
761
|
+
if (animate) {
|
|
762
|
+
const startDayHeight = this._dayHeight;
|
|
763
|
+
const startScroll = this._scrollTop;
|
|
764
|
+
|
|
765
|
+
// Compute scroll as a fraction of total height at each zoom level for smooth interpolation
|
|
766
|
+
const startTotal = this.totalHeight;
|
|
767
|
+
|
|
768
|
+
this._dayHeight = toDayHeight;
|
|
769
|
+
this.updateWeekOffsets();
|
|
770
|
+
const targetTotal = this.totalHeight;
|
|
771
|
+
|
|
772
|
+
this._dayHeight = startDayHeight;
|
|
773
|
+
this.updateWeekOffsets();
|
|
774
|
+
|
|
775
|
+
const startFrac = startTotal > 0 ? startScroll / startTotal : 0;
|
|
776
|
+
const targetFrac = targetTotal > 0 ? toScrollTop / targetTotal : 0;
|
|
777
|
+
|
|
778
|
+
const duration = 600;
|
|
779
|
+
const startTime = performance.now();
|
|
780
|
+
const easeOutExpo = (t: number): number =>
|
|
781
|
+
t === 1 ? 1 : 1 - 2 ** (-10 * t);
|
|
782
|
+
const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;
|
|
783
|
+
|
|
784
|
+
const tick = (currentTime: number): void => {
|
|
785
|
+
if (!this.scrollAnimationFrame) return;
|
|
786
|
+
|
|
787
|
+
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
788
|
+
|
|
789
|
+
this._dayHeight = startDayHeight + (toDayHeight - startDayHeight) * easeOutExpo(progress);
|
|
790
|
+
this.updateWeekOffsets();
|
|
791
|
+
|
|
792
|
+
const currentFrac = startFrac + (targetFrac - startFrac) * easeOutCubic(progress);
|
|
793
|
+
this._scrollTop = Math.max(0, currentFrac * this.totalHeight);
|
|
794
|
+
|
|
795
|
+
if (this.scrollContainer) {
|
|
796
|
+
if (
|
|
797
|
+
this.scrollContent &&
|
|
798
|
+
this.scrollContainer.scrollHeight < this._scrollTop
|
|
799
|
+
) {
|
|
800
|
+
this.scrollContent.style.minHeight =
|
|
801
|
+
this._scrollTop + window.innerHeight + "px";
|
|
802
|
+
}
|
|
803
|
+
this.scrollContainer.scrollTop = this._scrollTop;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
this.renderCanvas();
|
|
807
|
+
|
|
808
|
+
if (progress < 1) {
|
|
809
|
+
this.scrollAnimationFrame = requestAnimationFrame(tick);
|
|
810
|
+
} else {
|
|
811
|
+
this._dayHeight = toDayHeight;
|
|
812
|
+
this._scrollTop = toScrollTop;
|
|
813
|
+
this.saveDayHeight();
|
|
814
|
+
this.saveScrollPosition();
|
|
815
|
+
this.scrollAnimationFrame = null;
|
|
816
|
+
this.renderCanvas();
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
this.scrollAnimationFrame = requestAnimationFrame(tick);
|
|
821
|
+
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
this._dayHeight = toDayHeight;
|
|
826
|
+
this._scrollTop = toScrollTop;
|
|
751
827
|
|
|
752
828
|
this.saveDayHeight();
|
|
753
829
|
this.updateWeekOffsets();
|
|
754
830
|
|
|
755
831
|
if (syncScrollDOM && this.scrollContainer && this.scrollContent) {
|
|
756
|
-
if (this.scrollContainer.scrollHeight <
|
|
757
|
-
this.scrollContent.style.minHeight =
|
|
758
|
-
|
|
832
|
+
if (this.scrollContainer.scrollHeight < toScrollTop) {
|
|
833
|
+
this.scrollContent.style.minHeight = `${
|
|
834
|
+
toScrollTop + window.innerHeight
|
|
835
|
+
}px`;
|
|
759
836
|
}
|
|
760
|
-
this.scrollContainer.scrollTop =
|
|
837
|
+
this.scrollContainer.scrollTop = toScrollTop;
|
|
761
838
|
}
|
|
762
839
|
|
|
763
840
|
this.saveScrollPosition();
|
|
@@ -851,17 +928,19 @@ export class CalendarViewElement extends LitElement {
|
|
|
851
928
|
return { row, col };
|
|
852
929
|
}
|
|
853
930
|
|
|
854
|
-
getVisualPositionFromCoords(
|
|
931
|
+
getVisualPositionFromCoords(
|
|
932
|
+
x: number,
|
|
933
|
+
y: number,
|
|
934
|
+
gridWidth: number,
|
|
935
|
+
): { dayIndex: number; timeFraction: number; weekYOffset: number } | null {
|
|
855
936
|
if (!this.scrollContainer) return null;
|
|
856
|
-
|
|
937
|
+
|
|
857
938
|
const dayWidth = gridWidth / this._columnsPerRow;
|
|
858
939
|
const col = Math.floor((x - LEFT_GUTTER_WIDTH) / dayWidth);
|
|
859
940
|
if (col < 0 || col >= this._columnsPerRow) return null;
|
|
860
941
|
|
|
861
|
-
const week = this.weeks.find(
|
|
862
|
-
w.height > 0 &&
|
|
863
|
-
y >= w.yOffset &&
|
|
864
|
-
y < w.yOffset + w.height
|
|
942
|
+
const week = this.weeks.find(
|
|
943
|
+
(w) => w.height > 0 && y >= w.yOffset && y < w.yOffset + w.height,
|
|
865
944
|
);
|
|
866
945
|
if (!week) return null;
|
|
867
946
|
|
|
@@ -933,8 +1012,15 @@ export class CalendarViewElement extends LitElement {
|
|
|
933
1012
|
this.internal = new CalendarInternal({
|
|
934
1013
|
locale: this.getAttribute("locale") || undefined,
|
|
935
1014
|
weekStart: Number(this.getAttribute("week-start")),
|
|
936
|
-
storage: new IndexedDBStorage()
|
|
1015
|
+
storage: new IndexedDBStorage(),
|
|
937
1016
|
});
|
|
1017
|
+
|
|
1018
|
+
this.addEventListener("wheel", this.onWheel, { passive: false });
|
|
1019
|
+
this.addEventListener("dragstart", this.onDragStart);
|
|
1020
|
+
this.addEventListener("dragend", this.onDragEnd);
|
|
1021
|
+
this.addEventListener("dragover", this.onDragOver);
|
|
1022
|
+
this.addEventListener("dragleave", this.onDragLeave);
|
|
1023
|
+
this.addEventListener("drop", this.onDrop);
|
|
938
1024
|
}
|
|
939
1025
|
|
|
940
1026
|
loadDayHeight(): number {
|
|
@@ -984,18 +1070,16 @@ export class CalendarViewElement extends LitElement {
|
|
|
984
1070
|
}
|
|
985
1071
|
}
|
|
986
1072
|
|
|
987
|
-
scrollToToday = (): void => {
|
|
1073
|
+
scrollToToday = (animate = true): void => {
|
|
988
1074
|
const now = new Date();
|
|
989
1075
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
(w) => w.days.some((d) => CalendarInternal.isSameDay(d, now)),
|
|
1076
|
+
let weekIndex = this.weeks.findIndex((w) =>
|
|
1077
|
+
w.days.some((d) => CalendarInternal.isSameDay(d, now)),
|
|
993
1078
|
);
|
|
994
1079
|
if (weekIndex < 0) {
|
|
995
1080
|
this.weeks = this.internal.resetRangeAroundDate(now);
|
|
996
|
-
this.
|
|
997
|
-
|
|
998
|
-
(w) => w.days.some((d) => CalendarInternal.isSameDay(d, now)),
|
|
1081
|
+
weekIndex = this.weeks.findIndex((w) =>
|
|
1082
|
+
w.days.some((d) => CalendarInternal.isSameDay(d, now)),
|
|
999
1083
|
);
|
|
1000
1084
|
}
|
|
1001
1085
|
|
|
@@ -1012,7 +1096,17 @@ export class CalendarViewElement extends LitElement {
|
|
|
1012
1096
|
}
|
|
1013
1097
|
}
|
|
1014
1098
|
|
|
1015
|
-
this.scrollToDate(now, offsetInWeek, true,
|
|
1099
|
+
this.scrollToDate(now, offsetInWeek, animate, true, 900);
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
scrollToMonth = (animate = true): void => {
|
|
1103
|
+
const now = new Date();
|
|
1104
|
+
const monthMid = new Date(now.getFullYear(), now.getMonth(), 15);
|
|
1105
|
+
const dayHeight = Math.max(
|
|
1106
|
+
MIN_DAY_HEIGHT,
|
|
1107
|
+
Math.round(this.viewportHeight / 5),
|
|
1108
|
+
);
|
|
1109
|
+
this.scrollToDate(monthMid, 0.5, animate, true, dayHeight);
|
|
1016
1110
|
};
|
|
1017
1111
|
|
|
1018
1112
|
// History management methods
|
|
@@ -1100,7 +1194,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
1100
1194
|
offsetInWeek = 0.5,
|
|
1101
1195
|
animate = false,
|
|
1102
1196
|
extendRange = false,
|
|
1197
|
+
targetDayHeight?: number,
|
|
1103
1198
|
): void {
|
|
1199
|
+
const dayHeight = targetDayHeight ?? this._dayHeight;
|
|
1200
|
+
|
|
1104
1201
|
let weekIndex = this.weeks.findIndex(
|
|
1105
1202
|
(w) =>
|
|
1106
1203
|
w.days.some((d) => CalendarInternal.isSameDay(d, targetDate)) ||
|
|
@@ -1113,7 +1210,6 @@ export class CalendarViewElement extends LitElement {
|
|
|
1113
1210
|
// If date not found in current buffer, reset range around target (only if explicitly requested)
|
|
1114
1211
|
if (weekIndex < 0 && extendRange) {
|
|
1115
1212
|
this.weeks = this.internal.resetRangeAroundDate(targetDate);
|
|
1116
|
-
this.updateWeekOffsets();
|
|
1117
1213
|
|
|
1118
1214
|
// Find the week again in the new range
|
|
1119
1215
|
weekIndex = this.weeks.findIndex(
|
|
@@ -1127,15 +1223,24 @@ export class CalendarViewElement extends LitElement {
|
|
|
1127
1223
|
}
|
|
1128
1224
|
|
|
1129
1225
|
if (weekIndex >= 0) {
|
|
1226
|
+
// Temporarily apply target dayHeight to compute correct week offsets
|
|
1227
|
+
const savedDayHeight = this._dayHeight;
|
|
1228
|
+
this._dayHeight = dayHeight;
|
|
1229
|
+
this.updateWeekOffsets();
|
|
1230
|
+
|
|
1130
1231
|
const targetWeek = this.weeks[weekIndex];
|
|
1131
1232
|
if (targetWeek) {
|
|
1132
1233
|
const targetY = targetWeek.yOffset + targetWeek.height * offsetInWeek;
|
|
1133
1234
|
const targetScroll = Math.max(0, targetY - this.viewportHeight / 2);
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1235
|
+
|
|
1236
|
+
// Restore so animateToView reads the correct start dayHeight
|
|
1237
|
+
this._dayHeight = savedDayHeight;
|
|
1238
|
+
if (animate) this.updateWeekOffsets();
|
|
1239
|
+
|
|
1240
|
+
this.setView(dayHeight, targetScroll, true, animate);
|
|
1241
|
+
} else {
|
|
1242
|
+
this._dayHeight = savedDayHeight;
|
|
1243
|
+
this.updateWeekOffsets();
|
|
1139
1244
|
}
|
|
1140
1245
|
}
|
|
1141
1246
|
}
|
|
@@ -1147,39 +1252,6 @@ export class CalendarViewElement extends LitElement {
|
|
|
1147
1252
|
}
|
|
1148
1253
|
};
|
|
1149
1254
|
|
|
1150
|
-
private animateScrollTo(targetScroll: number): void {
|
|
1151
|
-
this.cancelScrollAnimation();
|
|
1152
|
-
|
|
1153
|
-
if (!this.scrollContainer) return;
|
|
1154
|
-
|
|
1155
|
-
const startScroll = this.scrollTop;
|
|
1156
|
-
const distance = targetScroll - startScroll;
|
|
1157
|
-
const duration = 800;
|
|
1158
|
-
const startTime = performance.now();
|
|
1159
|
-
|
|
1160
|
-
const easeOutExpo = (t: number): number => {
|
|
1161
|
-
return t === 1 ? 1 : 1 - 2 ** (-10 * t);
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
const animate = (currentTime: number): void => {
|
|
1165
|
-
if (!this.scrollAnimationFrame) return;
|
|
1166
|
-
|
|
1167
|
-
const elapsed = currentTime - startTime;
|
|
1168
|
-
const progress = Math.min(elapsed / duration, 1);
|
|
1169
|
-
const easedProgress = easeOutExpo(progress);
|
|
1170
|
-
|
|
1171
|
-
this.scrollTop = startScroll + distance * easedProgress;
|
|
1172
|
-
|
|
1173
|
-
if (progress < 1) {
|
|
1174
|
-
this.scrollAnimationFrame = requestAnimationFrame(animate);
|
|
1175
|
-
} else {
|
|
1176
|
-
this.scrollAnimationFrame = null;
|
|
1177
|
-
}
|
|
1178
|
-
};
|
|
1179
|
-
|
|
1180
|
-
this.scrollAnimationFrame = requestAnimationFrame(animate);
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
1255
|
// Push current state when entering filter mode
|
|
1184
1256
|
private pushFilterToHistory(): void {
|
|
1185
1257
|
if (this.saveHistoryTimeout) {
|
|
@@ -1234,25 +1306,16 @@ export class CalendarViewElement extends LitElement {
|
|
|
1234
1306
|
|
|
1235
1307
|
window.addEventListener("mousemove", this.onMouseMove);
|
|
1236
1308
|
window.addEventListener("mouseup", this.onMouseUp);
|
|
1237
|
-
window.addEventListener("wheel", this.onWheel, { passive: false });
|
|
1238
1309
|
window.addEventListener("paste", this.onPaste);
|
|
1239
|
-
this.addEventListener("dragstart", this.onDragStart);
|
|
1240
|
-
this.addEventListener("dragend", this.onDragEnd);
|
|
1241
|
-
this.addEventListener("dragover", this.onDragOver);
|
|
1242
|
-
this.addEventListener("dragleave", this.onDragLeave);
|
|
1243
|
-
this.addEventListener("drop", this.onDrop);
|
|
1244
1310
|
}
|
|
1245
1311
|
|
|
1246
1312
|
disconnectedCallback() {
|
|
1247
1313
|
super.disconnectedCallback();
|
|
1248
|
-
|
|
1249
|
-
|
|
1314
|
+
|
|
1315
|
+
window.removeEventListener("mousemove", this.onMouseMove);
|
|
1316
|
+
window.removeEventListener("mouseup", this.onMouseUp);
|
|
1250
1317
|
window.removeEventListener("paste", this.onPaste);
|
|
1251
|
-
|
|
1252
|
-
this.removeEventListener("dragend", this.onDragEnd);
|
|
1253
|
-
this.removeEventListener("dragover", this.onDragOver);
|
|
1254
|
-
this.removeEventListener("dragleave", this.onDragLeave);
|
|
1255
|
-
this.removeEventListener("drop", this.onDrop);
|
|
1318
|
+
|
|
1256
1319
|
if (this.scrollContainer) {
|
|
1257
1320
|
this.scrollContainer.removeEventListener(
|
|
1258
1321
|
"mouseleave",
|
|
@@ -1277,6 +1340,25 @@ export class CalendarViewElement extends LitElement {
|
|
|
1277
1340
|
|
|
1278
1341
|
updated() {
|
|
1279
1342
|
this.handleResize();
|
|
1343
|
+
this.repositionEventDetailOverlay();
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
repositionEventDetailOverlay(): void {
|
|
1347
|
+
if (!this.selectedEventForDetail || !this.selectedEventRect) return;
|
|
1348
|
+
|
|
1349
|
+
const overlay = this.renderRoot.querySelector<HTMLElement>(".event-detail-overlay");
|
|
1350
|
+
if (!overlay) return;
|
|
1351
|
+
|
|
1352
|
+
const actualHeight = overlay.offsetHeight;
|
|
1353
|
+
const GAP = 8;
|
|
1354
|
+
const containerHeight = this.scrollContainer?.clientHeight || 600;
|
|
1355
|
+
|
|
1356
|
+
const rawTop = this.selectedEventRect.y - this.scrollTop;
|
|
1357
|
+
const minTop = GAP;
|
|
1358
|
+
const maxTop = containerHeight - actualHeight - GAP;
|
|
1359
|
+
const top = Math.max(minTop, Math.min(maxTop, rawTop));
|
|
1360
|
+
|
|
1361
|
+
overlay.style.top = `${top}px`;
|
|
1280
1362
|
}
|
|
1281
1363
|
|
|
1282
1364
|
async firstUpdated() {
|
|
@@ -1299,7 +1381,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
1299
1381
|
}
|
|
1300
1382
|
|
|
1301
1383
|
if (this.scrollContainer) {
|
|
1302
|
-
this.scrollContainer.addEventListener("scroll", this.onScroll
|
|
1384
|
+
this.scrollContainer.addEventListener("scroll", this.onScroll, {
|
|
1385
|
+
passive: false,
|
|
1386
|
+
});
|
|
1303
1387
|
this.scrollContainer.addEventListener(
|
|
1304
1388
|
"mouseleave",
|
|
1305
1389
|
this.onScrollContainerMouseLeave,
|
|
@@ -1451,15 +1535,18 @@ export class CalendarViewElement extends LitElement {
|
|
|
1451
1535
|
|
|
1452
1536
|
updateColumnsForViewport(): void {
|
|
1453
1537
|
if (!this.scrollContainer) return;
|
|
1454
|
-
|
|
1538
|
+
|
|
1455
1539
|
const rect = this.scrollContainer.getBoundingClientRect();
|
|
1456
1540
|
const gridWidth = rect.width - LEFT_GUTTER_WIDTH;
|
|
1457
|
-
|
|
1541
|
+
|
|
1458
1542
|
// Determine optimal columns based on available width
|
|
1459
1543
|
// Minimum 60px per day column for usability
|
|
1460
1544
|
const minDayWidth = 120;
|
|
1461
|
-
const optimalColumns = Math.max(
|
|
1462
|
-
|
|
1545
|
+
const optimalColumns = Math.max(
|
|
1546
|
+
1,
|
|
1547
|
+
Math.min(7, Math.floor(gridWidth / minDayWidth)),
|
|
1548
|
+
);
|
|
1549
|
+
|
|
1463
1550
|
if (this._columnsPerRow !== optimalColumns) {
|
|
1464
1551
|
this._columnsPerRow = optimalColumns;
|
|
1465
1552
|
this.updateWeekOffsets();
|
|
@@ -1685,7 +1772,8 @@ export class CalendarViewElement extends LitElement {
|
|
|
1685
1772
|
if (todayIndex >= 0) {
|
|
1686
1773
|
const { row } = this.getDayVisualPosition(todayIndex);
|
|
1687
1774
|
const currentMinutes = today.getHours() * 60 + today.getMinutes();
|
|
1688
|
-
const timeY =
|
|
1775
|
+
const timeY =
|
|
1776
|
+
y + row * this.dayHeight + (currentMinutes / 1440) * this.dayHeight;
|
|
1689
1777
|
|
|
1690
1778
|
if (timeY >= 0 && timeY <= height) {
|
|
1691
1779
|
const hours = today.getHours().toString().padStart(2, "0");
|
|
@@ -1718,7 +1806,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
1718
1806
|
ctx.fill();
|
|
1719
1807
|
|
|
1720
1808
|
// Draw current time text in white
|
|
1721
|
-
ctx.fillStyle =
|
|
1809
|
+
ctx.fillStyle =
|
|
1810
|
+
getComputedStyle(this)
|
|
1811
|
+
.getPropertyValue("--text-primary")
|
|
1812
|
+
.trim() || "rgba(255, 255, 255, 1)";
|
|
1722
1813
|
ctx.fillText(timeText, textX, textY);
|
|
1723
1814
|
ctx.restore();
|
|
1724
1815
|
}
|
|
@@ -1762,10 +1853,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
1762
1853
|
this.renderEventsOnCanvas(ctx, scrollTop, height, dayWidth, visibleWeeks);
|
|
1763
1854
|
|
|
1764
1855
|
this.renderDateLabels();
|
|
1765
|
-
|
|
1856
|
+
|
|
1766
1857
|
// Draw sticky weekday labels at top of viewport
|
|
1767
1858
|
this.renderWeekdayLabels(ctx, dayWidth, visibleWeeks, scrollTop, height);
|
|
1768
|
-
|
|
1859
|
+
|
|
1769
1860
|
if (this.isCreatingEvent) {
|
|
1770
1861
|
this.renderEventCreationPreview();
|
|
1771
1862
|
}
|
|
@@ -1895,7 +1986,7 @@ export class CalendarViewElement extends LitElement {
|
|
|
1895
1986
|
});
|
|
1896
1987
|
}
|
|
1897
1988
|
return result;
|
|
1898
|
-
|
|
1989
|
+
})
|
|
1899
1990
|
: timedSegments;
|
|
1900
1991
|
|
|
1901
1992
|
// Combine segments with all-day events first (so they get top rows)
|
|
@@ -2577,6 +2668,8 @@ export class CalendarViewElement extends LitElement {
|
|
|
2577
2668
|
}
|
|
2578
2669
|
|
|
2579
2670
|
onWheel = (e: WheelEvent): void => {
|
|
2671
|
+
if (this.hasAttribute("scroll-lock")) return;
|
|
2672
|
+
|
|
2580
2673
|
if (this.scrollAnimationFrame) {
|
|
2581
2674
|
this.cancelScrollAnimation();
|
|
2582
2675
|
}
|
|
@@ -3077,7 +3170,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
3077
3170
|
const item = new ClipboardItem({ "text/plain": blob });
|
|
3078
3171
|
navigator.clipboard.write([item]).then(() => {
|
|
3079
3172
|
const count = selectedEvents.length;
|
|
3080
|
-
queueStatus(
|
|
3173
|
+
queueStatus(
|
|
3174
|
+
`Copied ${count} event${count === 1 ? "" : "s"} to clipboard`,
|
|
3175
|
+
);
|
|
3081
3176
|
});
|
|
3082
3177
|
}
|
|
3083
3178
|
|
|
@@ -3594,11 +3689,17 @@ export class CalendarViewElement extends LitElement {
|
|
|
3594
3689
|
// Find column indices from X coordinates
|
|
3595
3690
|
const startCol = Math.max(
|
|
3596
3691
|
0,
|
|
3597
|
-
Math.min(
|
|
3692
|
+
Math.min(
|
|
3693
|
+
this._columnsPerRow - 1,
|
|
3694
|
+
Math.floor((minX - LEFT_GUTTER_WIDTH) / dayWidth),
|
|
3695
|
+
),
|
|
3598
3696
|
);
|
|
3599
3697
|
const endCol = Math.max(
|
|
3600
3698
|
0,
|
|
3601
|
-
Math.min(
|
|
3699
|
+
Math.min(
|
|
3700
|
+
this._columnsPerRow - 1,
|
|
3701
|
+
Math.floor((maxX - LEFT_GUTTER_WIDTH) / dayWidth),
|
|
3702
|
+
),
|
|
3602
3703
|
);
|
|
3603
3704
|
|
|
3604
3705
|
// Find all weeks that intersect with the selection box
|
|
@@ -3620,19 +3721,19 @@ export class CalendarViewElement extends LitElement {
|
|
|
3620
3721
|
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
|
3621
3722
|
const day = week.days[dayIndex];
|
|
3622
3723
|
if (!day) continue;
|
|
3623
|
-
|
|
3724
|
+
|
|
3624
3725
|
const { row, col } = this.getDayVisualPosition(dayIndex);
|
|
3625
|
-
|
|
3726
|
+
|
|
3626
3727
|
// Check if this day's column is in the selection
|
|
3627
3728
|
if (col < startCol || col > endCol) continue;
|
|
3628
|
-
|
|
3729
|
+
|
|
3629
3730
|
// Calculate Y bounds for this visual row
|
|
3630
3731
|
const rowTop = week.yOffset + row * this.dayHeight;
|
|
3631
3732
|
const rowBottom = rowTop + this.dayHeight;
|
|
3632
|
-
|
|
3733
|
+
|
|
3633
3734
|
// Check if selection intersects with this row
|
|
3634
3735
|
if (maxY < rowTop || minY > rowBottom) continue;
|
|
3635
|
-
|
|
3736
|
+
|
|
3636
3737
|
const dayMinY = Math.max(minY, rowTop);
|
|
3637
3738
|
const dayMaxY = Math.min(maxY, rowBottom);
|
|
3638
3739
|
|
|
@@ -3646,7 +3747,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
3646
3747
|
|
|
3647
3748
|
// Calculate end time for this day
|
|
3648
3749
|
const dayEndOffset = dayMaxY - rowTop;
|
|
3649
|
-
const endMinutes = Math.ceil(
|
|
3750
|
+
const endMinutes = Math.ceil(
|
|
3751
|
+
(dayEndOffset / this.dayHeight) * 24 * 60,
|
|
3752
|
+
);
|
|
3650
3753
|
const endHour = Math.floor(endMinutes / 60);
|
|
3651
3754
|
const endMinute = endMinutes % 60;
|
|
3652
3755
|
|
|
@@ -3667,18 +3770,18 @@ export class CalendarViewElement extends LitElement {
|
|
|
3667
3770
|
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
|
3668
3771
|
const day = week.days[dayIndex];
|
|
3669
3772
|
if (!day) continue;
|
|
3670
|
-
|
|
3773
|
+
|
|
3671
3774
|
const { row, col } = this.getDayVisualPosition(dayIndex);
|
|
3672
|
-
|
|
3775
|
+
|
|
3673
3776
|
// Check if this day's column is in the selection
|
|
3674
3777
|
if (col < startCol || col > endCol) continue;
|
|
3675
|
-
|
|
3778
|
+
|
|
3676
3779
|
// Check if this day's row is in the selection
|
|
3677
3780
|
const rowTop = week.yOffset + row * this.dayHeight;
|
|
3678
3781
|
const rowBottom = rowTop + this.dayHeight;
|
|
3679
|
-
|
|
3782
|
+
|
|
3680
3783
|
if (maxY < rowTop || minY > rowBottom) continue;
|
|
3681
|
-
|
|
3784
|
+
|
|
3682
3785
|
const rangeStart = new Date(day);
|
|
3683
3786
|
rangeStart.setHours(0, 0, 0, 0);
|
|
3684
3787
|
|
|
@@ -3764,6 +3867,8 @@ export class CalendarViewElement extends LitElement {
|
|
|
3764
3867
|
|
|
3765
3868
|
this.renderCanvas();
|
|
3766
3869
|
|
|
3870
|
+
this.repositionEventDetailOverlay();
|
|
3871
|
+
|
|
3767
3872
|
this.checkAndExtendRange();
|
|
3768
3873
|
|
|
3769
3874
|
this.saveScrollPosition();
|
|
@@ -3867,7 +3972,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
3867
3972
|
onNotificationPopoverToggle = async (): Promise<void> => {
|
|
3868
3973
|
this.notificationPopoverOpen = !this.notificationPopoverOpen;
|
|
3869
3974
|
if (this.notificationPopoverOpen) {
|
|
3870
|
-
this.dispatchEvent(
|
|
3975
|
+
this.dispatchEvent(
|
|
3976
|
+
new CustomEvent("load-notifications", { bubbles: true }),
|
|
3977
|
+
);
|
|
3871
3978
|
}
|
|
3872
3979
|
this.requestUpdate();
|
|
3873
3980
|
};
|
|
@@ -3877,7 +3984,6 @@ export class CalendarViewElement extends LitElement {
|
|
|
3877
3984
|
this.requestUpdate();
|
|
3878
3985
|
};
|
|
3879
3986
|
|
|
3880
|
-
|
|
3881
3987
|
onThemeChange = (e: Event): void => {
|
|
3882
3988
|
const select = e.target as HTMLSelectElement;
|
|
3883
3989
|
const theme = select.value as ThemeName;
|
|
@@ -3912,8 +4018,6 @@ export class CalendarViewElement extends LitElement {
|
|
|
3912
4018
|
async onEventClick(event: CalendarEvent, e: MouseEvent): Promise<void> {
|
|
3913
4019
|
const isCmdOrCtrl = e.metaKey || e.ctrlKey;
|
|
3914
4020
|
|
|
3915
|
-
console.log(event);
|
|
3916
|
-
|
|
3917
4021
|
if (isCmdOrCtrl) {
|
|
3918
4022
|
this.internal.selectEvent(event, "toggle");
|
|
3919
4023
|
} else {
|
|
@@ -4204,9 +4308,7 @@ export class CalendarViewElement extends LitElement {
|
|
|
4204
4308
|
const { row } = this.getDayVisualPosition(dayIndex);
|
|
4205
4309
|
const totalMinutes = hours * 60 + minutes;
|
|
4206
4310
|
const rowY = week.yOffset + row * this.dayHeight;
|
|
4207
|
-
return (
|
|
4208
|
-
rowY + (totalMinutes / 1440) * this.dayHeight - this.scrollTop
|
|
4209
|
-
);
|
|
4311
|
+
return rowY + (totalMinutes / 1440) * this.dayHeight - this.scrollTop;
|
|
4210
4312
|
};
|
|
4211
4313
|
|
|
4212
4314
|
const getDayColumnX = (day: Date) => {
|
|
@@ -4381,7 +4483,7 @@ export class CalendarViewElement extends LitElement {
|
|
|
4381
4483
|
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
|
4382
4484
|
const day = week.days[dayIndex];
|
|
4383
4485
|
if (!day) continue;
|
|
4384
|
-
|
|
4486
|
+
|
|
4385
4487
|
const { row, col } = this.getDayVisualPosition(dayIndex);
|
|
4386
4488
|
const x = col * dayWidth;
|
|
4387
4489
|
const dayTop = week.yOffset + row * this.dayHeight - scrollTop;
|
|
@@ -4699,7 +4801,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
4699
4801
|
|
|
4700
4802
|
const col = Math.max(
|
|
4701
4803
|
0,
|
|
4702
|
-
Math.min(
|
|
4804
|
+
Math.min(
|
|
4805
|
+
this._columnsPerRow - 1,
|
|
4806
|
+
Math.floor((x - LEFT_GUTTER_WIDTH) / dayWidth),
|
|
4807
|
+
),
|
|
4703
4808
|
);
|
|
4704
4809
|
|
|
4705
4810
|
// Find week at Y position
|
|
@@ -4712,9 +4817,9 @@ export class CalendarViewElement extends LitElement {
|
|
|
4712
4817
|
// Calculate which visual row within the week
|
|
4713
4818
|
const rowInWeek = Math.floor((y - week.yOffset) / this.dayHeight);
|
|
4714
4819
|
const dayIndex = rowInWeek * this._columnsPerRow + col;
|
|
4715
|
-
|
|
4820
|
+
|
|
4716
4821
|
if (dayIndex < 0 || dayIndex > 6) return null;
|
|
4717
|
-
|
|
4822
|
+
|
|
4718
4823
|
const day = week.days[dayIndex];
|
|
4719
4824
|
if (!day) return null;
|
|
4720
4825
|
|
|
@@ -4857,7 +4962,8 @@ export class CalendarViewElement extends LitElement {
|
|
|
4857
4962
|
return html`<div class="notification-empty-state">No notifications set</div>`;
|
|
4858
4963
|
}
|
|
4859
4964
|
|
|
4860
|
-
return event.reminders.map(
|
|
4965
|
+
return event.reminders.map(
|
|
4966
|
+
(notif) => html`
|
|
4861
4967
|
<div class="notification-item">
|
|
4862
4968
|
<select
|
|
4863
4969
|
class="notification-select"
|
|
@@ -4867,14 +4973,20 @@ export class CalendarViewElement extends LitElement {
|
|
|
4867
4973
|
this.updateNotification(event, notif.id, { triggerOffset: offset });
|
|
4868
4974
|
}}
|
|
4869
4975
|
>
|
|
4870
|
-
${NOTIFICATION_PRESETS.map(
|
|
4976
|
+
${NOTIFICATION_PRESETS.map(
|
|
4977
|
+
(p) =>
|
|
4978
|
+
html`<option value="${p.value}" ?selected=${
|
|
4979
|
+
p.value === notif.triggerOffset
|
|
4980
|
+
}>${p.label}</option>`,
|
|
4981
|
+
)}
|
|
4871
4982
|
</select>
|
|
4872
4983
|
<button
|
|
4873
4984
|
class="notification-remove-button"
|
|
4874
4985
|
@click=${() => this.removeNotification(event, notif.id)}
|
|
4875
4986
|
>×</button>
|
|
4876
4987
|
</div>
|
|
4877
|
-
|
|
4988
|
+
`,
|
|
4989
|
+
);
|
|
4878
4990
|
}
|
|
4879
4991
|
|
|
4880
4992
|
addNotification(event: CalendarEvent) {
|
|
@@ -4884,31 +4996,41 @@ export class CalendarViewElement extends LitElement {
|
|
|
4884
4996
|
enabled: true,
|
|
4885
4997
|
};
|
|
4886
4998
|
const reminders = [...(event.reminders || []), newNotif];
|
|
4887
|
-
this.dispatchEvent(
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4999
|
+
this.dispatchEvent(
|
|
5000
|
+
new CustomEvent("update-event", {
|
|
5001
|
+
detail: { event, updates: { reminders } },
|
|
5002
|
+
bubbles: true,
|
|
5003
|
+
composed: true,
|
|
5004
|
+
}),
|
|
5005
|
+
);
|
|
4892
5006
|
}
|
|
4893
5007
|
|
|
4894
|
-
updateNotification(
|
|
4895
|
-
|
|
4896
|
-
|
|
5008
|
+
updateNotification(
|
|
5009
|
+
event: CalendarEvent,
|
|
5010
|
+
notifId: string,
|
|
5011
|
+
updates: { triggerOffset: number },
|
|
5012
|
+
) {
|
|
5013
|
+
const reminders = (event.reminders || []).map((n) =>
|
|
5014
|
+
n.id === notifId ? { ...n, ...updates } : n,
|
|
5015
|
+
);
|
|
5016
|
+
this.dispatchEvent(
|
|
5017
|
+
new CustomEvent("update-event", {
|
|
5018
|
+
detail: { event, updates: { reminders } },
|
|
5019
|
+
bubbles: true,
|
|
5020
|
+
composed: true,
|
|
5021
|
+
}),
|
|
4897
5022
|
);
|
|
4898
|
-
this.dispatchEvent(new CustomEvent("update-event", {
|
|
4899
|
-
detail: { event, updates: { reminders } },
|
|
4900
|
-
bubbles: true,
|
|
4901
|
-
composed: true,
|
|
4902
|
-
}));
|
|
4903
5023
|
}
|
|
4904
5024
|
|
|
4905
5025
|
removeNotification(event: CalendarEvent, notifId: string) {
|
|
4906
|
-
const reminders = (event.reminders || []).filter(n => n.id !== notifId);
|
|
4907
|
-
this.dispatchEvent(
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
5026
|
+
const reminders = (event.reminders || []).filter((n) => n.id !== notifId);
|
|
5027
|
+
this.dispatchEvent(
|
|
5028
|
+
new CustomEvent("update-event", {
|
|
5029
|
+
detail: { event, updates: { reminders } },
|
|
5030
|
+
bubbles: true,
|
|
5031
|
+
composed: true,
|
|
5032
|
+
}),
|
|
5033
|
+
);
|
|
4912
5034
|
}
|
|
4913
5035
|
|
|
4914
5036
|
shouldRenderEventWithStripes(event: CalendarEvent): boolean {
|
|
@@ -4997,9 +5119,13 @@ export class CalendarViewElement extends LitElement {
|
|
|
4997
5119
|
const containerHeight = this.scrollContainer?.clientHeight || 600;
|
|
4998
5120
|
|
|
4999
5121
|
// Try positioning to the right, fallback to left if no space
|
|
5000
|
-
const rightPosition =
|
|
5122
|
+
const rightPosition =
|
|
5123
|
+
this.selectedEventRect.x + this.selectedEventRect.width + GAP;
|
|
5001
5124
|
const leftPosition = this.selectedEventRect.x - OVERLAY_WIDTH - GAP;
|
|
5002
|
-
const preferredLeft =
|
|
5125
|
+
const preferredLeft =
|
|
5126
|
+
rightPosition + OVERLAY_WIDTH <= containerWidth
|
|
5127
|
+
? rightPosition
|
|
5128
|
+
: leftPosition;
|
|
5003
5129
|
|
|
5004
5130
|
// Clamp to viewport boundaries
|
|
5005
5131
|
const minLeft = LEFT_GUTTER_WIDTH + GAP;
|
|
@@ -5017,24 +5143,22 @@ export class CalendarViewElement extends LitElement {
|
|
|
5017
5143
|
|
|
5018
5144
|
return html`
|
|
5019
5145
|
<div class="event-detail-overlay" style="${style}">
|
|
5020
|
-
<div class="event-detail-color-bar" style="opacity: ${
|
|
5021
|
-
event.readOnly ? "0.5" : "1"
|
|
5022
|
-
}"></div>
|
|
5023
|
-
|
|
5024
5146
|
<div class="event-detail-content">
|
|
5025
5147
|
<div class="event-detail-header">
|
|
5026
5148
|
<div class="event-detail-header-content">
|
|
5027
5149
|
${
|
|
5028
|
-
|
|
5029
|
-
|
|
5150
|
+
event.calendar
|
|
5151
|
+
? html`
|
|
5030
5152
|
<div class="event-detail-section event-detail-calendar">
|
|
5031
5153
|
<div class="event-detail-value">${event.calendar}</div>
|
|
5032
5154
|
</div>
|
|
5033
5155
|
`
|
|
5034
|
-
|
|
5035
|
-
|
|
5156
|
+
: null
|
|
5157
|
+
}
|
|
5036
5158
|
${
|
|
5037
|
-
|
|
5159
|
+
event.readOnly ||
|
|
5160
|
+
(event.organizer != null &&
|
|
5161
|
+
!this.currentUserEmails.has(event.organizer.email))
|
|
5038
5162
|
? html`
|
|
5039
5163
|
<h3 class="event-detail-title">${
|
|
5040
5164
|
event.rrule ? html`<span style="opacity: 0.6">⟳</span> ` : ""
|
|
@@ -5192,12 +5316,15 @@ export class CalendarViewElement extends LitElement {
|
|
|
5192
5316
|
: null;
|
|
5193
5317
|
})()}
|
|
5194
5318
|
|
|
5195
|
-
${
|
|
5319
|
+
${
|
|
5320
|
+
!event.readOnly
|
|
5321
|
+
? html`
|
|
5196
5322
|
<div class="event-detail-section">
|
|
5197
5323
|
<div class="event-detail-label">
|
|
5198
5324
|
<span>Notifications</span>
|
|
5199
5325
|
|
|
5200
|
-
<button class="notification-add-button" title="Add notification" @click=${() =>
|
|
5326
|
+
<button class="notification-add-button" title="Add notification" @click=${() =>
|
|
5327
|
+
this.addNotification(event)}>
|
|
5201
5328
|
+
|
|
5202
5329
|
</button>
|
|
5203
5330
|
</div>
|
|
@@ -5205,10 +5332,14 @@ export class CalendarViewElement extends LitElement {
|
|
|
5205
5332
|
${this.renderNotificationsList(event)}
|
|
5206
5333
|
</div>
|
|
5207
5334
|
</div>
|
|
5208
|
-
`
|
|
5335
|
+
`
|
|
5336
|
+
: null
|
|
5337
|
+
}
|
|
5209
5338
|
|
|
5210
5339
|
${
|
|
5211
|
-
|
|
5340
|
+
event.readOnly ||
|
|
5341
|
+
(event.organizer != null &&
|
|
5342
|
+
!this.currentUserEmails.has(event.organizer.email))
|
|
5212
5343
|
? event.description
|
|
5213
5344
|
? html`
|
|
5214
5345
|
<div class="event-detail-section">
|
|
@@ -5258,7 +5389,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
5258
5389
|
this.updateEventTimeout = setTimeout(() => {
|
|
5259
5390
|
this.dispatchEvent(
|
|
5260
5391
|
new CustomEvent("update-event", {
|
|
5261
|
-
detail: {
|
|
5392
|
+
detail: {
|
|
5393
|
+
event,
|
|
5394
|
+
updates: { description: newDescription },
|
|
5395
|
+
},
|
|
5262
5396
|
bubbles: true,
|
|
5263
5397
|
composed: true,
|
|
5264
5398
|
}),
|
|
@@ -5276,7 +5410,10 @@ export class CalendarViewElement extends LitElement {
|
|
|
5276
5410
|
if (newDescription !== (event.description ?? "")) {
|
|
5277
5411
|
this.dispatchEvent(
|
|
5278
5412
|
new CustomEvent("update-event", {
|
|
5279
|
-
detail: {
|
|
5413
|
+
detail: {
|
|
5414
|
+
event,
|
|
5415
|
+
updates: { description: newDescription },
|
|
5416
|
+
},
|
|
5280
5417
|
bubbles: true,
|
|
5281
5418
|
composed: true,
|
|
5282
5419
|
}),
|
|
@@ -5352,6 +5489,11 @@ export class CalendarViewElement extends LitElement {
|
|
|
5352
5489
|
<div class="toolbar-left">
|
|
5353
5490
|
<slot name="toolbar-center"></slot>
|
|
5354
5491
|
|
|
5492
|
+
<button class="toolbar-button" title="Month" @click=${
|
|
5493
|
+
this.scrollToMonth
|
|
5494
|
+
}>
|
|
5495
|
+
Month
|
|
5496
|
+
</button>
|
|
5355
5497
|
<button class="toolbar-button" title="Today" @click=${
|
|
5356
5498
|
this.scrollToToday
|
|
5357
5499
|
}>
|
|
@@ -5460,31 +5602,45 @@ export class CalendarViewElement extends LitElement {
|
|
|
5460
5602
|
};
|
|
5461
5603
|
|
|
5462
5604
|
return html`
|
|
5463
|
-
<div class="notification-popover-overlay" @click=${
|
|
5605
|
+
<div class="notification-popover-overlay" @click=${
|
|
5606
|
+
this.onNotificationPopoverToggle
|
|
5607
|
+
}></div>
|
|
5464
5608
|
<div class="notification-popover">
|
|
5465
5609
|
<div class="notification-popover-header">
|
|
5466
5610
|
<h3>Upcoming Notifications</h3>
|
|
5467
|
-
<button class="notification-popover-close" @click=${
|
|
5611
|
+
<button class="notification-popover-close" @click=${
|
|
5612
|
+
this.onNotificationPopoverToggle
|
|
5613
|
+
}>×</button>
|
|
5468
5614
|
</div>
|
|
5469
5615
|
<div class="notification-popover-content">
|
|
5470
|
-
${
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5616
|
+
${
|
|
5617
|
+
this.scheduledNotifications.length === 0
|
|
5618
|
+
? html`<div class="notification-popover-empty">No scheduled notifications</div>`
|
|
5619
|
+
: this.scheduledNotifications.map(
|
|
5620
|
+
(notif) => html`
|
|
5474
5621
|
<div class="notification-popover-item">
|
|
5475
5622
|
<div class="notification-popover-item-header">
|
|
5476
|
-
<div class="notification-popover-item-title">${
|
|
5477
|
-
|
|
5623
|
+
<div class="notification-popover-item-title">${
|
|
5624
|
+
notif.eventTitle
|
|
5625
|
+
}</div>
|
|
5626
|
+
<div class="notification-popover-item-time">${formatTriggerTime(
|
|
5627
|
+
notif.triggerTime,
|
|
5628
|
+
)}</div>
|
|
5478
5629
|
</div>
|
|
5479
5630
|
<div class="notification-popover-item-details">
|
|
5480
|
-
<div class="notification-popover-item-event-time">📅 ${formatEventTime(
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5631
|
+
<div class="notification-popover-item-event-time">📅 ${formatEventTime(
|
|
5632
|
+
notif.eventStart,
|
|
5633
|
+
)}</div>
|
|
5634
|
+
${
|
|
5635
|
+
notif.eventLocation
|
|
5636
|
+
? html`<div class="notification-popover-item-location">📍 ${notif.eventLocation}</div>`
|
|
5637
|
+
: null
|
|
5638
|
+
}
|
|
5484
5639
|
</div>
|
|
5485
5640
|
</div>
|
|
5486
5641
|
`,
|
|
5487
|
-
|
|
5642
|
+
)
|
|
5643
|
+
}
|
|
5488
5644
|
</div>
|
|
5489
5645
|
</div>
|
|
5490
5646
|
`;
|