@apptegy/nuxt-navigation 0.1.118 → 0.1.119
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/runtime/components/Nav/Header.vue +7 -10
- package/dist/runtime/components/Nav/NotificationsPanel.vue +19 -5
- package/dist/runtime/components/Nav/NotificationsPanel.vue.d.ts +9 -1
- package/dist/runtime/types/notifications.d.ts +9 -1
- package/dist/runtime/utils/notification-api.d.ts +5 -0
- package/dist/runtime/utils/notification-api.js +6 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -70,8 +70,10 @@
|
|
|
70
70
|
:loading="notificationsLoading"
|
|
71
71
|
:active-tab-id="activeTabId"
|
|
72
72
|
:active-filter-id="activeFilterId"
|
|
73
|
+
:filter-catalog="filterCatalog"
|
|
73
74
|
@update:active-tab-id="handleTabChange"
|
|
74
75
|
@update:active-filter-id="handleFilterChange"
|
|
76
|
+
@update:filter-catalog="filterCatalog = $event"
|
|
75
77
|
@clear-all="(payload) => $emit('notifications-clear-all', payload)"
|
|
76
78
|
@dismiss="(payload) => $emit('notifications-dismiss', payload)"
|
|
77
79
|
@select="handleNotificationSelect"
|
|
@@ -90,8 +92,10 @@
|
|
|
90
92
|
:loading="notificationsLoading"
|
|
91
93
|
:active-tab-id="activeTabId"
|
|
92
94
|
:active-filter-id="activeFilterId"
|
|
95
|
+
:filter-catalog="filterCatalog"
|
|
93
96
|
@update:active-tab-id="handleTabChange"
|
|
94
97
|
@update:active-filter-id="handleFilterChange"
|
|
98
|
+
@update:filter-catalog="filterCatalog = $event"
|
|
95
99
|
@clear-all="(payload) => $emit('notifications-clear-all', payload)"
|
|
96
100
|
@dismiss="(payload) => $emit('notifications-dismiss', payload)"
|
|
97
101
|
@select="handleNotificationSelect"
|
|
@@ -173,6 +177,7 @@ const notificationsLoading = computed(() => props.notifications?.loading ?? fals
|
|
|
173
177
|
const panelOpen = ref(props.notifications?.panelOpen ?? false);
|
|
174
178
|
const activeTabId = ref(props.notifications?.activeTabId || "new");
|
|
175
179
|
const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
|
|
180
|
+
const filterCatalog = ref([]);
|
|
176
181
|
const popoverPt = {
|
|
177
182
|
content: {
|
|
178
183
|
class: "notifications-popover__content",
|
|
@@ -236,16 +241,8 @@ watch(
|
|
|
236
241
|
}
|
|
237
242
|
);
|
|
238
243
|
watch(panelOpen, (value) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
activeFilterId.value = "all-types";
|
|
244
|
-
patchNotifications({
|
|
245
|
-
panelOpen: false,
|
|
246
|
-
activeFilterId: "all-types"
|
|
247
|
-
});
|
|
248
|
-
if (!isMobilePanel.value) {
|
|
244
|
+
patchNotifications({ panelOpen: value });
|
|
245
|
+
if (!value && !isMobilePanel.value) {
|
|
249
246
|
popoverRef.value?.hide();
|
|
250
247
|
}
|
|
251
248
|
});
|
|
@@ -212,14 +212,18 @@ const props = defineProps({
|
|
|
212
212
|
loading: { type: Boolean, required: false, default: false },
|
|
213
213
|
activeTabId: { type: String, required: false },
|
|
214
214
|
activeFilterId: { type: String, required: false },
|
|
215
|
+
filterCatalog: { type: Array, required: false, default: () => [] },
|
|
215
216
|
embedded: { type: Boolean, required: false, default: false },
|
|
216
217
|
mobile: { type: Boolean, required: false, default: false }
|
|
217
218
|
});
|
|
218
|
-
const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "clear-all", "dismiss", "select", "close"]);
|
|
219
|
+
const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "update:filterCatalog", "clear-all", "dismiss", "select", "close"]);
|
|
219
220
|
const filtersRef = ref();
|
|
220
221
|
const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
|
|
221
222
|
const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
|
|
222
|
-
const stickyFilters = ref([]);
|
|
223
|
+
const stickyFilters = ref([...props.filterCatalog ?? []]);
|
|
224
|
+
function filterCatalogKey(filters) {
|
|
225
|
+
return filters.map((filter) => filter.id).join("|");
|
|
226
|
+
}
|
|
223
227
|
watch(
|
|
224
228
|
() => props.activeTabId,
|
|
225
229
|
(value) => {
|
|
@@ -236,6 +240,18 @@ watch(
|
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
242
|
);
|
|
243
|
+
watch(
|
|
244
|
+
() => props.filterCatalog,
|
|
245
|
+
(catalog) => {
|
|
246
|
+
if (!catalog?.length) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(catalog)) {
|
|
250
|
+
stickyFilters.value = [...catalog];
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{ deep: true }
|
|
254
|
+
);
|
|
239
255
|
watch(
|
|
240
256
|
() => props.data.tabs,
|
|
241
257
|
(tabs) => {
|
|
@@ -250,9 +266,6 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
|
|
|
250
266
|
const activeTab = computed(
|
|
251
267
|
() => props.data.tabs.find((tab) => tab.id === activeTabId.value)
|
|
252
268
|
);
|
|
253
|
-
function filterCatalogKey(filters) {
|
|
254
|
-
return filters.map((filter) => filter.id).join("|");
|
|
255
|
-
}
|
|
256
269
|
watch(
|
|
257
270
|
[allNotifications, activeTab, activeFilterId, () => props.data.filters],
|
|
258
271
|
() => {
|
|
@@ -265,6 +278,7 @@ watch(
|
|
|
265
278
|
});
|
|
266
279
|
if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
|
|
267
280
|
stickyFilters.value = nextStickyFilters;
|
|
281
|
+
emit("update:filterCatalog", [...nextStickyFilters]);
|
|
268
282
|
}
|
|
269
283
|
},
|
|
270
284
|
{ immediate: true, deep: true }
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { INotificationsPanelData } from '../../types/index.js';
|
|
1
|
+
import type { INotificationsPanelData, INotificationFilter } from '../../types/index.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
data: INotificationsPanelData;
|
|
4
4
|
loading?: boolean;
|
|
5
5
|
activeTabId?: string;
|
|
6
6
|
activeFilterId?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Last "all types" pill catalog, owned by the header so it survives panel remount.
|
|
9
|
+
* Lets a kind filter stay selected on reopen without inventing the full default catalog.
|
|
10
|
+
*/
|
|
11
|
+
filterCatalog?: INotificationFilter[];
|
|
7
12
|
embedded?: boolean;
|
|
8
13
|
mobile?: boolean;
|
|
9
14
|
};
|
|
@@ -15,6 +20,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
|
|
|
15
20
|
}) => any;
|
|
16
21
|
"update:activeTabId": (id: string) => any;
|
|
17
22
|
"update:activeFilterId": (id: string) => any;
|
|
23
|
+
"update:filterCatalog": (filters: INotificationFilter[]) => any;
|
|
18
24
|
"clear-all": (payload: {
|
|
19
25
|
groupId: string;
|
|
20
26
|
tabId: string;
|
|
@@ -34,6 +40,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
|
|
|
34
40
|
}) => any) | undefined;
|
|
35
41
|
"onUpdate:activeTabId"?: ((id: string) => any) | undefined;
|
|
36
42
|
"onUpdate:activeFilterId"?: ((id: string) => any) | undefined;
|
|
43
|
+
"onUpdate:filterCatalog"?: ((filters: INotificationFilter[]) => any) | undefined;
|
|
37
44
|
"onClear-all"?: ((payload: {
|
|
38
45
|
groupId: string;
|
|
39
46
|
tabId: string;
|
|
@@ -47,6 +54,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
|
|
|
47
54
|
onClose?: (() => any) | undefined;
|
|
48
55
|
}>, {
|
|
49
56
|
loading: boolean;
|
|
57
|
+
filterCatalog: INotificationFilter[];
|
|
50
58
|
embedded: boolean;
|
|
51
59
|
mobile: boolean;
|
|
52
60
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -19,6 +19,11 @@ export interface INotificationApiRecord {
|
|
|
19
19
|
detail_link?: string | null;
|
|
20
20
|
notification_state: NotificationApiState | string;
|
|
21
21
|
created_at: string;
|
|
22
|
+
/**
|
|
23
|
+
* Most recent activity on the notification (e.g. new reaction/comment).
|
|
24
|
+
* Prefer this over `created_at` for relative time and date grouping.
|
|
25
|
+
*/
|
|
26
|
+
last_activity_at?: string | null;
|
|
22
27
|
}
|
|
23
28
|
export interface INotificationsPagination {
|
|
24
29
|
page: number;
|
|
@@ -48,7 +53,10 @@ export interface INotificationItem {
|
|
|
48
53
|
actors: INotificationActor[];
|
|
49
54
|
actionText: string;
|
|
50
55
|
timestampLabel: string;
|
|
51
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* ISO-8601 timestamp used for group labels and relative time.
|
|
58
|
+
* Derived from API `last_activity_at`, falling back to `created_at`.
|
|
59
|
+
*/
|
|
52
60
|
createdAt?: string;
|
|
53
61
|
contextLabel?: string;
|
|
54
62
|
/** When false, the notification counts toward the bell badge and appears in the New tab. */
|
|
@@ -5,6 +5,11 @@ export declare const DEFAULT_NOTIFICATION_TABS: INotificationTab[];
|
|
|
5
5
|
export declare function normalizeNotificationKind(value: string): string;
|
|
6
6
|
export declare function mapSourceRecordType(sourceRecordType: string): NotificationType;
|
|
7
7
|
export declare function mapNotificationKind(kind: string | null | undefined, fallbackType: NotificationType): string;
|
|
8
|
+
/** Prefer `last_activity_at` for display/grouping; fall back to `created_at`. */
|
|
9
|
+
export declare function resolveNotificationActivityAt(record: {
|
|
10
|
+
created_at: string;
|
|
11
|
+
last_activity_at?: string | null;
|
|
12
|
+
}): string;
|
|
8
13
|
export declare function mapNotificationApiActor(actor: INotificationApiActor): {
|
|
9
14
|
name: string;
|
|
10
15
|
avatarUrl: string | undefined;
|
|
@@ -32,6 +32,9 @@ export function mapNotificationKind(kind, fallbackType) {
|
|
|
32
32
|
}
|
|
33
33
|
return normalizeNotificationKind(kind) || normalizeNotificationKind(fallbackType);
|
|
34
34
|
}
|
|
35
|
+
export function resolveNotificationActivityAt(record) {
|
|
36
|
+
return record.last_activity_at || record.created_at;
|
|
37
|
+
}
|
|
35
38
|
export function mapNotificationApiActor(actor) {
|
|
36
39
|
const name = [actor.first_name, actor.last_name].filter(Boolean).join(" ").trim();
|
|
37
40
|
return {
|
|
@@ -42,13 +45,14 @@ export function mapNotificationApiActor(actor) {
|
|
|
42
45
|
export function mapNotificationApiRecord(record, options) {
|
|
43
46
|
const state = record.notification_state;
|
|
44
47
|
const type = mapSourceRecordType(record.source_record_type);
|
|
48
|
+
const activityAt = resolveNotificationActivityAt(record);
|
|
45
49
|
return {
|
|
46
50
|
id: String(record.id),
|
|
47
51
|
type,
|
|
48
52
|
kind: mapNotificationKind(record.kind, type),
|
|
49
53
|
actors: (record.actors ?? []).map(mapNotificationApiActor),
|
|
50
54
|
actionText: record.title,
|
|
51
|
-
timestampLabel: formatNotificationTimeAgo(
|
|
55
|
+
timestampLabel: formatNotificationTimeAgo(activityAt, {
|
|
52
56
|
locale: options.locale,
|
|
53
57
|
now: options.now,
|
|
54
58
|
labels: {
|
|
@@ -56,7 +60,7 @@ export function mapNotificationApiRecord(record, options) {
|
|
|
56
60
|
yesterdayTimeAgo: options.dateLabels.yesterdayTimeAgo
|
|
57
61
|
}
|
|
58
62
|
}),
|
|
59
|
-
createdAt:
|
|
63
|
+
createdAt: activityAt,
|
|
60
64
|
contextLabel: record.summary,
|
|
61
65
|
dismissed: state === "dismissed",
|
|
62
66
|
read: state === "read",
|