@apptegy/nuxt-navigation 0.1.118 → 0.1.120
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 +23 -15
- package/dist/runtime/components/Nav/Header.vue.d.ts +4 -2
- 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
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<slot name="org-select" />
|
|
11
11
|
<slot name="user-controls" />
|
|
12
|
-
<div class="
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
<div class="header__end">
|
|
13
|
+
<slot name="searchbar" />
|
|
14
|
+
<div class="user-control-actions">
|
|
15
|
+
<div
|
|
16
|
+
v-if="showNotifications"
|
|
17
|
+
class="header__notifications"
|
|
18
|
+
>
|
|
17
19
|
<Button
|
|
18
20
|
type="button"
|
|
19
21
|
severity="secondary"
|
|
@@ -70,8 +72,10 @@
|
|
|
70
72
|
:loading="notificationsLoading"
|
|
71
73
|
:active-tab-id="activeTabId"
|
|
72
74
|
:active-filter-id="activeFilterId"
|
|
75
|
+
:filter-catalog="filterCatalog"
|
|
73
76
|
@update:active-tab-id="handleTabChange"
|
|
74
77
|
@update:active-filter-id="handleFilterChange"
|
|
78
|
+
@update:filter-catalog="filterCatalog = $event"
|
|
75
79
|
@clear-all="(payload) => $emit('notifications-clear-all', payload)"
|
|
76
80
|
@dismiss="(payload) => $emit('notifications-dismiss', payload)"
|
|
77
81
|
@select="handleNotificationSelect"
|
|
@@ -90,8 +94,10 @@
|
|
|
90
94
|
:loading="notificationsLoading"
|
|
91
95
|
:active-tab-id="activeTabId"
|
|
92
96
|
:active-filter-id="activeFilterId"
|
|
97
|
+
:filter-catalog="filterCatalog"
|
|
93
98
|
@update:active-tab-id="handleTabChange"
|
|
94
99
|
@update:active-filter-id="handleFilterChange"
|
|
100
|
+
@update:filter-catalog="filterCatalog = $event"
|
|
95
101
|
@clear-all="(payload) => $emit('notifications-clear-all', payload)"
|
|
96
102
|
@dismiss="(payload) => $emit('notifications-dismiss', payload)"
|
|
97
103
|
@select="handleNotificationSelect"
|
|
@@ -132,6 +138,7 @@
|
|
|
132
138
|
/>
|
|
133
139
|
</NuxtLink>
|
|
134
140
|
</template>
|
|
141
|
+
</div>
|
|
135
142
|
</div>
|
|
136
143
|
</header>
|
|
137
144
|
</template>
|
|
@@ -173,6 +180,7 @@ const notificationsLoading = computed(() => props.notifications?.loading ?? fals
|
|
|
173
180
|
const panelOpen = ref(props.notifications?.panelOpen ?? false);
|
|
174
181
|
const activeTabId = ref(props.notifications?.activeTabId || "new");
|
|
175
182
|
const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
|
|
183
|
+
const filterCatalog = ref([]);
|
|
176
184
|
const popoverPt = {
|
|
177
185
|
content: {
|
|
178
186
|
class: "notifications-popover__content",
|
|
@@ -236,16 +244,8 @@ watch(
|
|
|
236
244
|
}
|
|
237
245
|
);
|
|
238
246
|
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) {
|
|
247
|
+
patchNotifications({ panelOpen: value });
|
|
248
|
+
if (!value && !isMobilePanel.value) {
|
|
249
249
|
popoverRef.value?.hide();
|
|
250
250
|
}
|
|
251
251
|
});
|
|
@@ -316,10 +316,18 @@ header.header {
|
|
|
316
316
|
background-color: var(--primary-white);
|
|
317
317
|
font: var(--font-regular);
|
|
318
318
|
}
|
|
319
|
+
header.header .header__end {
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
justify-content: flex-end;
|
|
323
|
+
gap: 24px;
|
|
324
|
+
min-width: 0;
|
|
325
|
+
}
|
|
319
326
|
header.header .user-control-actions {
|
|
320
327
|
display: flex;
|
|
321
328
|
align-items: center;
|
|
322
329
|
gap: 8px;
|
|
330
|
+
flex-shrink: 0;
|
|
323
331
|
}
|
|
324
332
|
header.header .header__notifications {
|
|
325
333
|
position: relative;
|
|
@@ -17,7 +17,7 @@ interface IUserDropdown {
|
|
|
17
17
|
userDropdownOptions: Array<unknown>;
|
|
18
18
|
}
|
|
19
19
|
type __VLS_Props = IHeaderProps & IUserDropdown;
|
|
20
|
-
declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {},
|
|
20
|
+
declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_17: {};
|
|
21
21
|
type __VLS_Slots = {} & {
|
|
22
22
|
breadcrumbs?: (props: typeof __VLS_1) => any;
|
|
23
23
|
} & {
|
|
@@ -25,7 +25,9 @@ type __VLS_Slots = {} & {
|
|
|
25
25
|
} & {
|
|
26
26
|
'user-controls'?: (props: typeof __VLS_5) => any;
|
|
27
27
|
} & {
|
|
28
|
-
|
|
28
|
+
searchbar?: (props: typeof __VLS_7) => any;
|
|
29
|
+
} & {
|
|
30
|
+
notifications?: (props: typeof __VLS_17) => any;
|
|
29
31
|
};
|
|
30
32
|
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
33
|
"selected-option": (...args: any[]) => void;
|
|
@@ -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",
|