@apptegy/nuxt-navigation 0.1.117 → 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 +5 -0
- package/dist/runtime/components/Nav/NotificationsPanel.vue +51 -13
- 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/dist/runtime/utils/notifications.js +2 -5
- 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",
|
|
@@ -32,11 +32,21 @@
|
|
|
32
32
|
:aria-label="$t('navigation.notificationsPanel.close')"
|
|
33
33
|
@click="$emit('close')"
|
|
34
34
|
>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
<svg
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
width="20"
|
|
38
|
+
height="20"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
stroke-width="2"
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
38
45
|
aria-hidden="true"
|
|
39
|
-
|
|
46
|
+
>
|
|
47
|
+
<path d="M18 6 6 18" />
|
|
48
|
+
<path d="m6 6 12 12" />
|
|
49
|
+
</svg>
|
|
40
50
|
</Button>
|
|
41
51
|
</div>
|
|
42
52
|
|
|
@@ -102,11 +112,20 @@
|
|
|
102
112
|
:aria-label="$t('navigation.notificationsPanel.scrollFilters')"
|
|
103
113
|
@click="scrollFilters"
|
|
104
114
|
>
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
<svg
|
|
116
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
117
|
+
width="16"
|
|
118
|
+
height="16"
|
|
119
|
+
viewBox="0 0 24 24"
|
|
120
|
+
fill="none"
|
|
121
|
+
stroke="currentColor"
|
|
122
|
+
stroke-width="2"
|
|
123
|
+
stroke-linecap="round"
|
|
124
|
+
stroke-linejoin="round"
|
|
108
125
|
aria-hidden="true"
|
|
109
|
-
|
|
126
|
+
>
|
|
127
|
+
<path d="m9 18 6-6-6-6" />
|
|
128
|
+
</svg>
|
|
110
129
|
</Button>
|
|
111
130
|
</div>
|
|
112
131
|
|
|
@@ -193,14 +212,18 @@ const props = defineProps({
|
|
|
193
212
|
loading: { type: Boolean, required: false, default: false },
|
|
194
213
|
activeTabId: { type: String, required: false },
|
|
195
214
|
activeFilterId: { type: String, required: false },
|
|
215
|
+
filterCatalog: { type: Array, required: false, default: () => [] },
|
|
196
216
|
embedded: { type: Boolean, required: false, default: false },
|
|
197
217
|
mobile: { type: Boolean, required: false, default: false }
|
|
198
218
|
});
|
|
199
|
-
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"]);
|
|
200
220
|
const filtersRef = ref();
|
|
201
221
|
const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
|
|
202
222
|
const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
|
|
203
|
-
const stickyFilters = ref([]);
|
|
223
|
+
const stickyFilters = ref([...props.filterCatalog ?? []]);
|
|
224
|
+
function filterCatalogKey(filters) {
|
|
225
|
+
return filters.map((filter) => filter.id).join("|");
|
|
226
|
+
}
|
|
204
227
|
watch(
|
|
205
228
|
() => props.activeTabId,
|
|
206
229
|
(value) => {
|
|
@@ -217,6 +240,18 @@ watch(
|
|
|
217
240
|
}
|
|
218
241
|
}
|
|
219
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
|
+
);
|
|
220
255
|
watch(
|
|
221
256
|
() => props.data.tabs,
|
|
222
257
|
(tabs) => {
|
|
@@ -231,9 +266,6 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
|
|
|
231
266
|
const activeTab = computed(
|
|
232
267
|
() => props.data.tabs.find((tab) => tab.id === activeTabId.value)
|
|
233
268
|
);
|
|
234
|
-
function filterCatalogKey(filters) {
|
|
235
|
-
return filters.map((filter) => filter.id).join("|");
|
|
236
|
-
}
|
|
237
269
|
watch(
|
|
238
270
|
[allNotifications, activeTab, activeFilterId, () => props.data.filters],
|
|
239
271
|
() => {
|
|
@@ -246,6 +278,7 @@ watch(
|
|
|
246
278
|
});
|
|
247
279
|
if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
|
|
248
280
|
stickyFilters.value = nextStickyFilters;
|
|
281
|
+
emit("update:filterCatalog", [...nextStickyFilters]);
|
|
249
282
|
}
|
|
250
283
|
},
|
|
251
284
|
{ immediate: true, deep: true }
|
|
@@ -481,6 +514,11 @@ function scrollFilters() {
|
|
|
481
514
|
width: 28px;
|
|
482
515
|
height: 28px;
|
|
483
516
|
padding: 0;
|
|
517
|
+
color: var(--grey-60, #6b7280);
|
|
518
|
+
}
|
|
519
|
+
.notifications-panel__filters-next :deep(svg) {
|
|
520
|
+
display: block;
|
|
521
|
+
flex-shrink: 0;
|
|
484
522
|
}
|
|
485
523
|
.notifications-panel__body {
|
|
486
524
|
overflow-y: auto;
|
|
@@ -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",
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_NOTIFICATION_FILTERS,
|
|
3
|
-
NOTIFICATION_FILTER_CATEGORIES
|
|
4
|
-
} from "../constants/notification-types.js";
|
|
1
|
+
import { NOTIFICATION_FILTER_CATEGORIES } from "../constants/notification-types.js";
|
|
5
2
|
import { normalizeNotificationKind } from "./notification-api.js";
|
|
6
3
|
export function flattenNotifications(groups) {
|
|
7
4
|
return groups.flatMap((group) => group.notifications);
|
|
@@ -103,7 +100,7 @@ export function resolveStickyNotificationFilters({
|
|
|
103
100
|
if (activeFilterId === "all-types") {
|
|
104
101
|
nextStickyFilters = dynamicFilters;
|
|
105
102
|
} else if (!stickyFilters.length) {
|
|
106
|
-
nextStickyFilters = dynamicFilters
|
|
103
|
+
nextStickyFilters = dynamicFilters;
|
|
107
104
|
}
|
|
108
105
|
if (activeFilterId !== "all-types" && nextStickyFilters.length) {
|
|
109
106
|
return {
|