@apptegy/nuxt-navigation 0.1.114 → 0.1.116
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/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/Nav/Header.vue +29 -22
- package/dist/runtime/components/Nav/Header.vue.d.ts +23 -14
- package/dist/runtime/components/Nav/NotificationItem.vue +1 -18
- package/dist/runtime/components/Nav/NotificationItem.vue.d.ts +2 -2
- package/dist/runtime/components/Nav/NotificationsPanel.vue +33 -7
- package/dist/runtime/components/Nav/NotificationsPanel.vue.d.ts +9 -9
- package/dist/runtime/composables/useSidebar.d.ts +1 -1
- package/dist/runtime/constants/notification-types.d.ts +4 -2
- package/dist/runtime/constants/notification-types.js +15 -8
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/notifications.d.ts +28 -1
- package/dist/runtime/utils/notification-api.d.ts +3 -0
- package/dist/runtime/utils/notification-api.js +12 -1
- package/dist/runtime/utils/notifications.d.ts +14 -0
- package/dist/runtime/utils/notifications.js +57 -7
- package/package.json +15 -16
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir } from '@nuxt/kit';
|
|
2
2
|
|
|
3
|
-
const module
|
|
3
|
+
const module = defineNuxtModule({
|
|
4
4
|
meta: {
|
|
5
5
|
name: "@apptegy/nuxt-navigation",
|
|
6
6
|
compatibility: {
|
|
@@ -22,4 +22,4 @@ const module$1 = defineNuxtModule({
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export { module
|
|
25
|
+
export { module as default };
|
|
@@ -148,12 +148,7 @@ import {
|
|
|
148
148
|
} from "../../utils/notification-api";
|
|
149
149
|
const props = defineProps({
|
|
150
150
|
sticky: { type: Boolean, required: false },
|
|
151
|
-
|
|
152
|
-
notificationsResponse: { type: [Object, null], required: false, default: null },
|
|
153
|
-
notificationsLoading: { type: Boolean, required: false, default: false },
|
|
154
|
-
notificationPanelOpen: { type: Boolean, required: false, default: false },
|
|
155
|
-
activeTabId: { type: String, required: false, default: "" },
|
|
156
|
-
activeFilterId: { type: String, required: false, default: "" },
|
|
151
|
+
notifications: { type: [Object, null], required: false, default: null },
|
|
157
152
|
user: { type: Object, required: true },
|
|
158
153
|
impersonatedUser: { type: Object, required: false },
|
|
159
154
|
useLogoutEmit: { type: Boolean, required: false },
|
|
@@ -163,9 +158,7 @@ const nuxtApp = useNuxtApp();
|
|
|
163
158
|
const emit = defineEmits([
|
|
164
159
|
"selected-option",
|
|
165
160
|
"user-logout",
|
|
166
|
-
"update:
|
|
167
|
-
"update:activeTabId",
|
|
168
|
-
"update:activeFilterId",
|
|
161
|
+
"update:notifications",
|
|
169
162
|
"notifications-clear-all",
|
|
170
163
|
"notifications-dismiss",
|
|
171
164
|
"notifications-select",
|
|
@@ -173,8 +166,13 @@ const emit = defineEmits([
|
|
|
173
166
|
]);
|
|
174
167
|
const popoverRef = ref(null);
|
|
175
168
|
const isMobilePanel = useMediaQuery("(max-width: 960px)");
|
|
176
|
-
const panelOpen = ref(props.notificationPanelOpen);
|
|
177
169
|
const isBellShaking = ref(false);
|
|
170
|
+
const showNotifications = computed(() => props.notifications != null);
|
|
171
|
+
const notificationsResponse = computed(() => props.notifications?.response ?? null);
|
|
172
|
+
const notificationsLoading = computed(() => props.notifications?.loading ?? false);
|
|
173
|
+
const panelOpen = ref(props.notifications?.panelOpen ?? false);
|
|
174
|
+
const activeTabId = ref(props.notifications?.activeTabId || "new");
|
|
175
|
+
const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
|
|
178
176
|
const popoverPt = {
|
|
179
177
|
content: {
|
|
180
178
|
class: "notifications-popover__content",
|
|
@@ -188,10 +186,10 @@ const notificationDateLabels = computed(() => ({
|
|
|
188
186
|
justNow: nuxtApp.$t("navigation.notificationsPanel.justNow")
|
|
189
187
|
}));
|
|
190
188
|
const notificationsPanel = computed(() => {
|
|
191
|
-
if (!
|
|
189
|
+
if (!notificationsResponse.value) {
|
|
192
190
|
return null;
|
|
193
191
|
}
|
|
194
|
-
return mapNotificationsApiResponse(
|
|
192
|
+
return mapNotificationsApiResponse(notificationsResponse.value, {
|
|
195
193
|
locale: nuxtApp.$intl.locale,
|
|
196
194
|
dateLabels: notificationDateLabels.value,
|
|
197
195
|
retentionLabel: nuxtApp.$t("navigation.notificationsPanel.retentionLabel"),
|
|
@@ -202,10 +200,17 @@ const notificationsPanel = computed(() => {
|
|
|
202
200
|
});
|
|
203
201
|
});
|
|
204
202
|
const notificationCount = computed(
|
|
205
|
-
() => getNotificationsTotalCount(
|
|
203
|
+
() => getNotificationsTotalCount(notificationsResponse.value)
|
|
206
204
|
);
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
function patchNotifications(patch) {
|
|
206
|
+
if (!props.notifications) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
emit("update:notifications", {
|
|
210
|
+
...props.notifications,
|
|
211
|
+
...patch
|
|
212
|
+
});
|
|
213
|
+
}
|
|
209
214
|
watch(
|
|
210
215
|
notificationCount,
|
|
211
216
|
(newCount, oldCount) => {
|
|
@@ -223,19 +228,21 @@ function onBellShakeEnd() {
|
|
|
223
228
|
isBellShaking.value = false;
|
|
224
229
|
}
|
|
225
230
|
watch(
|
|
226
|
-
() => props.
|
|
231
|
+
() => props.notifications?.panelOpen,
|
|
227
232
|
(value) => {
|
|
228
|
-
|
|
233
|
+
if (value !== void 0) {
|
|
234
|
+
panelOpen.value = value;
|
|
235
|
+
}
|
|
229
236
|
}
|
|
230
237
|
);
|
|
231
238
|
watch(panelOpen, (value) => {
|
|
232
|
-
|
|
239
|
+
patchNotifications({ panelOpen: value });
|
|
233
240
|
if (!value && !isMobilePanel.value) {
|
|
234
241
|
popoverRef.value?.hide();
|
|
235
242
|
}
|
|
236
243
|
});
|
|
237
244
|
watch(
|
|
238
|
-
() => props.activeTabId,
|
|
245
|
+
() => props.notifications?.activeTabId,
|
|
239
246
|
(value) => {
|
|
240
247
|
if (value) {
|
|
241
248
|
activeTabId.value = value;
|
|
@@ -243,7 +250,7 @@ watch(
|
|
|
243
250
|
}
|
|
244
251
|
);
|
|
245
252
|
watch(
|
|
246
|
-
() => props.activeFilterId,
|
|
253
|
+
() => props.notifications?.activeFilterId,
|
|
247
254
|
(value) => {
|
|
248
255
|
if (value) {
|
|
249
256
|
activeFilterId.value = value;
|
|
@@ -279,11 +286,11 @@ function handleNotificationSelect(payload) {
|
|
|
279
286
|
}
|
|
280
287
|
function handleTabChange(id) {
|
|
281
288
|
activeTabId.value = id;
|
|
282
|
-
|
|
289
|
+
patchNotifications({ activeTabId: id });
|
|
283
290
|
}
|
|
284
291
|
function handleFilterChange(id) {
|
|
285
292
|
activeFilterId.value = id;
|
|
286
|
-
|
|
293
|
+
patchNotifications({ activeFilterId: id });
|
|
287
294
|
}
|
|
288
295
|
</script>
|
|
289
296
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { INotificationsHeaderModel, IUser } from '../../types/index.js';
|
|
2
2
|
interface IHeaderProps {
|
|
3
3
|
sticky?: boolean;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
activeTabId?: string;
|
|
9
|
-
activeFilterId?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Host-owned notification center state. Pass `null`/`undefined` to hide the bell.
|
|
6
|
+
*/
|
|
7
|
+
notifications?: INotificationsHeaderModel | null;
|
|
10
8
|
}
|
|
11
9
|
interface IUserDropdown {
|
|
12
10
|
user: {
|
|
@@ -29,13 +27,24 @@ type __VLS_Slots = {} & {
|
|
|
29
27
|
} & {
|
|
30
28
|
notifications?: (props: typeof __VLS_15) => any;
|
|
31
29
|
};
|
|
32
|
-
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
+
"selected-option": (...args: any[]) => void;
|
|
32
|
+
"user-logout": (...args: any[]) => void;
|
|
33
|
+
"update:notifications": (...args: any[]) => void;
|
|
34
|
+
"notifications-clear-all": (...args: any[]) => void;
|
|
35
|
+
"notifications-dismiss": (...args: any[]) => void;
|
|
36
|
+
"notifications-select": (...args: any[]) => void;
|
|
37
|
+
"notification-click": (...args: any[]) => void;
|
|
38
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
|
+
"onSelected-option"?: ((...args: any[]) => any) | undefined;
|
|
40
|
+
"onUser-logout"?: ((...args: any[]) => any) | undefined;
|
|
41
|
+
"onUpdate:notifications"?: ((...args: any[]) => any) | undefined;
|
|
42
|
+
"onNotifications-clear-all"?: ((...args: any[]) => any) | undefined;
|
|
43
|
+
"onNotifications-dismiss"?: ((...args: any[]) => any) | undefined;
|
|
44
|
+
"onNotifications-select"?: ((...args: any[]) => any) | undefined;
|
|
45
|
+
"onNotification-click"?: ((...args: any[]) => any) | undefined;
|
|
46
|
+
}>, {
|
|
47
|
+
notifications: INotificationsHeaderModel | null;
|
|
39
48
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
40
49
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
41
50
|
export default _default;
|
|
@@ -38,10 +38,6 @@
|
|
|
38
38
|
|
|
39
39
|
<div class="notification-item__content">
|
|
40
40
|
<p class="notification-item__message">
|
|
41
|
-
<strong>{{ primaryActor.name }}</strong>
|
|
42
|
-
<template v-if="additionalActorsLabel">
|
|
43
|
-
{{ additionalActorsLabel }}
|
|
44
|
-
</template>
|
|
45
41
|
{{ item.actionText }}
|
|
46
42
|
</p>
|
|
47
43
|
<p class="notification-item__meta">
|
|
@@ -106,16 +102,6 @@ const groupBadgeLabel = computed(() => {
|
|
|
106
102
|
const count = props.item.groupCount ?? Math.max(props.item.actors.length - 1, 0);
|
|
107
103
|
return count > 0 ? `+${count}` : "";
|
|
108
104
|
});
|
|
109
|
-
const additionalActorsLabel = computed(() => {
|
|
110
|
-
if (props.item.groupCount && props.item.groupCount > 0) {
|
|
111
|
-
return ` and ${props.item.groupCount} others`;
|
|
112
|
-
}
|
|
113
|
-
if (props.item.actors.length <= 1) {
|
|
114
|
-
return "";
|
|
115
|
-
}
|
|
116
|
-
const names = props.item.actors.slice(1).map((actor) => actor.name);
|
|
117
|
-
return `, ${names.join(", ")}`;
|
|
118
|
-
});
|
|
119
105
|
function svgIcon(paths) {
|
|
120
106
|
return h(
|
|
121
107
|
"svg",
|
|
@@ -220,7 +206,7 @@ function handleDismiss(event) {
|
|
|
220
206
|
text-align: left;
|
|
221
207
|
color: inherit;
|
|
222
208
|
}
|
|
223
|
-
.notification-item__main:hover .notification-item__message
|
|
209
|
+
.notification-item__main:hover .notification-item__message {
|
|
224
210
|
color: var(--purple-60, #662e80);
|
|
225
211
|
}
|
|
226
212
|
.notification-item__main:focus-visible {
|
|
@@ -297,9 +283,6 @@ function handleDismiss(event) {
|
|
|
297
283
|
line-height: 20px;
|
|
298
284
|
color: var(--grey-90, #111827);
|
|
299
285
|
}
|
|
300
|
-
.notification-item__message strong {
|
|
301
|
-
font-weight: 600;
|
|
302
|
-
}
|
|
303
286
|
.notification-item__meta {
|
|
304
287
|
display: flex;
|
|
305
288
|
flex-wrap: wrap;
|
|
@@ -3,10 +3,10 @@ type __VLS_Props = {
|
|
|
3
3
|
item: INotificationItem;
|
|
4
4
|
};
|
|
5
5
|
declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
-
dismiss: (id: string) => any;
|
|
7
6
|
select: (item: INotificationItem) => any;
|
|
7
|
+
dismiss: (id: string) => any;
|
|
8
8
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
-
onDismiss?: ((id: string) => any) | undefined;
|
|
10
9
|
onSelect?: ((item: INotificationItem) => any) | undefined;
|
|
10
|
+
onDismiss?: ((id: string) => any) | undefined;
|
|
11
11
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
12
|
export default _default;
|
|
@@ -185,7 +185,7 @@ import {
|
|
|
185
185
|
getVisibleFilters,
|
|
186
186
|
isInBadge,
|
|
187
187
|
notificationMatchesFilter,
|
|
188
|
-
|
|
188
|
+
resolveStickyNotificationFilters,
|
|
189
189
|
shouldShowFilterChips
|
|
190
190
|
} from "../../utils/notifications";
|
|
191
191
|
const props = defineProps({
|
|
@@ -200,6 +200,7 @@ const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "clear-
|
|
|
200
200
|
const filtersRef = ref();
|
|
201
201
|
const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
|
|
202
202
|
const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
|
|
203
|
+
const stickyFilters = ref([]);
|
|
203
204
|
watch(
|
|
204
205
|
() => props.activeTabId,
|
|
205
206
|
(value) => {
|
|
@@ -230,19 +231,44 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
|
|
|
230
231
|
const activeTab = computed(
|
|
231
232
|
() => props.data.tabs.find((tab) => tab.id === activeTabId.value)
|
|
232
233
|
);
|
|
233
|
-
|
|
234
|
-
() =>
|
|
234
|
+
function filterCatalogKey(filters) {
|
|
235
|
+
return filters.map((filter) => filter.id).join("|");
|
|
236
|
+
}
|
|
237
|
+
watch(
|
|
238
|
+
[allNotifications, activeTab, activeFilterId, () => props.data.filters],
|
|
239
|
+
() => {
|
|
240
|
+
const { nextStickyFilters } = resolveStickyNotificationFilters({
|
|
241
|
+
notifications: allNotifications.value,
|
|
242
|
+
tab: activeTab.value,
|
|
243
|
+
providedFilters: props.data.filters,
|
|
244
|
+
activeFilterId: activeFilterId.value,
|
|
245
|
+
stickyFilters: stickyFilters.value
|
|
246
|
+
});
|
|
247
|
+
if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
|
|
248
|
+
stickyFilters.value = nextStickyFilters;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{ immediate: true, deep: true }
|
|
252
|
+
);
|
|
253
|
+
const displayFilters = computed(
|
|
254
|
+
() => resolveStickyNotificationFilters({
|
|
255
|
+
notifications: allNotifications.value,
|
|
256
|
+
tab: activeTab.value,
|
|
257
|
+
providedFilters: props.data.filters,
|
|
258
|
+
activeFilterId: activeFilterId.value,
|
|
259
|
+
stickyFilters: stickyFilters.value
|
|
260
|
+
}).filters
|
|
235
261
|
);
|
|
236
262
|
const visibleFilters = computed(() => {
|
|
237
263
|
if (usesDynamicFilters.value) {
|
|
238
|
-
return
|
|
264
|
+
return displayFilters.value;
|
|
239
265
|
}
|
|
240
|
-
return getVisibleFilters(allNotifications.value,
|
|
266
|
+
return getVisibleFilters(allNotifications.value, displayFilters.value, activeTab.value);
|
|
241
267
|
});
|
|
242
268
|
const showFilterChips = computed(
|
|
243
269
|
() => shouldShowFilterChips(
|
|
244
270
|
allNotifications.value,
|
|
245
|
-
|
|
271
|
+
displayFilters.value,
|
|
246
272
|
activeTab.value,
|
|
247
273
|
usesDynamicFilters.value
|
|
248
274
|
)
|
|
@@ -269,7 +295,7 @@ function formatCount(count) {
|
|
|
269
295
|
return formatNotificationCount(count);
|
|
270
296
|
}
|
|
271
297
|
const activeFilter = computed(
|
|
272
|
-
() => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) ||
|
|
298
|
+
() => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) || displayFilters.value.find((filter) => filter.id === activeFilterId.value)
|
|
273
299
|
);
|
|
274
300
|
function showClearAll(groupIndex) {
|
|
275
301
|
return groupIndex === 0 && (activeTabId.value === "new" || activeTab.value?.unreadOnly);
|
|
@@ -8,6 +8,11 @@ type __VLS_Props = {
|
|
|
8
8
|
mobile?: boolean;
|
|
9
9
|
};
|
|
10
10
|
declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
select: (payload: {
|
|
12
|
+
item: INotificationsPanelData["groups"][number]["notifications"][number];
|
|
13
|
+
tabId: string;
|
|
14
|
+
filterId: string;
|
|
15
|
+
}) => any;
|
|
11
16
|
"update:activeTabId": (id: string) => any;
|
|
12
17
|
"update:activeFilterId": (id: string) => any;
|
|
13
18
|
"clear-all": (payload: {
|
|
@@ -20,13 +25,13 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
|
|
|
20
25
|
tabId: string;
|
|
21
26
|
filterId: string;
|
|
22
27
|
}) => any;
|
|
23
|
-
|
|
28
|
+
close: () => any;
|
|
29
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
30
|
+
onSelect?: ((payload: {
|
|
24
31
|
item: INotificationsPanelData["groups"][number]["notifications"][number];
|
|
25
32
|
tabId: string;
|
|
26
33
|
filterId: string;
|
|
27
|
-
}) => any;
|
|
28
|
-
close: () => any;
|
|
29
|
-
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
34
|
+
}) => any) | undefined;
|
|
30
35
|
"onUpdate:activeTabId"?: ((id: string) => any) | undefined;
|
|
31
36
|
"onUpdate:activeFilterId"?: ((id: string) => any) | undefined;
|
|
32
37
|
"onClear-all"?: ((payload: {
|
|
@@ -39,11 +44,6 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
|
|
|
39
44
|
tabId: string;
|
|
40
45
|
filterId: string;
|
|
41
46
|
}) => any) | undefined;
|
|
42
|
-
onSelect?: ((payload: {
|
|
43
|
-
item: INotificationsPanelData["groups"][number]["notifications"][number];
|
|
44
|
-
tabId: string;
|
|
45
|
-
filterId: string;
|
|
46
|
-
}) => any) | undefined;
|
|
47
47
|
onClose?: (() => any) | undefined;
|
|
48
48
|
}>, {
|
|
49
49
|
loading: boolean;
|
|
@@ -6,14 +6,16 @@ export declare const NOTIFICATION_BADGE_COLOR = "#C84B31";
|
|
|
6
6
|
export declare const NOTIFICATION_FILTER_CATEGORIES: Array<{
|
|
7
7
|
id: string;
|
|
8
8
|
label: string;
|
|
9
|
-
|
|
9
|
+
kinds: string[];
|
|
10
10
|
}>;
|
|
11
11
|
/** @deprecated Use NOTIFICATION_FILTER_CATEGORIES */
|
|
12
12
|
export declare const DEFAULT_NOTIFICATION_FILTERS: ({
|
|
13
13
|
id: string;
|
|
14
14
|
label: string;
|
|
15
|
-
|
|
15
|
+
kinds: string[];
|
|
16
16
|
} | {
|
|
17
17
|
id: string;
|
|
18
18
|
label: string;
|
|
19
19
|
})[];
|
|
20
|
+
/** Resolve `filter[kind][]` values for a notification center filter pill id. */
|
|
21
|
+
export declare function getKindsForNotificationFilter(filterId: string | null | undefined): string[];
|
|
@@ -14,35 +14,42 @@ export const NOTIFICATION_FILTER_CATEGORIES = [
|
|
|
14
14
|
{
|
|
15
15
|
id: "announcements",
|
|
16
16
|
label: "Announcements",
|
|
17
|
-
|
|
17
|
+
kinds: ["announcement"]
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
id: "messages",
|
|
21
21
|
label: "Messages",
|
|
22
|
-
|
|
22
|
+
kinds: ["message"]
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
id: "comments-
|
|
26
|
-
label: "Comments &
|
|
27
|
-
|
|
25
|
+
id: "comments-reactions",
|
|
26
|
+
label: "Comments & Reactions",
|
|
27
|
+
kinds: ["comment", "reaction"]
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
id: "school-posts",
|
|
31
31
|
label: "School Posts",
|
|
32
|
-
|
|
32
|
+
kinds: ["school_post"]
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
id: "system",
|
|
36
36
|
label: "System",
|
|
37
|
-
|
|
37
|
+
kinds: ["system"]
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
id: "other",
|
|
41
41
|
label: "Other",
|
|
42
|
-
|
|
42
|
+
kinds: ["unknown"]
|
|
43
43
|
}
|
|
44
44
|
];
|
|
45
45
|
export const DEFAULT_NOTIFICATION_FILTERS = [
|
|
46
46
|
{ id: "all-types", label: "All types" },
|
|
47
47
|
...NOTIFICATION_FILTER_CATEGORIES
|
|
48
48
|
];
|
|
49
|
+
export function getKindsForNotificationFilter(filterId) {
|
|
50
|
+
if (!filterId || filterId === "all-types") {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const category = NOTIFICATION_FILTER_CATEGORIES.find((filter) => filter.id === filterId);
|
|
54
|
+
return category?.kinds ? [...category.kinds] : [];
|
|
55
|
+
}
|
|
@@ -11,4 +11,4 @@ export interface IUser {
|
|
|
11
11
|
client: string;
|
|
12
12
|
admin_email: string;
|
|
13
13
|
}
|
|
14
|
-
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, } from './notifications.js';
|
|
14
|
+
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
|
|
@@ -7,6 +7,11 @@ export interface INotificationApiActor {
|
|
|
7
7
|
}
|
|
8
8
|
export interface INotificationApiRecord {
|
|
9
9
|
id: number | string;
|
|
10
|
+
/**
|
|
11
|
+
* Category used for filter pills / `filter[kind][]` (e.g. announcement, comment, reaction).
|
|
12
|
+
* Distinct from `source_record_type`, which describes the underlying record.
|
|
13
|
+
*/
|
|
14
|
+
kind?: string | null;
|
|
10
15
|
source_record_type: string;
|
|
11
16
|
title: string;
|
|
12
17
|
summary?: string;
|
|
@@ -33,7 +38,13 @@ export interface INotificationActor {
|
|
|
33
38
|
}
|
|
34
39
|
export interface INotificationItem {
|
|
35
40
|
id: string;
|
|
41
|
+
/** Visual/icon type — derived from `source_record_type`. */
|
|
36
42
|
type: NotificationType;
|
|
43
|
+
/**
|
|
44
|
+
* Filter category — derived from API `kind` (falls back to `type` when kind is absent).
|
|
45
|
+
* Used by filter pills and `filter[kind][]`.
|
|
46
|
+
*/
|
|
47
|
+
kind: string;
|
|
37
48
|
actors: INotificationActor[];
|
|
38
49
|
actionText: string;
|
|
39
50
|
timestampLabel: string;
|
|
@@ -67,7 +78,15 @@ export interface INotificationFilter {
|
|
|
67
78
|
id: string;
|
|
68
79
|
label: string;
|
|
69
80
|
count?: number;
|
|
70
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Notification kinds included in this filter (`filter[kind][]`).
|
|
83
|
+
* Omit or leave empty to include all kinds.
|
|
84
|
+
*/
|
|
85
|
+
kinds?: string[];
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated Prefer `kinds`. Kept for callers that still filter by
|
|
88
|
+
* `source_record_type`-derived `type`.
|
|
89
|
+
*/
|
|
71
90
|
types?: NotificationType[];
|
|
72
91
|
}
|
|
73
92
|
export interface INotificationsPanelData {
|
|
@@ -77,3 +96,11 @@ export interface INotificationsPanelData {
|
|
|
77
96
|
filters?: INotificationFilter[];
|
|
78
97
|
groups: INotificationGroup[];
|
|
79
98
|
}
|
|
99
|
+
/** Host-owned notification center state bound to NavHeader via `v-model:notifications`. */
|
|
100
|
+
export interface INotificationsHeaderModel {
|
|
101
|
+
response: INotificationsApiResponse | null;
|
|
102
|
+
loading?: boolean;
|
|
103
|
+
panelOpen?: boolean;
|
|
104
|
+
activeTabId?: string;
|
|
105
|
+
activeFilterId?: string;
|
|
106
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { INotificationApiActor, INotificationApiRecord, INotificationGroup, INotificationItem, INotificationsApiResponse, INotificationsPanelData, INotificationTab, NotificationType } from '../types/index.js';
|
|
2
2
|
import { type INotificationDateLabels } from './notification-dates.js';
|
|
3
3
|
export declare const DEFAULT_NOTIFICATION_TABS: INotificationTab[];
|
|
4
|
+
/** Normalize kind values for filter matching and `filter[kind][]` query params. */
|
|
5
|
+
export declare function normalizeNotificationKind(value: string): string;
|
|
4
6
|
export declare function mapSourceRecordType(sourceRecordType: string): NotificationType;
|
|
7
|
+
export declare function mapNotificationKind(kind: string | null | undefined, fallbackType: NotificationType): string;
|
|
5
8
|
export declare function mapNotificationApiActor(actor: INotificationApiActor): {
|
|
6
9
|
name: string;
|
|
7
10
|
avatarUrl: string | undefined;
|
|
@@ -19,10 +19,19 @@ export const DEFAULT_NOTIFICATION_TABS = [
|
|
|
19
19
|
{ id: "new", label: "New", unreadOnly: true },
|
|
20
20
|
{ id: "all", label: "All" }
|
|
21
21
|
];
|
|
22
|
+
export function normalizeNotificationKind(value) {
|
|
23
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/-/g, "_");
|
|
24
|
+
}
|
|
22
25
|
export function mapSourceRecordType(sourceRecordType) {
|
|
23
26
|
const normalizedSourceRecordType = sourceRecordType.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
24
27
|
return SOURCE_RECORD_TYPE_MAP[normalizedSourceRecordType] ?? "unknown";
|
|
25
28
|
}
|
|
29
|
+
export function mapNotificationKind(kind, fallbackType) {
|
|
30
|
+
if (!kind) {
|
|
31
|
+
return normalizeNotificationKind(fallbackType);
|
|
32
|
+
}
|
|
33
|
+
return normalizeNotificationKind(kind) || normalizeNotificationKind(fallbackType);
|
|
34
|
+
}
|
|
26
35
|
export function mapNotificationApiActor(actor) {
|
|
27
36
|
const name = [actor.first_name, actor.last_name].filter(Boolean).join(" ").trim();
|
|
28
37
|
return {
|
|
@@ -32,9 +41,11 @@ export function mapNotificationApiActor(actor) {
|
|
|
32
41
|
}
|
|
33
42
|
export function mapNotificationApiRecord(record, options) {
|
|
34
43
|
const state = record.notification_state;
|
|
44
|
+
const type = mapSourceRecordType(record.source_record_type);
|
|
35
45
|
return {
|
|
36
46
|
id: String(record.id),
|
|
37
|
-
type
|
|
47
|
+
type,
|
|
48
|
+
kind: mapNotificationKind(record.kind, type),
|
|
38
49
|
actors: (record.actors ?? []).map(mapNotificationApiActor),
|
|
39
50
|
actionText: record.title,
|
|
40
51
|
timestampLabel: formatNotificationTimeAgo(record.created_at, {
|
|
@@ -14,6 +14,20 @@ export declare function getScopedNotifications(notifications: INotificationItem[
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function buildFiltersFromNotifications(notifications: INotificationItem[], tab: INotificationTab | undefined): INotificationFilter[];
|
|
16
16
|
export declare function resolveNotificationFilters(notifications: INotificationItem[], tab: INotificationTab | undefined, filters?: INotificationFilter[]): INotificationFilter[];
|
|
17
|
+
/**
|
|
18
|
+
* Keeps the last "all types" pill catalog while a kind filter is active, so chips
|
|
19
|
+
* don't collapse when results are already filtered (e.g. via `filter[kind][]`).
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveStickyNotificationFilters({ notifications, tab, providedFilters, activeFilterId, stickyFilters, }: {
|
|
22
|
+
notifications: INotificationItem[];
|
|
23
|
+
tab: INotificationTab | undefined;
|
|
24
|
+
providedFilters?: INotificationFilter[];
|
|
25
|
+
activeFilterId: string;
|
|
26
|
+
stickyFilters: INotificationFilter[];
|
|
27
|
+
}): {
|
|
28
|
+
filters: INotificationFilter[];
|
|
29
|
+
nextStickyFilters: INotificationFilter[];
|
|
30
|
+
};
|
|
17
31
|
export declare function filterHasContent(notifications: INotificationItem[], filter: INotificationFilter, tab: INotificationTab | undefined): boolean;
|
|
18
32
|
export declare function getVisibleFilters(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined): INotificationFilter[];
|
|
19
33
|
export declare function shouldShowFilterChips(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined, usesDynamicFilters?: boolean): boolean;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_NOTIFICATION_FILTERS,
|
|
3
|
+
NOTIFICATION_FILTER_CATEGORIES
|
|
4
|
+
} from "../constants/notification-types.js";
|
|
5
|
+
import { normalizeNotificationKind } from "./notification-api.js";
|
|
2
6
|
export function flattenNotifications(groups) {
|
|
3
7
|
return groups.flatMap((group) => group.notifications);
|
|
4
8
|
}
|
|
@@ -23,11 +27,19 @@ export function getInBadgeNotifications(notifications) {
|
|
|
23
27
|
export function getInBadgeCount(notifications) {
|
|
24
28
|
return getInBadgeNotifications(notifications).length;
|
|
25
29
|
}
|
|
30
|
+
function filterKinds(filter) {
|
|
31
|
+
if (filter.kinds?.length) {
|
|
32
|
+
return filter.kinds.map(normalizeNotificationKind);
|
|
33
|
+
}
|
|
34
|
+
return (filter.types ?? []).map((type) => normalizeNotificationKind(type));
|
|
35
|
+
}
|
|
26
36
|
export function notificationMatchesFilter(notification, filter) {
|
|
27
|
-
|
|
37
|
+
const kinds = filterKinds(filter);
|
|
38
|
+
if (!kinds.length) {
|
|
28
39
|
return true;
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
const notificationKind = normalizeNotificationKind(notification.kind || notification.type);
|
|
42
|
+
return kinds.includes(notificationKind);
|
|
31
43
|
}
|
|
32
44
|
export function getTabBadgeCount(notifications, tab) {
|
|
33
45
|
if (tab.unreadOnly || tab.id === "new") {
|
|
@@ -36,7 +48,7 @@ export function getTabBadgeCount(notifications, tab) {
|
|
|
36
48
|
return 0;
|
|
37
49
|
}
|
|
38
50
|
export function getFilterBadgeCount(notifications, filter) {
|
|
39
|
-
if (
|
|
51
|
+
if (filter.id === "all-types" || !filterKinds(filter).length) {
|
|
40
52
|
return 0;
|
|
41
53
|
}
|
|
42
54
|
return getInBadgeNotifications(notifications).filter(
|
|
@@ -51,9 +63,13 @@ export function getScopedNotifications(notifications, tab) {
|
|
|
51
63
|
}
|
|
52
64
|
export function buildFiltersFromNotifications(notifications, tab) {
|
|
53
65
|
const scoped = getScopedNotifications(notifications, tab);
|
|
54
|
-
const
|
|
66
|
+
const kindsPresent = new Set(
|
|
67
|
+
scoped.map(
|
|
68
|
+
(notification) => normalizeNotificationKind(notification.kind || notification.type)
|
|
69
|
+
)
|
|
70
|
+
);
|
|
55
71
|
const categories = NOTIFICATION_FILTER_CATEGORIES.filter(
|
|
56
|
-
(category) => category.
|
|
72
|
+
(category) => category.kinds.some((kind) => kindsPresent.has(normalizeNotificationKind(kind)))
|
|
57
73
|
);
|
|
58
74
|
if (categories.length < 2) {
|
|
59
75
|
return [];
|
|
@@ -69,6 +85,37 @@ export function resolveNotificationFilters(notifications, tab, filters) {
|
|
|
69
85
|
}
|
|
70
86
|
return buildFiltersFromNotifications(notifications, tab);
|
|
71
87
|
}
|
|
88
|
+
export function resolveStickyNotificationFilters({
|
|
89
|
+
notifications,
|
|
90
|
+
tab,
|
|
91
|
+
providedFilters,
|
|
92
|
+
activeFilterId,
|
|
93
|
+
stickyFilters
|
|
94
|
+
}) {
|
|
95
|
+
if (providedFilters?.length) {
|
|
96
|
+
return {
|
|
97
|
+
filters: providedFilters,
|
|
98
|
+
nextStickyFilters: stickyFilters
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const dynamicFilters = buildFiltersFromNotifications(notifications, tab);
|
|
102
|
+
let nextStickyFilters = stickyFilters;
|
|
103
|
+
if (activeFilterId === "all-types") {
|
|
104
|
+
nextStickyFilters = dynamicFilters;
|
|
105
|
+
} else if (!stickyFilters.length) {
|
|
106
|
+
nextStickyFilters = dynamicFilters.length ? dynamicFilters : [...DEFAULT_NOTIFICATION_FILTERS];
|
|
107
|
+
}
|
|
108
|
+
if (activeFilterId !== "all-types" && nextStickyFilters.length) {
|
|
109
|
+
return {
|
|
110
|
+
filters: nextStickyFilters,
|
|
111
|
+
nextStickyFilters
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
filters: dynamicFilters.length ? dynamicFilters : nextStickyFilters,
|
|
116
|
+
nextStickyFilters
|
|
117
|
+
};
|
|
118
|
+
}
|
|
72
119
|
export function filterHasContent(notifications, filter, tab) {
|
|
73
120
|
const scoped = getScopedNotifications(notifications, tab);
|
|
74
121
|
return scoped.some((notification) => notificationMatchesFilter(notification, filter));
|
|
@@ -76,7 +123,10 @@ export function filterHasContent(notifications, filter, tab) {
|
|
|
76
123
|
export function getVisibleFilters(notifications, filters, tab) {
|
|
77
124
|
return filters.filter((filter) => {
|
|
78
125
|
if (filter.id === "other") {
|
|
79
|
-
return notifications.some((notification) =>
|
|
126
|
+
return notifications.some((notification) => {
|
|
127
|
+
const kind = normalizeNotificationKind(notification.kind || notification.type);
|
|
128
|
+
return kind === "unknown" || notification.type === "unknown";
|
|
129
|
+
});
|
|
80
130
|
}
|
|
81
131
|
return true;
|
|
82
132
|
}).filter((filter) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apptegy/nuxt-navigation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.116",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,20 +36,19 @@
|
|
|
36
36
|
"primevue": "^4.2.5"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@nuxt/devtools": "
|
|
40
|
-
"@nuxt/eslint-config": "
|
|
41
|
-
"@nuxt/module-builder": "
|
|
42
|
-
"@nuxt/schema": "
|
|
43
|
-
"@nuxt/test-utils": "
|
|
44
|
-
"@types/node": "
|
|
45
|
-
"eslint": "
|
|
46
|
-
"nuxt": "
|
|
39
|
+
"@nuxt/devtools": "catalog:",
|
|
40
|
+
"@nuxt/eslint-config": "catalog:",
|
|
41
|
+
"@nuxt/module-builder": "catalog:",
|
|
42
|
+
"@nuxt/schema": "catalog:",
|
|
43
|
+
"@nuxt/test-utils": "catalog:",
|
|
44
|
+
"@types/node": "catalog:",
|
|
45
|
+
"eslint": "catalog:",
|
|
46
|
+
"nuxt": "catalog:",
|
|
47
47
|
"primevue": "^4.5.4",
|
|
48
|
-
"sass": "
|
|
49
|
-
"typescript": "
|
|
50
|
-
"vitest": "
|
|
51
|
-
"vue": "
|
|
52
|
-
"vue-tsc": "
|
|
53
|
-
}
|
|
54
|
-
"gitHead": "d57420f3128d73ad4ee181b2c437040434c0023c"
|
|
48
|
+
"sass": "catalog:",
|
|
49
|
+
"typescript": "catalog:",
|
|
50
|
+
"vitest": "catalog:",
|
|
51
|
+
"vue": "catalog:",
|
|
52
|
+
"vue-tsc": "catalog:"
|
|
53
|
+
}
|
|
55
54
|
}
|